Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@timesplinter/pimple": "^2.0.0",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"buttplug": "^3.2.1",
"buttplug": "^5.0.1",
"class-transformer": "^0.5.1",
"cors": "^2.8.5",
"dotenv": "^17.2.0",
Expand Down
4 changes: 2 additions & 2 deletions src/controller/automation/stopScriptController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export default class StopScriptController implements ControllerInterface
this.scriptRuntime = scriptRuntime;
}

public execute(req: Request, res: Response): void
public async execute(req: Request, res: Response): Promise<void>
{
this.scriptRuntime.stop();
await this.scriptRuntime.stop();

res.sendStatus(200);
}
Expand Down
58 changes: 43 additions & 15 deletions src/device/protocol/buttplugIo/buttplugIoDevice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Exclude, Expose } from 'class-transformer';
import { ActuatorType, ButtplugClientDevice, SensorType } from 'buttplug';
import { ButtplugClientDevice, DeviceOutput, DeviceOutputCommand, InputCommandType, InputType, OutputType } from 'buttplug';
import Device, { ExtractAttributeValue } from '../../device.js';
import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js';
import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js';
Expand All @@ -8,9 +8,9 @@ import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js';
import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js';
import EventEmitter from 'events';

type ButtplugActuatorTypeKey = `${ActuatorType}-${number}`;
type ButtplugSensorTypeKey = `${SensorType}-${number}`;
export type ButtplugIoDeviceAttributeKey = ButtplugActuatorTypeKey | ButtplugSensorTypeKey;
type ButtplugOutputTypeKey = `${OutputType}-${number}`;
type ButtplugInputTypeKey = `${InputType}-${number}`;
export type ButtplugIoDeviceAttributeKey = ButtplugOutputTypeKey | ButtplugInputTypeKey;

export type ButtplugIoDeviceAttributes = Record<
ButtplugIoDeviceAttributeKey,
Expand Down Expand Up @@ -41,9 +41,20 @@ export default class ButtplugIoDevice extends Device<ButtplugIoDeviceAttributes>
}

protected override async doRefresh(): Promise<void> {
for (const sensor of this.buttplugClientDevice.messageAttributes.SensorReadCmd ?? []) {
const value = await this.buttplugClientDevice.sensorRead(sensor.Index, sensor.SensorType);
this.attributes[`${sensor.SensorType}-${sensor.Index}`].value = Int.from(value[0]);
for (const [, feature] of this.buttplugClientDevice.features) {
for (const [, input] of feature.inputs) {
if (input.type === InputType.Unknown) {
continue;
}
const attrKey = `${input.type}-${feature.index}` as ButtplugIoDeviceAttributeKey;
if (this.attributes[attrKey] === undefined) {
continue;
}
const response = await feature.runInput(input.type, InputCommandType.Read);
if (response?.value !== undefined) {
this.attributes[attrKey].value = Int.from(response.value);
}
}
}
}

Expand Down Expand Up @@ -77,21 +88,38 @@ export default class ButtplugIoDevice extends Device<ButtplugIoDeviceAttributes>
throw new Error(`Unsupported attribute type '${attribute.constructor.name}' for buttplug.io`);
}

const [actuatorType, index] = attributeName.split('-');
const dashIndex = attributeName.lastIndexOf('-');
const outputType = attributeName.slice(0, dashIndex) as OutputType;
const index = parseInt(attributeName.slice(dashIndex + 1), 10);

await this.send(actuatorType as ActuatorType, parseInt(index, 10), valueToSend);
await this.send(outputType, index, valueToSend);

this.attributes[`${attributeName}`].value = value;

return value;
}

protected async send(command: ActuatorType, index: number, value: number): Promise<void> {
return await this.buttplugClientDevice.scalar({
'ActuatorType': command,
'Scalar': value,
'Index': index
});
protected async send(command: OutputType, index: number, value: number): Promise<void> {
const feature = this.buttplugClientDevice.features.get(index);
if (feature === undefined) {
throw new Error(`Feature index ${index} not found`);
}
await feature.runOutput(ButtplugIoDevice.createOutputCommand(command, value));
}

private static createOutputCommand(type: OutputType, value: number): DeviceOutputCommand {
switch (type) {
case OutputType.Vibrate: return DeviceOutput.Vibrate.value(value);
case OutputType.Rotate: return DeviceOutput.Rotate.value(value);
case OutputType.Oscillate: return DeviceOutput.Oscillate.value(value);
case OutputType.Constrict: return DeviceOutput.Constrict.value(value);
case OutputType.Inflate: return DeviceOutput.Inflate.value(value);
case OutputType.Temperature: return DeviceOutput.Temperature.value(value);
case OutputType.Led: return DeviceOutput.Led.value(value);
case OutputType.Spray: return DeviceOutput.Spray.value(value);
case OutputType.Position: return DeviceOutput.Position.value(value);
default: throw new Error(`Unsupported output type: ${type}`);
}
}

public get getButtplugClientDevice(): ButtplugClientDevice
Expand Down
95 changes: 47 additions & 48 deletions src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import UuidFactory from '../../../factory/uuidFactory.js';
import Settings from '../../../settings/settings.js';
import { ButtplugClientDevice } from 'buttplug';
import { ButtplugClientDevice, ButtplugClientDeviceFeatureValueRange, InputType, OutputType } from 'buttplug';
import ButtplugIoDevice, { ButtplugIoDeviceAttributeKey, ButtplugIoDeviceAttributes } from './buttplugIoDevice.js';
import KnownDevice from '../../../settings/knownDevice.js';
import Logger from '../../../logging/Logger.js';
import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js';
import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js';
import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js';
import DateFactory from '../../../factory/dateFactory.js';
import { Int } from '../../../util/numbers.js';
import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js';
import EventEmitterFactory from '../../../factory/eventEmitterFactory.js';
import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js';


export default class ButtplugIoDeviceFactory
Expand Down Expand Up @@ -60,59 +59,59 @@ export default class ButtplugIoDeviceFactory
}

