Giter Club home page Giter Club logo

buttplug's Introduction

Buttplug

Patreon donate button Github donate button Discourse Forums Discord Twitter

Crates.io Version Crates.io Downloads Crates.io License

A Rust implementation of the Buttplug Intimate Hardware Control Protocol, including a client and server. This is the core implementation of Buttplug.

This repo is a monorepo with multiple projects, including:

  • buttplug - Rust implementation of the Buttplug protocol spec
  • buttplug-schema - JSON schema for the Buttplug protocol spec
  • buttplug-device-config - Device configuration file for buttplug (where we store all of the device identifiers)
  • buttplug_derive - Procedural macros used by the buttplug rust library.

For information about compiling and using these libraries, please check the README files in their directories.

For a list of applications using Buttplug, see the awesome-buttplug repo.

Other Language Implementations

See the awesome-buttplug repo for a full list of implementations.

buttplug's People

Contributors

anon1efergwerfwer avatar atkdg avatar blackspherefollower avatar chadbrewbaker avatar dependabot[bot] avatar felixonmars avatar funjack avatar github-d3p avatar joe1994 avatar lemon-sh avatar ljzd-pro avatar mftrips avatar palfrey avatar qdot avatar scj643 avatar shadlock0133 avatar siege-wizard avatar suheugene avatar sullyj3 avatar sutekhvrc avatar systemf0x avatar thatbatluna avatar violetbp avatar voidfield101 avatar vyrcossont avatar yoooi0 avatar z1xus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

buttplug's Issues

RFP - DelayedDeviceMessage message

Name of Outgoing Message, if any: DelayedDeviceMessage

Name of Incoming Message, if any: N/A

Message Fields

  • DelayedDeviceMessage
    • Delay - number: Time in milliseconds to delay the message
    • Messages - array of Buttplug Device Messages: Device message(s)

Message Flow

  • DelayedDeviceMessage Sent
    • OkWithIndex returned on successful scheduling of all messages.
    • Error (with code ButtplugMessageError) returned on Error. Possible errors:
      • Delay <= 0
      • Messages array with no messages
      • Messages array with non-ButtplugDeviceMessage messages

Once Delayed Messages are being run:

  • Returns ErrorWithIndex if any message fails, due to wrong message type, device disconnection, etc...
  • Stops execution of all messages on error.
  • Can be cancelled using CancelIndex

Describe message usage scenario

A message that wraps a list of device messages, and adds a delay field (in milliseconds), allowing the wrapped messages to be executed at a later time.

Original bug here

RFP: PatternPlayback Message

Name of Outgoing Message, if any:

  • PatternPlaybackCmd

Message Fields:

  • PatternPlaybackCmd

    • DeviceIndex: number, Index of device to send command to
    • PatternIndex: number, Index of pattern to send
  • DeviceAdded/DeviceList

    • FeatureCount: number: Number of actuators that can playback patterns
    • ActuatorType: List of strings: Describes the actuator type for the pattern, in order of feature count. Vibrate, Rotate, Linear, Estim, etc...
    • Patterns: List of lists of strings: Names of patterns on device, as related to their actuators
    • StepCount: List of uints: Pattern step levels for each actuator on the device.

Describe message usage scenario

Provides a way for users to run patterns built into their hardware. Depending on hardware protocols and capabilities, should also be able to read pattern names/indexes off of toys that allow storage/uploading.

Additional context

Example message:

[
  {
    "DeviceAdded": {
      "Id": 0,
      "DeviceName": "Lovense Hush v48",
      "DeviceIndex": 0,
      "DeviceMessages": {
        "SingleMotorVibrateCmd": {},
        "VibrateCmd": { "FeatureCount": 2 },
        "PatternPlaybackCmd": { "FeatureCount": 2, "ActuatorType": ["Vibrate", "Vibrate"], "StepCount": [1, 1],  "Patterns": [["Wave", "Pulse", "Sawtooth"], ["Wave", "Pulse", "Sawtooth"]]},
        "StopDeviceCmd": {}
      }
    }
  }
]
[{
	"PatternPlaybackCmd": {
		"Id": 0,
		"DeviceIndex": 0,
		"Patterns": [{
			"Index": 0,
			"Pattern": "Wave",
			"Strength": 1
		}]
	}
}]

