From 7040e35f1f1de482579967cf1180195b1d0b6a93 Mon Sep 17 00:00:00 2001 From: blackspherefollower Date: Sat, 9 Nov 2019 16:03:49 +0000 Subject: [PATCH] feat: Adding Lovehoney Desire device support --- .../LovehoneyDesireKnickerVibeTests.cs | 99 +++++++++++++++ .../LovehoneyDesireProstateVibeTests.cs | 100 +++++++++++++++ .../DeviceConfigurationManager.cs | 1 + .../Protocols/LovehoneyDesireProtocol.cs | 120 ++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 Buttplug.Test/Devices/Protocols/LovehoneyDesireKnickerVibeTests.cs create mode 100644 Buttplug.Test/Devices/Protocols/LovehoneyDesireProstateVibeTests.cs create mode 100644 Buttplug/Devices/Protocols/LovehoneyDesireProtocol.cs diff --git a/Buttplug.Test/Devices/Protocols/LovehoneyDesireKnickerVibeTests.cs b/Buttplug.Test/Devices/Protocols/LovehoneyDesireKnickerVibeTests.cs new file mode 100644 index 00000000..0fd7f3bc --- /dev/null +++ b/Buttplug.Test/Devices/Protocols/LovehoneyDesireKnickerVibeTests.cs @@ -0,0 +1,99 @@ +// +// Buttplug C# Source Code File - Visit https://buttplug.io for more info about the project. +// Copyright (c) Nonpolynomial Labs LLC. All rights reserved. +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root for full license information. +// + +// Test file, disable ConfigureAwait checking. +// ReSharper disable ConsiderUsingConfigureAwait + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Buttplug.Core.Messages; +using Buttplug.Devices; +using Buttplug.Devices.Protocols; +using Buttplug.Test.Devices.Protocols.Utils; +using JetBrains.Annotations; +using NUnit.Framework; + +namespace Buttplug.Test.Devices.Protocols +{ + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Test classes can skip documentation requirements")] + [TestFixture] + public class LovehoneyDesireKnickerVibeTests + { + [NotNull] + private ProtocolTestUtils testUtil; + + [SetUp] + public async Task Init() + { + testUtil = new ProtocolTestUtils(); + await testUtil.SetupTest("KNICKER VIBE"); + } + + [Test] + public void TestAllowedMessages() + { + testUtil.TestDeviceAllowedMessages(new Dictionary() + { + { typeof(StopDeviceCmd), 0 }, + { typeof(SingleMotorVibrateCmd), 0 }, + { typeof(VibrateCmd), 1 }, + }); + } + + // StopDeviceCmd noop test handled in GeneralDeviceTests + + [Test] + public async Task TestStopDeviceCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x01, 0x40 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new SingleMotorVibrateCmd(4, 0.5), expected, false); + + expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x01, 0x00 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new StopDeviceCmd(4), expected, false); + } + + [Test] + public async Task TestSingleMotorVibrateCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x01, 0x40 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new SingleMotorVibrateCmd(4, 0.5), expected, false); + } + + [Test] + public async Task TestVibrateCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x01, 0x40 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(VibrateCmd.Create(4, 1, 0.5, 1), expected, false); + } + + [Test] + public void TestInvalidVibrateCmd() + { + testUtil.TestInvalidVibrateCmd(1); + } + } +} \ No newline at end of file diff --git a/Buttplug.Test/Devices/Protocols/LovehoneyDesireProstateVibeTests.cs b/Buttplug.Test/Devices/Protocols/LovehoneyDesireProstateVibeTests.cs new file mode 100644 index 00000000..5f1e623f --- /dev/null +++ b/Buttplug.Test/Devices/Protocols/LovehoneyDesireProstateVibeTests.cs @@ -0,0 +1,100 @@ +// +// Buttplug C# Source Code File - Visit https://buttplug.io for more info about the project. +// Copyright (c) Nonpolynomial Labs LLC. All rights reserved. +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root for full license information. +// + +// Test file, disable ConfigureAwait checking. +// ReSharper disable ConsiderUsingConfigureAwait + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Buttplug.Core.Messages; +using Buttplug.Devices; +using Buttplug.Devices.Protocols; +using Buttplug.Test.Devices.Protocols.Utils; +using JetBrains.Annotations; +using NUnit.Framework; + +namespace Buttplug.Test.Devices.Protocols +{ + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Test classes can skip documentation requirements")] + [TestFixture] + public class LovehoneyDesireProstateVibeTests + { + [NotNull] + private ProtocolTestUtils testUtil; + + [SetUp] + public async Task Init() + { + testUtil = new ProtocolTestUtils(); + await testUtil.SetupTest("PROSTATE VIBE"); + } + + [Test] + public void TestAllowedMessages() + { + testUtil.TestDeviceAllowedMessages(new Dictionary() + { + { typeof(StopDeviceCmd), 0 }, + { typeof(SingleMotorVibrateCmd), 0 }, + { typeof(VibrateCmd), 2 }, + }); + } + + // StopDeviceCmd noop test handled in GeneralDeviceTests + + [Test] + public async Task TestStopDeviceCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x00, 0x40 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new SingleMotorVibrateCmd(4, 0.5), expected, false); + + expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x00, 0x00 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new StopDeviceCmd(4), expected, false); + } + + [Test] + public async Task TestSingleMotorVibrateCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x00, 0x40 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(new SingleMotorVibrateCmd(4, 0.5), expected, false); + } + + [Test] + public async Task TestVibrateCmd() + { + var expected = + new List<(byte[], string)>() + { + (new byte[] { 0xF3, 0x01, 0x40 }, Endpoints.Tx), + (new byte[] { 0xF3, 0x02, 0x00 }, Endpoints.Tx), + }; + + await testUtil.TestDeviceMessage(VibrateCmd.Create(4, 1, 0.5, 1), expected, false); + } + + [Test] + public void TestInvalidVibrateCmd() + { + testUtil.TestInvalidVibrateCmd(2); + } + } +} \ No newline at end of file diff --git a/Buttplug/Devices/Configuration/DeviceConfigurationManager.cs b/Buttplug/Devices/Configuration/DeviceConfigurationManager.cs index a7224449..7cb9072c 100644 --- a/Buttplug/Devices/Configuration/DeviceConfigurationManager.cs +++ b/Buttplug/Devices/Configuration/DeviceConfigurationManager.cs @@ -53,6 +53,7 @@ protected DeviceConfigurationManager() AddProtocol("kiiroo-v21-vibrator", typeof(KiirooGen21VibeProtocol)); AddProtocol("lelo-f1s", typeof(LeloF1sProtocol)); AddProtocol("libo", typeof(LiBoProtocol)); + AddProtocol("lovehoney-desire", typeof(LovehoneyDesireProtocol)); AddProtocol("magic-motion", typeof(MagicMotionProtocol)); AddProtocol("mysteryvibe", typeof(MysteryVibeProtocol)); AddProtocol("picobong", typeof(PicobongProtocol)); diff --git a/Buttplug/Devices/Protocols/LovehoneyDesireProtocol.cs b/Buttplug/Devices/Protocols/LovehoneyDesireProtocol.cs new file mode 100644 index 00000000..ae91242f --- /dev/null +++ b/Buttplug/Devices/Protocols/LovehoneyDesireProtocol.cs @@ -0,0 +1,120 @@ +// +// Buttplug C# Source Code File - Visit https://buttplug.io for more info about the project. +// Copyright (c) Nonpolynomial Labs LLC. All rights reserved. +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Buttplug.Core.Logging; +using Buttplug.Core.Messages; + +namespace Buttplug.Devices.Protocols +{ + internal class LovehoneyDesireProtocol : ButtplugDeviceProtocol + { + private readonly double[] _vibratorSpeeds = { 0, 0 }; + + internal struct LovehoneyDesireType + { + public string Name; + public uint VibeCount; + } + + internal readonly Dictionary _deviceMap = new Dictionary() + { + { "PROSTATE VIBE", new LovehoneyDesireType() { Name = "Prostate Vibrator", VibeCount = 2 } }, + { "KNICKER VIBE", new LovehoneyDesireType() { Name = "Knicker Vibrator", VibeCount = 1 } }, + }; + + private readonly LovehoneyDesireType _devInfo; + + public LovehoneyDesireProtocol(IButtplugLogManager aLogManager, + IButtplugDeviceImpl aInterface) + : base(aLogManager, + "Lovehoney Desire Device", + aInterface) + { + if (_deviceMap.TryGetValue(aInterface.Name, out var dev)) + { + _devInfo = dev; + Name = $"Lovehoney Desire {dev.Name}"; + } + else + { + _devInfo = new LovehoneyDesireType() + { + Name = "Unknown Lovehoney Desire Device", + VibeCount = 1, + }; + } + + AddMessageHandler(HandleSingleMotorVibrateCmd); + AddMessageHandler(HandleVibrateCmd, new MessageAttributes() { FeatureCount = _devInfo.VibeCount }); + AddMessageHandler(HandleStopDeviceCmd); + } + + private async Task HandleStopDeviceCmd(ButtplugDeviceMessage aMsg, CancellationToken aToken) + { + BpLogger.Debug("Stopping Device " + Name); + return await HandleSingleMotorVibrateCmd(new SingleMotorVibrateCmd(aMsg.DeviceIndex, 0, aMsg.Id), aToken).ConfigureAwait(false); + } + + private async Task HandleSingleMotorVibrateCmd(ButtplugDeviceMessage aMsg, CancellationToken aToken) + { + var cmdMsg = CheckMessageHandler(aMsg); + + return await HandleVibrateCmd(VibrateCmd.Create(cmdMsg.DeviceIndex, cmdMsg.Id, cmdMsg.Speed, _devInfo.VibeCount), aToken).ConfigureAwait(false); + } + + private async Task HandleVibrateCmd(ButtplugDeviceMessage aMsg, CancellationToken aToken) + { + var cmdMsg = CheckGenericMessageHandler(aMsg, _devInfo.VibeCount); + var changed = new[] { false, false }; + + foreach (var v in cmdMsg.Speeds) + { + if (Math.Abs(v.Speed - _vibratorSpeeds[v.Index]) < 0.001) + { + continue; + } + + changed[v.Index] = true; + _vibratorSpeeds[v.Index] = v.Speed; + } + + if (_devInfo.VibeCount >= 2 && + Math.Abs(_vibratorSpeeds[0] - _vibratorSpeeds[1]) < 0.001 && + (changed[0] || changed[1] || !SentVibration)) + { + // Values are the same, so use global setter + await Interface.WriteValueAsync( + new byte[] { 0xF3, 0, (byte)Convert.ToUInt32(_vibratorSpeeds[0] * 0x7F) }, aToken) + .ConfigureAwait(false); + + SentVibration = true; + return new Ok(aMsg.Id); + } + + if (changed[0] || !SentVibration) + { + await Interface.WriteValueAsync( + new byte[] { 0xF3, 1, (byte)Convert.ToUInt32(_vibratorSpeeds[0] * 0x7F) }, aToken) + .ConfigureAwait(false); + } + + if (_devInfo.VibeCount >= 2 && (changed[1] || !SentVibration)) + { + await Interface.WriteValueAsync( + new byte[] { 0xF3, 2, (byte)Convert.ToUInt32(_vibratorSpeeds[1] * 0x7F) }, aToken) + .ConfigureAwait(false); + } + + SentVibration = true; + return new Ok(aMsg.Id); + } + } +}