private static parseDeviceAttributes(buttplugDevice: ButtplugClientDevice): ButtplugIoDeviceAttributes {
const attributes = {} as ButtplugIoDeviceAttributes;

for (const item of buttplugDevice.messageAttributes.ScalarCmd ?? []) {
const attrName = `${item.ActuatorType}-${item.Index}` as ButtplugIoDeviceAttributeKey;

if (item.StepCount > 2) {
attributes[attrName] = IntRangeDeviceAttribute.createInitialized(
attrName,
item.FeatureDescriptor,
DeviceAttributeModifier.writeOnly,
undefined,
Int.ZERO,
Int.from(item.StepCount),
Int.from(1),
Int.ZERO
);
} else {
attributes[attrName] = BoolDeviceAttribute.createInitialized(
attrName, item.FeatureDescriptor, DeviceAttributeModifier.writeOnly, false
);
const attributes: ButtplugIoDeviceAttributes = {};

for (const [, feature] of buttplugDevice.features) {
for (const [outputType, output] of feature.outputs) {
if (outputType === OutputType.Unknown) {
continue;
}

const attrName: ButtplugIoDeviceAttributeKey = `${outputType}-${feature.index}`;

attributes[attrName] = this.createAttribute(attrName, feature.featureDescriptor, output.valueRange, DeviceAttributeModifier.writeOnly);
}
}

for (const item of buttplugDevice.messageAttributes.SensorReadCmd ?? []) {
const attrName = `${item.SensorType}-${item.Index}` as ButtplugIoDeviceAttributeKey;

// A range is defined by two numbers, if there are more or less, let's fallback
// to a normal integer attribute. Not that dramatic for a sensor after all.
if (item.StepRange.length === 2) {
attributes[attrName] = IntRangeDeviceAttribute.createInitialized(
`${item.SensorType}-${item.Index}`,
item.FeatureDescriptor,
DeviceAttributeModifier.readOnly,
undefined,
Int.from(item.StepRange[0]),
Int.from(item.StepRange[1]),
Int.from(1),
Int.ZERO
);
} else {
attributes[attrName] = IntDeviceAttribute.createInitialized(
`${item.SensorType}-${item.Index}`,
item.FeatureDescriptor,
DeviceAttributeModifier.readOnly,
undefined,
Int.ZERO
);
for (const [, input] of feature.inputs) {
if (input.type === InputType.Unknown) {
continue;
}

const attrName: ButtplugIoDeviceAttributeKey = `${input.type}-${feature.index}`;

attributes[attrName] = this.createAttribute(attrName, feature.featureDescriptor, input.valueRange, DeviceAttributeModifier.readOnly);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return attributes;
}

private static createAttribute(
attrName: ButtplugIoDeviceAttributeKey,
featureDescriptor: string,
valueRange: ButtplugClientDeviceFeatureValueRange,
attributeModifier: DeviceAttributeModifier
): IntRangeDeviceAttribute|BoolDeviceAttribute {
const [valueRangeMin, valueRangeMax] = valueRange;

if (valueRangeMin === 0 && valueRangeMax === 1) {
return BoolDeviceAttribute.createInitialized(
attrName, featureDescriptor, DeviceAttributeModifier.writeOnly, false
);
}

return IntRangeDeviceAttribute.createInitialized(
attrName,
featureDescriptor || undefined,
attributeModifier,
undefined,
Int.from(valueRangeMin),
Int.from(valueRangeMax),
Int.from(1),
Int.ZERO
);
}

private createKnownDevice(buttplugDevice: ButtplugClientDevice, provider: string, useDeviceNameAsId: boolean): KnownDevice {
// Since we don't get a unique identifier for the Bluetooth device from Intiface,
// we need to use the index assigned to the device by Intiface. It's the best we have.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import DeviceProvider from '../../provider/deviceProvider.js';
import ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js';
import Logger from '../../../logging/Logger.js';
import { asyncHandler, setImmediateInterval } from '../../../util/async.js';
import SlvCtrlPlusButtplugWebsocketClientConnector from './slvCtrlPlusButtplugWebsocketClientConnector.js';
import DeviceManager from '../../deviceManager.js';
import { logError } from '../../../util/error.js';

Expand Down Expand Up @@ -43,8 +42,8 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider<Bu

const url = `ws://${this.websocketAddress}/buttplug`;

this.buttplugConnector = new SlvCtrlPlusButtplugWebsocketClientConnector(url);
this.buttplugClient = new ButtplugClient('SlvCtrlPlus');
this.buttplugConnector = new ButtplugNodeWebsocketClientConnector(url);
this.buttplugClient = new ButtplugClient(`SlvCtrlPlus (pid: ${process.pid})`);
this.buttplugClient.on('disconnect', asyncHandler(
this.handleLostConnection.bind(this, url),
(e: unknown) => logError(this.logger, `Error in disconnect handler`, e)
Expand Down

This file was deleted.

Loading
Loading