Skip to content

Commit bb87264

Browse files
bnm12qdot
authored andcommitted
Feature: Adding basic support for the LiBo Whale toy (PiPiJing)
1 parent 99a642c commit bb87264

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

Buttplug.Server/Bluetooth/BluetoothSubtypeManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ protected BluetoothSubtypeManager([NotNull] IButtplugLogManager aLogManager)
2929
new VorzeSABluetoothInfo(),
3030
new WeVibeBluetoothInfo(),
3131
new MysteryVibeBluetoothInfo(),
32+
new LiBoBluetoothInfo(),
3233
};
3334
}
3435
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Buttplug.Core;
6+
using Buttplug.Core.Messages;
7+
8+
namespace Buttplug.Server.Bluetooth.Devices
9+
{
10+
internal class LiBoBluetoothInfo : IBluetoothDeviceInfo
11+
{
12+
public enum Chrs : uint
13+
{
14+
Tx1 = 0,
15+
Tx2,
16+
Rx,
17+
}
18+
19+
public Guid[] Services { get; } =
20+
{
21+
new Guid("00006000-0000-1000-8000-00805f9b34fb"), // Write Service
22+
new Guid("00006050-0000-1000-8000-00805f9b34fb"), // Read service (battery)
23+
24+
// I'm pretty sure that the 2nd one here is ignored due to a call to Services.First() in the bluetooth manager
25+
};
26+
27+
public string[] Names { get; } =
28+
{
29+
"PiPiJing"
30+
};
31+
32+
public Guid[] Characteristics { get; } =
33+
{
34+
// tx1 characteristic
35+
new Guid("00006001-0000-1000-8000-00805f9b34fb"), // Shock
36+
37+
// tx2 characteristic
38+
new Guid("00006002-0000-1000-8000-00805f9b34fb"), // VibeMode
39+
40+
// rx characteristic
41+
new Guid("00006051-0000-1000-8000-00805f9b34fb"), // Read for battery level
42+
};
43+
44+
public IButtplugDevice CreateDevice(IButtplugLogManager aLogManager,
45+
IBluetoothDeviceInterface aInterface)
46+
{
47+
return new LiBo(aLogManager, aInterface, this);
48+
}
49+
}
50+
51+
internal class LiBo : ButtplugBluetoothDevice
52+
{
53+
private readonly uint _vibratorCount = 1;
54+
private readonly uint _shockerCount = 1;
55+
private readonly double[] _vibratorSpeed = { 0 };
56+
private readonly double[] _shockerIntensity = { 0 };
57+
58+
public LiBo(IButtplugLogManager aLogManager,
59+
IBluetoothDeviceInterface aInterface,
60+
IBluetoothDeviceInfo aInfo)
61+
: base(aLogManager,
62+
$"LiBo Device ({aInterface.Name})",
63+
aInterface,
64+
aInfo)
65+
{
66+
MsgFuncs.Add(typeof(SingleMotorVibrateCmd), new ButtplugDeviceWrapper(HandleSingleMotorVibrateCmd));
67+
MsgFuncs.Add(typeof(VibrateCmd), new ButtplugDeviceWrapper(HandleVibrateCmd, new MessageAttributes() { FeatureCount = _vibratorCount }));
68+
// Here goes a handler for Estim shocking
69+
MsgFuncs.Add(typeof(StopDeviceCmd), new ButtplugDeviceWrapper(HandleStopDeviceCmd));
70+
}
71+
72+
private async Task<ButtplugMessage> HandleStopDeviceCmd(ButtplugDeviceMessage aMsg)
73+
{
74+
return await HandleSingleMotorVibrateCmd(new SingleMotorVibrateCmd(aMsg.DeviceIndex, 0, aMsg.Id));
75+
}
76+
77+
private async Task<ButtplugMessage> HandleSingleMotorVibrateCmd(ButtplugDeviceMessage aMsg)
78+
{
79+
if (!(aMsg is SingleMotorVibrateCmd cmdMsg))
80+
{
81+
return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
82+
}
83+
84+
var subCmds = new List<VibrateCmd.VibrateSubcommand>();
85+
for (var i = 0u; i < _vibratorCount; i++)
86+
{
87+
subCmds.Add(new VibrateCmd.VibrateSubcommand(i, cmdMsg.Speed));
88+
}
89+
90+
return await HandleVibrateCmd(new VibrateCmd(cmdMsg.DeviceIndex, subCmds, cmdMsg.Id));
91+
}
92+
93+
private async Task<ButtplugMessage> HandleVibrateCmd(ButtplugDeviceMessage aMsg)
94+
{
95+
if (!(aMsg is VibrateCmd cmdMsg))
96+
{
97+
return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
98+
}
99+
100+
if (cmdMsg.Speeds.Count < 1 || cmdMsg.Speeds.Count > _vibratorCount)
101+
{
102+
return new Error(
103+
$"VibrateCmd requires between 1 and {_vibratorCount} vectors for this device.",
104+
Error.ErrorClass.ERROR_DEVICE,
105+
cmdMsg.Id);
106+
}
107+
108+
var changed = false;
109+
foreach (var v in cmdMsg.Speeds)
110+
{
111+
if (v.Index >= _vibratorCount)
112+
{
113+
return new Error(
114+
$"Index {v.Index} is out of bounds for VibrateCmd for this device.",
115+
Error.ErrorClass.ERROR_DEVICE,
116+
cmdMsg.Id);
117+
}
118+
119+
if (!(Math.Abs(v.Speed - _vibratorSpeed[v.Index]) > 0.001))
120+
{
121+
continue;
122+
}
123+
124+
changed = true;
125+
_vibratorSpeed[v.Index] = v.Speed;
126+
}
127+
128+
if (!changed)
129+
{
130+
return new Ok(cmdMsg.Id);
131+
}
132+
133+
int mode = (int)Math.Ceiling(_vibratorSpeed[0] * 3); // Map a 0 - 100% value to a 0 - 3 value since 0 * x == 0 this will turn off the vibe if speed is 0.00
134+
135+
var data = new byte[] { Convert.ToByte(mode) };
136+
137+
return await Interface.WriteValue(aMsg.Id,
138+
Info.Characteristics[(uint)LiBoBluetoothInfo.Chrs.Tx2],
139+
data);
140+
}
141+
142+
private int getBatteryLevel()
143+
{
144+
throw new NotImplementedException("While most of this method is done it still needs to be adjusted/completed - see the comments");
145+
int batteryLevel = Convert.ToInt32(0x64 /* 100 in hex */); // Read from Info.Services[1] + Info.Characteristics[uint)LiBoBluetoothInfo.Chrs.Rx] here
146+
return batteryLevel; // As far as I can tell we currently have no way of reading values
147+
}
148+
149+
private async Task<ButtplugMessage> HandleShockCmd(ButtplugDeviceMessage aMsg)
150+
{
151+
throw new NotImplementedException("While most of this method is done it still needs to be adjusted/completed - see the comments");
152+
if (!(aMsg is VibrateCmd cmdMsg)) // ShockCmd once it gets implemented
153+
{
154+
return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
155+
}
156+
157+
if (cmdMsg.Speeds.Count < 1 || cmdMsg.Speeds.Count > _shockerCount)
158+
{
159+
return new Error(
160+
$"ShockCmd requires between 1 and {_shockerCount} vectors for this device.", // Fix cmd name once settled
161+
Error.ErrorClass.ERROR_DEVICE,
162+
cmdMsg.Id);
163+
}
164+
165+
var changed = false;
166+
foreach (var v in cmdMsg.Speeds)
167+
{
168+
if (v.Index >= _shockerCount)
169+
{
170+
return new Error(
171+
$"Index {v.Index} is out of bounds for VibrateCmd for this device.", // Fix cmd name once settled
172+
Error.ErrorClass.ERROR_DEVICE,
173+
cmdMsg.Id);
174+
}
175+
176+
if (!(Math.Abs(v.Speed - _shockerIntensity[v.Index]) > 0.001))
177+
{
178+
continue;
179+
}
180+
181+
changed = true;
182+
_shockerIntensity[v.Index] = v.Speed;
183+
}
184+
185+
if (!changed)
186+
{
187+
return new Ok(cmdMsg.Id);
188+
}
189+
190+
int speed = 0;
191+
192+
// If intensity is set to 0 skip this with 0x00 and turn the shocker off
193+
if (_shockerIntensity[0] > 0)
194+
{
195+
speed = (int)Math.Floor(_shockerIntensity[0] * 7) << 4; // Map a 0 - 100% value to a 0 - 6 value shift 4 to have 0xY0 where Y is speed
196+
speed++; // add 1 to speed to get 0xY1 where Y is speed and 1 is mode 1 (constant shock)
197+
}
198+
199+
var data = new byte[] { Convert.ToByte(speed) };
200+
201+
return await Interface.WriteValue(aMsg.Id,
202+
Info.Characteristics[(uint)LiBoBluetoothInfo.Chrs.Tx1],
203+
data);
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)