Devices do not disconnect properly if bluetooth radio removed/turned off while device in use

Platform: Windows 10 15063, Buttplug v0.0.1.478

STR:

  1. Turn on Bluetooth, have bluetooth device ready to connect
  2. Start Application with Device Tab
  3. Hit start scanning, wait for device to connect
  4. Hit stop scanning
  5. Remove bluetooth dongle

Expected:

Bluetooth device registers as disconnected

Actual:

No change. Device is still considered connected, and if it is in use, the server will still try to send packets to it.

All pub calls to Client should process events from inner loop

Feature Description

All pub calls to the client after connect (so not connect itself) should:

  • See if there's any events on the inner loop channel, and process them if so.
  • Check connection is active.

Needs to happen in this order so if we get a server disconnected event, we can error out on the second clause. This will also possibly take care of updating devices as needed for #24.

Set up CI on Azure Pipelines

Feature Description

Make sure the library builds across mac/linux/windows. Possibly automate via release tagging at some point, once that's figured out.

Figure out serialization system

We were using msgpack in buttplug-py, but with the serde library in rust, serializing messages may be pretty flexible on format. Need to figure out how this is going to look.

Disconnected devices don't throw useful errors when sending commands via ClientDevice

Describe the bug
If a device has disconnected, but its struct instance still exists, sending commands through this struct instance doesn't relay useful information.

STR:

  1. Connected device is turned off.
  2. DeviceRemoved event received
  3. Send message to device.

Expected behavior

Some sort of "Device disconnected" error up front.

Actual behavior

Things fail, but it requires going all the way to the server to figure this out.

Move from multiple libraries to features

Feature Description

Since we can use features with Rust/Cargo, this may save us from having to build and maintain a ton of different libraries like we do for nuget/npm.

If we bounce off a Kiiroo Onyx 2.1 connection, somehow warn the user about Whitelisting

