import { ButtplugDeviceError, ButtplugError, ButtplugMessageError } from "../core/Exceptions"; import * as Messages from "../core/Messages"; import { DeviceOutputCommand } from "./ButtplugClientDeviceCommand"; export class ButtplugClientDeviceFeature { constructor( private _deviceIndex: number, private _deviceName: string, private _feature: Messages.DeviceFeature, private _sendClosure: ( msg: Messages.ButtplugMessage ) => Promise) { } protected send = async (msg: Messages.ButtplugMessage): Promise => { return await this._sendClosure(msg); } protected sendMsgExpectOk = async ( msg: Messages.ButtplugMessage ): Promise => { const response = await this.send(msg); if (response.Ok !== undefined) { return; } else if (response.Error !== undefined) { throw ButtplugError.FromError(response as Messages.Error); } else { throw new ButtplugMessageError("Expected Ok or Error, and didn't get either!"); } }; protected isOutputValid(type: Messages.OutputType) { if (this._feature.Output !== undefined && !this._feature.Output.hasOwnProperty(type)) { throw new ButtplugDeviceError(`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`); } } protected isInputValid(type: Messages.InputType) { if (this._feature.Input !== undefined && !this._feature.Input.hasOwnProperty(type)) { throw new ButtplugDeviceError(`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`); } } protected async sendOutputCmd(command: DeviceOutputCommand): Promise { // Make sure the requested feature is valid this.isOutputValid(command.outputType); if (command.value === undefined) { throw new ButtplugDeviceError(`${command.outputType} requires value defined`); } let type = command.outputType; let duration: undefined | number = undefined; if (type == Messages.OutputType.HwPositionWithDuration) { if (command.duration === undefined) { throw new ButtplugDeviceError("PositionWithDuration requires duration defined"); } duration = command.duration; } let value: number; let p = command.value; if (p.percent === undefined) { // TODO Check step limits here value = command.value.steps!; } else { value = Math.ceil(this._feature.Output[type]!.Value![1] * p.percent); } let newCommand: Messages.DeviceFeatureOutput = { Value: value, Duration: duration }; let outCommand = {}; outCommand[type.toString()] = newCommand; let cmd: Messages.ButtplugMessage = { OutputCmd: { Id: 1, DeviceIndex: this._deviceIndex, FeatureIndex: this._feature.FeatureIndex, Command: outCommand } }; await this.sendMsgExpectOk(cmd); } public get featureDescriptor(): string { return this._feature.FeatureDescription; } public get featureIndex(): number { return this._feature.FeatureIndex; } public get outputTypes(): Messages.OutputType[] { if (this._feature.Output === undefined) return []; return Object.keys(this._feature.Output) as Messages.OutputType[]; } public get inputTypes(): Messages.InputType[] { if (this._feature.Input === undefined) return []; return Object.keys(this._feature.Input) as Messages.InputType[]; } public outputMaxValue(type: Messages.OutputType): number { if (this._feature.Output === undefined || this._feature.Output[type] === undefined) { return 0; } const val = this._feature.Output[type]!.Value; // Value can arrive as number[] [min, max] from server or as number if (Array.isArray(val)) { return val[val.length - 1]; } return val as number; } public hasOutput(type: Messages.OutputType): boolean { if (this._feature.Output !== undefined) { return this._feature.Output.hasOwnProperty(type.toString()); } return false; } public hasInput(type: Messages.InputType): boolean { if (this._feature.Input !== undefined) { return this._feature.Input.hasOwnProperty(type.toString()); } return false; } public async runOutput(cmd: DeviceOutputCommand): Promise { if (this._feature.Output !== undefined && this._feature.Output.hasOwnProperty(cmd.outputType.toString())) { return this.sendOutputCmd(cmd); } throw new ButtplugDeviceError(`Output type ${cmd.outputType} not supported by feature.`); } public async runInput(inputType: Messages.InputType, inputCommand: Messages.InputCommandType): Promise { // Make sure the requested feature is valid this.isInputValid(inputType); let inputAttributes = this._feature.Input[inputType]; console.log(this._feature.Input); if ((inputCommand === Messages.InputCommandType.Unsubscribe && !inputAttributes.Command.includes(Messages.InputCommandType.Subscribe)) && !inputAttributes.Command.includes(inputCommand)) { throw new ButtplugDeviceError(`${inputType} does not support command ${inputCommand}`); } let cmd: Messages.ButtplugMessage = { InputCmd: { Id: 1, DeviceIndex: this._deviceIndex, FeatureIndex: this._feature.FeatureIndex, Type: inputType, Command: inputCommand, } }; if (inputCommand == Messages.InputCommandType.Read) { const response = await this.send(cmd); if (response.InputReading !== undefined) { return response.InputReading; } else if (response.Error !== undefined) { throw ButtplugError.FromError(response as Messages.Error); } else { throw new ButtplugMessageError("Expected InputReading or Error, and didn't get either!"); } } else { console.log(`Sending subscribe message: ${JSON.stringify(cmd)}`); await this.sendMsgExpectOk(cmd); console.log("Got back ok?"); } } }