Skip to content

Commit 4fe144e

Browse files
committed
feat: Add basic Mysteryvibe Crescendo support
Connects to device, sets it to real time control mode, allows sending of packets. Fixes #406
1 parent e9e61b4 commit 4fe144e

2 files changed

Lines changed: 178 additions & 0 deletions

File tree

Buttplug.Server/Bluetooth/BluetoothSubtypeManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ protected BluetoothSubtypeManager([NotNull] IButtplugLogManager aLogManager)
2828
new VibratissimoBluetoothInfo(),
2929
new VorzeSABluetoothInfo(),
3030
new WeVibeBluetoothInfo(),
31+
new MysteryVibeBluetoothInfo(),
3132
};
3233
}
3334
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using System.Timers;
7+
using Buttplug.Core;
8+
using Buttplug.Core.Messages;
9+
10+
namespace Buttplug.Server.Bluetooth.Devices
11+
{
12+
internal class MysteryVibeBluetoothInfo : IBluetoothDeviceInfo
13+
{
14+
public enum Chrs : uint
15+
{
16+
ModeControl = 0,
17+
MotorControl = 1,
18+
}
19+
20+
public Guid[] Services { get; } = { new Guid("f0006900-110c-478b-b74b-6f403b364a9c") };
21+
22+
public Dictionary<uint, Guid> Characteristics { get; } = new Dictionary<uint, Guid>()
23+
{
24+
{ (uint)Chrs.ModeControl, new Guid("f0006901-110c-478B-B74B-6F403B364A9C") },
25+
{ (uint)Chrs.MotorControl, new Guid("f0006903-110c-478B-B74B-6F403B364A9C") },
26+
};
27+
28+
public string[] NamePrefixes { get; } = { };
29+
30+
public string[] Names { get; } =
31+
{
32+
"MV Crescendo",
33+
};
34+
35+
public IButtplugDevice CreateDevice(IButtplugLogManager aLogManager,
36+
IBluetoothDeviceInterface aInterface)
37+
{
38+
return new MysteryVibe(aLogManager, aInterface, this);
39+
}
40+
}
41+
42+
internal class MysteryVibe : ButtplugBluetoothDevice
43+
{
44+
internal static readonly byte[] NullSpeed = { 0, 0, 0, 0, 0, 0 };
45+
46+
// This max speed seems weird, but going over it causes the device to slow down?
47+
internal static readonly byte MaxSpeed = 56;
48+
49+
// Approximate timing delay taken from watching packet timing and testing manually.
50+
internal static readonly uint DelayTimeMS = 93;
51+
52+
private byte[] _vibratorSpeeds = NullSpeed;
53+
private readonly System.Timers.Timer _updateValueTimer = new System.Timers.Timer();
54+
55+
public MysteryVibe(IButtplugLogManager aLogManager,
56+
IBluetoothDeviceInterface aInterface,
57+
IBluetoothDeviceInfo aInfo)
58+
: base(aLogManager,
59+
$"MysteryVibe Crescendo",
60+
aInterface,
61+
aInfo)
62+
{
63+
// Create a new timer that wont fire any events just yet
64+
65+
_updateValueTimer.Interval = DelayTimeMS;
66+
_updateValueTimer.Elapsed += MysteryVibeUpdateHandler;
67+
_updateValueTimer.Enabled = false;
68+
aInterface.DeviceRemoved += OnDeviceRemoved;
69+
70+
MsgFuncs.Add(typeof(SingleMotorVibrateCmd), new ButtplugDeviceWrapper(HandleSingleMotorVibrateCmd));
71+
MsgFuncs.Add(typeof(VibrateCmd), new ButtplugDeviceWrapper(HandleVibrateCmd, new MessageAttributes() { FeatureCount = 6 }));
72+
MsgFuncs.Add(typeof(StopDeviceCmd), new ButtplugDeviceWrapper(HandleStopDeviceCmd));
73+
}
74+
75+
public override async Task<ButtplugMessage> Initialize()
76+
{
77+
BpLogger.Trace($"Initializing {Name}");
78+
79+
// Kick Vibrator into motor control mode, just copying what the app sends when you go to
80+
// create pattern mode.
81+
return await Interface.WriteValue(ButtplugConsts.SystemMsgId,
82+
(uint)MysteryVibeBluetoothInfo.Chrs.ModeControl,
83+
new byte[] { 0x43, 0x02, 0x00 }, true);
84+
}
85+
86+
private void OnDeviceRemoved(object aEvent, EventArgs aArgs)
87+
{
88+
// Timer should be turned off on removal.
89+
_updateValueTimer.Enabled = false;
90+
91+
// Clean up event handler for that magic day when devices manage to disconnect.
92+
Interface.DeviceRemoved -= OnDeviceRemoved;
93+
}
94+
95+
private async void MysteryVibeUpdateHandler(object aEvent, ElapsedEventArgs aArgs)
96+
{
97+
if (_vibratorSpeeds.SequenceEqual(NullSpeed))
98+
{
99+
_updateValueTimer.Enabled = false;
100+
}
101+
102+
if (await Interface.WriteValue(ButtplugConsts.DefaultMsgId,
103+
(uint)MysteryVibeBluetoothInfo.Chrs.MotorControl,
104+
_vibratorSpeeds) is Error errorMsg)
105+
{
106+
BpLogger.Error($"Cannot send update to {Name}, device may stop moving.");
107+
_updateValueTimer.Enabled = false;
108+
}
109+
}
110+
111+
private async Task<ButtplugMessage> HandleStopDeviceCmd(ButtplugDeviceMessage aMsg)
112+
{
113+
BpLogger.Debug($"Stopping Device {Name}");
114+
return await HandleSingleMotorVibrateCmd(new SingleMotorVibrateCmd(aMsg.DeviceIndex, 0, aMsg.Id));
115+
}
116+
117+
private async Task<ButtplugMessage> HandleSingleMotorVibrateCmd(ButtplugDeviceMessage aMsg)
118+
{
119+
if (!(aMsg is SingleMotorVibrateCmd cmdMsg))
120+
{
121+
return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
122+
}
123+
124+
return await HandleVibrateCmd(
125+
VibrateCmd.Create(cmdMsg.DeviceIndex, cmdMsg.Id, cmdMsg.Speed, 6));
126+
}
127+
128+
private async Task<ButtplugMessage> HandleVibrateCmd(ButtplugDeviceMessage aMsg)
129+
{
130+
if (!(aMsg is VibrateCmd cmdMsg))
131+
{
132+
return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
133+
}
134+
135+
if (cmdMsg.Speeds.Count < 1 || cmdMsg.Speeds.Count > 6)
136+
{
137+
return new Error(
138+
"VibrateCmd requires 1-6 commands for this device.",
139+
Error.ErrorClass.ERROR_DEVICE,
140+
cmdMsg.Id);
141+
}
142+
143+
var newVibratorSpeeds = (byte[])_vibratorSpeeds.Clone();
144+
145+
foreach (var v in cmdMsg.Speeds)
146+
{
147+
if (v.Index > 5)
148+
{
149+
return new Error(
150+
$"Index {v.Index} is out of bounds for VibrateCmd for this device.",
151+
Error.ErrorClass.ERROR_DEVICE,
152+
cmdMsg.Id);
153+
}
154+
155+
newVibratorSpeeds[v.Index] = (byte)(v.Speed * MaxSpeed);
156+
}
157+
158+
if (newVibratorSpeeds.SequenceEqual(_vibratorSpeeds))
159+
{
160+
return new Ok(aMsg.Id);
161+
}
162+
163+
_vibratorSpeeds = newVibratorSpeeds;
164+
165+
if (!_updateValueTimer.Enabled)
166+
{
167+
// Run the update handler once to start the command
168+
MysteryVibeUpdateHandler(null, null);
169+
170+
// Start the timer to it will keep updating
171+
_updateValueTimer.Enabled = true;
172+
}
173+
174+
return new Ok(aMsg.Id);
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)