The Onyx 2.1 (and I'm guessing future Kiiroo devices) will only connect to one host, and any other host they try to connect to will connect, then disconnect instantly afterward. Fixing this requires holding the power button down for 15 seconds. If we bounce off devices like this, we should somehow warn the users they'll need to do this.

What that will look like from both the library and UI sides is a good question.

Move networking/event core to Tokio

Right now, we're living on top of a bunch of mio 0.5/ws-rs/mpsc::channels smooshed together into something that vaguely works. It'd be nice to unify this into using Tokio and composing the services we need.

Separate client library and internal client loop, make client Clonable

Currently, the client carries its own event loop, which means we have to move it off to run that and can no longer access it. This makes life difficult for trying to orchestrate client tasks (scanning, disconnecting, events, etc).

  • When a client is first created (Client::new), set up a channel between the client API struct and the client loop, and hand back a tuple of the usable client API, and the client event loop.
  • Both the client and any device struct should be clonable, but there should only be one event loop per client/devices.
  • Developers are free to create multiple clients with multiple loops if they want, but it's up to them to keep track of which of their API structs tie to which client.

Make Buttplug Error Codes more helpful

Back in #20 we at least implemented error codes. However, there's only 5 codes at the moment, and they're not super useful for helping developers figure out whether they can recover or not. The system can't really error in THAT may ways (like, 10s, maybe), so we should try to enumerate as many errors as we can to see if that's feasible.

Make connect failure actually fail

Feature Description

Failures on ButtplugClient::connect just hang right now, because there's no way to communicate the failure back to the client through the connector. This should most likely run wait_for_event internally to wait for some sort of return from the event loop.

Throttle commands to BLE devices

When interpolating from Launch (linear) movement to Vibration, ScriptPlayer and Syncydink can generate commands at a rate that is faster than can be processed by BLE. Since C# uses WriteNoResponse, this means we can queue up commands, sometimes resulting in a multi-second/minute lag in device response. We should figure out a way to set maximum command frequency for devices so we only choose the latest command, versus trying to replay everything.

RFP - Received and Cancel Message

Name of Outgoing Message, if any: Cancel

Name of Incoming Message, if any: Received

Message Fields:

  • Cancel
    • Id: number - Message Id
    • CancelId: number - Message Id of the long-running message we want to cancel
  • Received
    • Id: number - Message Id

Message Flow:

  • [Long Running Message sent by client]
  • Received sent back by server, to acknowledge the task is now running, but is not finished.
  • If task finishes, Ok is sent by server later.
  • If client sends Cancel with the original message Id, task is canceled, Ok reply is sent to Cancel.

Describe message usage scenario

Some messages can have extremely long running times. For instance, we can have a delayed message that might run minutes in the future. Since each message already has a unique ID or task object (Task in C#, promise in ts/js, etc), we can treat these IDs as task identifiers. A Received message lets us know the server got the message, and a Cancel message would allow us to cancel subscriptions and other long or possibly infinite running tasks.

Additional context

Note that this is PROTOCOL level. We can still have client level APIs that make subscriptions look like simple subscriptions, so this doesn't need that level of ergonomics. Even for things like subscriptions, we can keep long running tasks to let us know everything that's in flight, versus sending subscribe/unsubscribe and being expected to explicitly hold that new state ourselves.

Make ButtplugClientDevice just hold a client

Feature Description

Since #8 landed, clients are now cloneable. This means that ButtplugClientDevice objects can just hold Clients, instead of having to establish their own channels in the inner loop.

Also, using pub(crate) or other pub modifiers, if devices have their own wait_for_event function, this can spin the client loop to get event updates, as well as subscribed device updates.

RFP: Clarification on Generic Subcommand Index Handling

We don't currently have any rules about how we handle repeated subindexes in Generic Commands.

For instance, with VibrateCmd, say someone creates the following packet:

{
"VibrateCmd":
{
"Speeds": [
{ "Index": 0, "Speed": 0.5 },
{ "Index": 0, "Speed": 0.75 }
]
},
"Id": 10,
"DeviceIndex": 0
}

Our implementations in both C# and JS would currently send 2 commands to the device back to back, which is silly. We should specify whether either the last value for each index should be chosen, or whether we should error out on this case.

I don't have strong feelings either direction. Erroring out would require actual library logic though, I don't believe we'd easily be able to catch that via the schema.

Move examples to their own project

Feature Description

Examples live in the Buttplug project right now, but may use other device managers, connectors, etc... They should live in their own top level project.

Add more logging during configuration loads

Loading the device configuration files is probably one of the most important operations we do right now, but it's also fairly opaque because we don't log anything out it. Putting out verbose logs on configuration loads will help with debugging in the future.

Implement paired device checking for BLE devices

Paired BLE devices have problems with UWP Bluetooth GATT commands. There's a solution in FredTungsten/ScriptPlayer#79. Bringing over @raser1's codeblock:

static public bool IsLaunchPaired()
{
	var scope = new ManagementScope(@"\\" + Environment.MachineName + @"\root\CIMV2");
	var sq = new SelectQuery("SELECT Name FROM Win32_PnPEntity WHERE Name='Launch'");
	var searcher = new ManagementObjectSearcher(scope, sq);
	var moc = searcher.Get();

	foreach (ManagementObject mo in moc)
	{
		object propName = mo.Properties["Name"].Value;			
		Debug.WriteLine($"{propName} is paired!");
		return true;
	}
	
	return false;
}

Websocket can't disconnect

Describe the bug
Can't disconnect from a websocket. Calling disconnect currently will just return None with no action, and due to the design of the RemoteConnectorHelper, there's no way to call close at all.

Fix Mysteryvibe name parsing

Right now we just convert any Mysteryvibe name to "Mysteryvibe Crescendo". Now that the Tenuto works, this is no longer true. Mysteryvibe names are of the format "MV [name]", which may be followed by spaces, so until we have pretty names in the device config file, we should:

  • Chop the MV off the front
  • Trim the leftover string on both sides
  • Attach the remaining string to "Mysteryvibe"

Allow users to rename devices, based on their address

Hi I wanted to ask if there is anyway to identify a Device via the C# api other than by name.

For Example if I connect 2 Lovense Hush is there any way to distinguish those?

Preferably stable even through Client and/or Server Restarts?

Background:

I would like to be able to save currently selected devices and restore the ones that get found the next time my aplication starts and connects to the buttplug server.

Implement ping timer on client

Feature Description

Add ping method to client for outside programs to call. Do not repeat the ping timer idea from C#.

Add LiBo Whale Battery reading functionality

Code is done but we don't have a battery status message yet. cc @bnm12

private int getBatteryLevel()
{
            throw new NotImplementedException("While most of this method is done it still needs to be adjusted/completed - see the comments");
            int batteryLevel = Convert.ToInt32(0x64 /* 100 in hex */);  // Read from Info.Services[1] + Info.Characteristics[uint)LiBoBluetoothInfo.Chrs.Rx] here
            return batteryLevel;                                        // As far as I can tell we currently have no way of reading values
}

Use Vibrate:X instead of VibrateN:X when Lovense gets VibrateCmd with same speeds

This is an optimization bug more than anything, and it's only for the Edge at the moment.

Lovense "Vibrate:X" causes all motors to vibrate, while "Vibrate1X:", "Vibrate2:X", etc cause single motors to vibrate. If we get a VibrateCmd with 2 subcommands, each of which have the same speed, we can just send Vibrate:X over. Saves us a bluetooth exchange.

Add LiBo Whale estim functionality

Code was already done, but we don't have a corresponding message. cc @bnm12

private async Task<ButtplugMessage> HandleShockCmd(ButtplugDeviceMessage aMsg)
        {
            throw new NotImplementedException("While most of this method is done it still needs to be adjusted/completed - see the comments");
            if (!(aMsg is VibrateCmd cmdMsg)) // ShockCmd once it gets implemented
            {
                return BpLogger.LogErrorMsg(aMsg.Id, Error.ErrorClass.ERROR_DEVICE, "Wrong Handler");
            }

            if (cmdMsg.Speeds.Count < 1 || cmdMsg.Speeds.Count > _shockerCount)
            {
                return new Error(
                    $"ShockCmd requires between 1 and {_shockerCount} vectors for this device.", // Fix cmd name once settled
                    Error.ErrorClass.ERROR_DEVICE,
                    cmdMsg.Id);
            }

            var changed = false;
            foreach (var v in cmdMsg.Speeds)
            {
                if (v.Index >= _shockerCount)
                {
                    return new Error(
                        $"Index {v.Index} is out of bounds for VibrateCmd for this device.", // Fix cmd name once settled
                        Error.ErrorClass.ERROR_DEVICE,
                        cmdMsg.Id);
                }

                if (!(Math.Abs(v.Speed - _shockerIntensity[v.Index]) > 0.001))
                {
                    continue;
                }

                changed = true;
                _shockerIntensity[v.Index] = v.Speed;
            }

            if (!changed)
            {
                return new Ok(cmdMsg.Id);
            }

            int speed = 0;

            // If intensity is set to 0 skip this with 0x00 and turn the shocker off
            if (_shockerIntensity[0] > 0)
            {
                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
                speed++; // add 1 to speed to get 0xY1 where Y is speed and 1 is mode 1 (constant shock)
            }

            var data = new byte[] { Convert.ToByte(speed) };

            return await Interface.WriteValue(aMsg.Id,
                Info.Characteristics[(uint)LiBoBluetoothInfo.Chrs.Tx1],
                data);
        }

Use Vibrate:X instead of VibrateN:X when Lovense gets VibrateCmd with same speeds

This is an optimization bug more than anything, and it's only for the Edge at the moment.

Lovense "Vibrate:X" causes all motors to vibrate, while "Vibrate1X:", "Vibrate2:X", etc cause single motors to vibrate. If we get a VibrateCmd with 2 subcommands, each of which have the same speed, we can just send Vibrate:X over. Saves us a bluetooth exchange.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.