feat: upgrade buttplug package to protocol v4 and WASM v10
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
Upgrade the buttplug TypeScript client from class-based v3 protocol to interface-based v4 protocol, and the Rust/WASM server from the monolithic buttplug 9.0.9 crate to the split buttplug_core/buttplug_server/ buttplug_server_device_config 10.0.0 crates. TypeScript changes: - Messages are now plain interfaces with msgId()/setMsgId() helpers - ActuatorType → OutputType, SensorType → InputType - ScalarCmd/RotateCmd/LinearCmd → OutputCmd, SensorReadCmd → InputCmd - Client.ts → ButtplugClient.ts, new DeviceCommand/DeviceFeature files - Devices getter returns Map instead of array - Removed class-transformer/reflect-metadata dependencies Rust/WASM changes: - Split imports across buttplug_core, buttplug_server, buttplug_server_device_config - Removed ButtplugServerDowngradeWrapper (use ButtplugServer directly) - Replaced ButtplugFuture/ButtplugFutureStateShared with tokio::sync::oneshot - Updated Hardware::new for new 6-arg signature - Uses git fork (valknarthing/buttplug) to fix missing wasm deps in buttplug_core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,485 +7,203 @@
|
||||
*/
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
import { instanceToPlain, Type } from "class-transformer";
|
||||
import "reflect-metadata";
|
||||
import { ButtplugMessageError } from './Exceptions';
|
||||
|
||||
export const SYSTEM_MESSAGE_ID = 0;
|
||||
export const DEFAULT_MESSAGE_ID = 1;
|
||||
export const MAX_ID = 4294967295;
|
||||
export const MESSAGE_SPEC_VERSION = 3;
|
||||
export const MESSAGE_SPEC_VERSION_MAJOR = 4;
|
||||
export const MESSAGE_SPEC_VERSION_MINOR = 0;
|
||||
|
||||
export class MessageAttributes {
|
||||
public ScalarCmd?: Array<GenericDeviceMessageAttributes>;
|
||||
public RotateCmd?: Array<GenericDeviceMessageAttributes>;
|
||||
public LinearCmd?: Array<GenericDeviceMessageAttributes>;
|
||||
public RawReadCmd?: RawDeviceMessageAttributes;
|
||||
public RawWriteCmd?: RawDeviceMessageAttributes;
|
||||
public RawSubscribeCmd?: RawDeviceMessageAttributes;
|
||||
public SensorReadCmd?: Array<SensorDeviceMessageAttributes>;
|
||||
public SensorSubscribeCmd?: Array<SensorDeviceMessageAttributes>;
|
||||
public StopDeviceCmd: {};
|
||||
|
||||
constructor(data: Partial<MessageAttributes>) {
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
public update() {
|
||||
this.ScalarCmd?.forEach((x, i) => (x.Index = i));
|
||||
this.RotateCmd?.forEach((x, i) => (x.Index = i));
|
||||
this.LinearCmd?.forEach((x, i) => (x.Index = i));
|
||||
this.SensorReadCmd?.forEach((x, i) => (x.Index = i));
|
||||
this.SensorSubscribeCmd?.forEach((x, i) => (x.Index = i));
|
||||
}
|
||||
// Base message interfaces
|
||||
export interface ButtplugMessage {
|
||||
Ok?: Ok;
|
||||
Ping?: Ping;
|
||||
Error?: Error;
|
||||
RequestServerInfo?: RequestServerInfo;
|
||||
ServerInfo?: ServerInfo;
|
||||
RequestDeviceList?: RequestDeviceList;
|
||||
StartScanning?: StartScanning;
|
||||
StopScanning?: StopScanning;
|
||||
ScanningFinished?: ScanningFinished;
|
||||
StopCmd?: StopCmd;
|
||||
InputCmd?: InputCmd;
|
||||
InputReading?: InputReading;
|
||||
OutputCmd?: OutputCmd;
|
||||
DeviceList?: DeviceList;
|
||||
}
|
||||
|
||||
export enum ActuatorType {
|
||||
Unknown = "Unknown",
|
||||
Vibrate = "Vibrate",
|
||||
Rotate = "Rotate",
|
||||
Oscillate = "Oscillate",
|
||||
Constrict = "Constrict",
|
||||
Inflate = "Inflate",
|
||||
Position = "Position",
|
||||
export function msgId(msg: ButtplugMessage): number {
|
||||
for (let [_, entry] of Object.entries(msg)) {
|
||||
if (entry != undefined) {
|
||||
return entry.Id;
|
||||
}
|
||||
}
|
||||
throw new ButtplugMessageError(`Message ${msg} does not have an ID.`);
|
||||
}
|
||||
|
||||
export enum SensorType {
|
||||
Unknown = "Unknown",
|
||||
Battery = "Battery",
|
||||
RSSI = "RSSI",
|
||||
Button = "Button",
|
||||
Pressure = "Pressure",
|
||||
// Temperature,
|
||||
// Accelerometer,
|
||||
// Gyro,
|
||||
export function setMsgId(msg: ButtplugMessage, id: number) {
|
||||
for (let [_, entry] of Object.entries(msg)) {
|
||||
if (entry != undefined) {
|
||||
entry.Id = id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ButtplugMessageError(`Message ${msg} does not have an ID.`);
|
||||
}
|
||||
|
||||
export class GenericDeviceMessageAttributes {
|
||||
public FeatureDescriptor: string;
|
||||
public ActuatorType: ActuatorType;
|
||||
public StepCount: number;
|
||||
public Index = 0;
|
||||
constructor(data: Partial<GenericDeviceMessageAttributes>) {
|
||||
Object.assign(this, data);
|
||||
}
|
||||
export interface Ok {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class RawDeviceMessageAttributes {
|
||||
constructor(public Endpoints: Array<string>) {}
|
||||
}
|
||||
|
||||
export class SensorDeviceMessageAttributes {
|
||||
public FeatureDescriptor: string;
|
||||
public SensorType: SensorType;
|
||||
public StepRange: Array<number>;
|
||||
public Index = 0;
|
||||
constructor(data: Partial<GenericDeviceMessageAttributes>) {
|
||||
Object.assign(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ButtplugMessage {
|
||||
constructor(public Id: number) {}
|
||||
|
||||
// tslint:disable-next-line:ban-types
|
||||
public get Type(): Function {
|
||||
return this.constructor;
|
||||
}
|
||||
|
||||
public toJSON(): string {
|
||||
return JSON.stringify(this.toProtocolFormat());
|
||||
}
|
||||
|
||||
public toProtocolFormat(): object {
|
||||
const jsonObj = {};
|
||||
jsonObj[(this.constructor as unknown as { Name: string }).Name] =
|
||||
instanceToPlain(this);
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
public update() {}
|
||||
}
|
||||
|
||||
export abstract class ButtplugDeviceMessage extends ButtplugMessage {
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Id: number,
|
||||
) {
|
||||
super(Id);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ButtplugSystemMessage extends ButtplugMessage {
|
||||
constructor(public Id: number = SYSTEM_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class Ok extends ButtplugSystemMessage {
|
||||
static Name = "Ok";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class Ping extends ButtplugMessage {
|
||||
static Name = "Ping";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
export interface Ping {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export enum ErrorClass {
|
||||
ERROR_UNKNOWN,
|
||||
ERROR_INIT,
|
||||
ERROR_PING,
|
||||
ERROR_MSG,
|
||||
ERROR_DEVICE,
|
||||
ERROR_UNKNOWN,
|
||||
ERROR_INIT,
|
||||
ERROR_PING,
|
||||
ERROR_MSG,
|
||||
ERROR_DEVICE,
|
||||
}
|
||||
|
||||
export class Error extends ButtplugMessage {
|
||||
static Name = "Error";
|
||||
|
||||
constructor(
|
||||
public ErrorMessage: string,
|
||||
public ErrorCode: ErrorClass = ErrorClass.ERROR_UNKNOWN,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(Id);
|
||||
}
|
||||
|
||||
get Schemversion() {
|
||||
return 0;
|
||||
}
|
||||
export interface Error {
|
||||
ErrorMessage: string;
|
||||
ErrorCode: ErrorClass;
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class DeviceInfo {
|
||||
public DeviceIndex: number;
|
||||
public DeviceName: string;
|
||||
@Type(() => MessageAttributes)
|
||||
public DeviceMessages: MessageAttributes;
|
||||
public DeviceDisplayName?: string;
|
||||
public DeviceMessageTimingGap?: number;
|
||||
|
||||
constructor(data: Partial<DeviceInfo>) {
|
||||
Object.assign(this, data);
|
||||
}
|
||||
export interface RequestDeviceList {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class DeviceList extends ButtplugMessage {
|
||||
static Name = "DeviceList";
|
||||
|
||||
@Type(() => DeviceInfo)
|
||||
public Devices: DeviceInfo[];
|
||||
public Id: number;
|
||||
|
||||
constructor(devices: DeviceInfo[], id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(id);
|
||||
this.Devices = devices;
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
public update() {
|
||||
for (const device of this.Devices) {
|
||||
device.DeviceMessages.update();
|
||||
}
|
||||
}
|
||||
export interface StartScanning {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class DeviceAdded extends ButtplugSystemMessage {
|
||||
static Name = "DeviceAdded";
|
||||
|
||||
public DeviceIndex: number;
|
||||
public DeviceName: string;
|
||||
@Type(() => MessageAttributes)
|
||||
public DeviceMessages: MessageAttributes;
|
||||
public DeviceDisplayName?: string;
|
||||
public DeviceMessageTimingGap?: number;
|
||||
|
||||
constructor(data: Partial<DeviceAdded>) {
|
||||
super();
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
public update() {
|
||||
this.DeviceMessages.update();
|
||||
}
|
||||
export interface StopScanning {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class DeviceRemoved extends ButtplugSystemMessage {
|
||||
static Name = "DeviceRemoved";
|
||||
|
||||
constructor(public DeviceIndex: number) {
|
||||
super();
|
||||
}
|
||||
export interface StopAllDevices {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class RequestDeviceList extends ButtplugMessage {
|
||||
static Name = "RequestDeviceList";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
export interface ScanningFinished {
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class StartScanning extends ButtplugMessage {
|
||||
static Name = "StartScanning";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
export interface RequestServerInfo {
|
||||
ClientName: string;
|
||||
ProtocolVersionMajor: number;
|
||||
ProtocolVersionMinor: number;
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class StopScanning extends ButtplugMessage {
|
||||
static Name = "StopScanning";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
export interface ServerInfo {
|
||||
MaxPingTime: number;
|
||||
ServerName: string;
|
||||
ProtocolVersionMajor: number;
|
||||
ProtocolVersionMinor: number;
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class ScanningFinished extends ButtplugSystemMessage {
|
||||
static Name = "ScanningFinished";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
export interface DeviceFeature {
|
||||
FeatureDescriptor: string;
|
||||
Output: { [key: string]: DeviceFeatureOutput };
|
||||
Input: { [key: string]: DeviceFeatureInput };
|
||||
FeatureIndex: number;
|
||||
}
|
||||
|
||||
export class RequestServerInfo extends ButtplugMessage {
|
||||
static Name = "RequestServerInfo";
|
||||
|
||||
constructor(
|
||||
public ClientName: string,
|
||||
public MessageVersion: number = 0,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(Id);
|
||||
}
|
||||
export interface DeviceInfo {
|
||||
DeviceIndex: number;
|
||||
DeviceName: string;
|
||||
DeviceFeatures: { [key: number]: DeviceFeature };
|
||||
DeviceDisplayName?: string;
|
||||
DeviceMessageTimingGap?: number;
|
||||
}
|
||||
|
||||
export class ServerInfo extends ButtplugSystemMessage {
|
||||
static Name = "ServerInfo";
|
||||
|
||||
constructor(
|
||||
public MessageVersion: number,
|
||||
public MaxPingTime: number,
|
||||
public ServerName: string,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
export interface DeviceList {
|
||||
Devices: { [key: number]: DeviceInfo };
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class StopDeviceCmd extends ButtplugDeviceMessage {
|
||||
static Name = "StopDeviceCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number = -1,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
export enum OutputType {
|
||||
Unknown = 'Unknown',
|
||||
Vibrate = 'Vibrate',
|
||||
Rotate = 'Rotate',
|
||||
Oscillate = 'Oscillate',
|
||||
Constrict = 'Constrict',
|
||||
Inflate = 'Inflate',
|
||||
Position = 'Position',
|
||||
HwPositionWithDuration = 'HwPositionWithDuration',
|
||||
Temperature = 'Temperature',
|
||||
Spray = 'Spray',
|
||||
Led = 'Led',
|
||||
}
|
||||
|
||||
export class StopAllDevices extends ButtplugMessage {
|
||||
static Name = "StopAllDevices";
|
||||
|
||||
constructor(public Id: number = DEFAULT_MESSAGE_ID) {
|
||||
super(Id);
|
||||
}
|
||||
export enum InputType {
|
||||
Unknown = 'Unknown',
|
||||
Battery = 'Battery',
|
||||
RSSI = 'RSSI',
|
||||
Button = 'Button',
|
||||
Pressure = 'Pressure',
|
||||
// Temperature,
|
||||
// Accelerometer,
|
||||
// Gyro,
|
||||
}
|
||||
|
||||
export class GenericMessageSubcommand {
|
||||
protected constructor(public Index: number) {}
|
||||
export enum InputCommandType {
|
||||
Read = 'Read',
|
||||
Subscribe = 'Subscribe',
|
||||
Unsubscribe = 'Unsubscribe',
|
||||
}
|
||||
|
||||
export class ScalarSubcommand extends GenericMessageSubcommand {
|
||||
constructor(
|
||||
Index: number,
|
||||
public Scalar: number,
|
||||
public ActuatorType: ActuatorType,
|
||||
) {
|
||||
super(Index);
|
||||
}
|
||||
export interface DeviceFeatureInput {
|
||||
Value: number[];
|
||||
Command: InputCommandType[];
|
||||
}
|
||||
|
||||
export class ScalarCmd extends ButtplugDeviceMessage {
|
||||
static Name = "ScalarCmd";
|
||||
|
||||
constructor(
|
||||
public Scalars: ScalarSubcommand[],
|
||||
public DeviceIndex: number = -1,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
export interface DeviceFeatureOutput {
|
||||
Value: number;
|
||||
Duration?: number;
|
||||
}
|
||||
|
||||
export class RotateSubcommand extends GenericMessageSubcommand {
|
||||
constructor(
|
||||
Index: number,
|
||||
public Speed: number,
|
||||
public Clockwise: boolean,
|
||||
) {
|
||||
super(Index);
|
||||
}
|
||||
export interface OutputCmd {
|
||||
DeviceIndex: number;
|
||||
FeatureIndex: number;
|
||||
Command: { [key: string]: DeviceFeatureOutput };
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class RotateCmd extends ButtplugDeviceMessage {
|
||||
static Name = "RotateCmd";
|
||||
// Device Input Commands
|
||||
|
||||
public static Create(
|
||||
deviceIndex: number,
|
||||
commands: [number, boolean][],
|
||||
): RotateCmd {
|
||||
const cmdList: RotateSubcommand[] = new Array<RotateSubcommand>();
|
||||
|
||||
let i = 0;
|
||||
for (const [speed, clockwise] of commands) {
|
||||
cmdList.push(new RotateSubcommand(i, speed, clockwise));
|
||||
++i;
|
||||
}
|
||||
|
||||
return new RotateCmd(cmdList, deviceIndex);
|
||||
}
|
||||
constructor(
|
||||
public Rotations: RotateSubcommand[],
|
||||
public DeviceIndex: number = -1,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
export interface InputCmd {
|
||||
DeviceIndex: number;
|
||||
FeatureIndex: number;
|
||||
Type: InputType;
|
||||
Command: InputCommandType;
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class VectorSubcommand extends GenericMessageSubcommand {
|
||||
constructor(
|
||||
Index: number,
|
||||
public Position: number,
|
||||
public Duration: number,
|
||||
) {
|
||||
super(Index);
|
||||
}
|
||||
export interface InputValue {
|
||||
Value: number;
|
||||
}
|
||||
|
||||
export class LinearCmd extends ButtplugDeviceMessage {
|
||||
static Name = "LinearCmd";
|
||||
|
||||
public static Create(
|
||||
deviceIndex: number,
|
||||
commands: [number, number][],
|
||||
): LinearCmd {
|
||||
const cmdList: VectorSubcommand[] = new Array<VectorSubcommand>();
|
||||
|
||||
let i = 0;
|
||||
for (const cmd of commands) {
|
||||
cmdList.push(new VectorSubcommand(i, cmd[0], cmd[1]));
|
||||
++i;
|
||||
}
|
||||
|
||||
return new LinearCmd(cmdList, deviceIndex);
|
||||
}
|
||||
constructor(
|
||||
public Vectors: VectorSubcommand[],
|
||||
public DeviceIndex: number = -1,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
export interface InputReading {
|
||||
DeviceIndex: number;
|
||||
FeatureIndex: number;
|
||||
Reading: { [key: string]: InputValue };
|
||||
Id: number | undefined;
|
||||
}
|
||||
|
||||
export class SensorReadCmd extends ButtplugDeviceMessage {
|
||||
static Name = "SensorReadCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public SensorIndex: number,
|
||||
public SensorType: SensorType,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class SensorReading extends ButtplugDeviceMessage {
|
||||
static Name = "SensorReading";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public SensorIndex: number,
|
||||
public SensorType: SensorType,
|
||||
public Data: number[],
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class RawReadCmd extends ButtplugDeviceMessage {
|
||||
static Name = "RawReadCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Endpoint: string,
|
||||
public ExpectedLength: number,
|
||||
public Timeout: number,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class RawWriteCmd extends ButtplugDeviceMessage {
|
||||
static Name = "RawWriteCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Endpoint: string,
|
||||
public Data: Uint8Array,
|
||||
public WriteWithResponse: boolean,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class RawSubscribeCmd extends ButtplugDeviceMessage {
|
||||
static Name = "RawSubscribeCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Endpoint: string,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class RawUnsubscribeCmd extends ButtplugDeviceMessage {
|
||||
static Name = "RawUnsubscribeCmd";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Endpoint: string,
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
}
|
||||
|
||||
export class RawReading extends ButtplugDeviceMessage {
|
||||
static Name = "RawReading";
|
||||
|
||||
constructor(
|
||||
public DeviceIndex: number,
|
||||
public Endpoint: string,
|
||||
public Data: number[],
|
||||
public Id: number = DEFAULT_MESSAGE_ID,
|
||||
) {
|
||||
super(DeviceIndex, Id);
|
||||
}
|
||||
export interface StopCmd {
|
||||
Id: number | undefined;
|
||||
DeviceIndex: number | undefined;
|
||||
FeatureIndex: number | undefined;
|
||||
Inputs: boolean | undefined;
|
||||
Outputs: boolean | undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user