UNPKG

timeline-state-resolver

Version:
166 lines • 7.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiOSCMessageDevice = void 0; const _ = require("underscore"); const timeline_state_resolver_types_1 = require("timeline-state-resolver-types"); const device_1 = require("../../service/device"); const deviceConnection_1 = require("./deviceConnection"); /** * This is a generic wrapper for any osc-enabled device. */ class MultiOSCMessageDevice extends device_1.Device { constructor() { super(...arguments); this.actions = {}; this._connections = {}; this._commandQueue = []; } async init(initOptions, testOptions) { this._timeBetweenCommands = initOptions.timeBetweenCommands; for (const connOptions of initOptions.connections) { const connectionId = connOptions.connectionId; const connection = new deviceConnection_1.OSCConnection(); connection.on('error', (err) => this.context.logger.error('Error in MultiOSC connection ' + connectionId, err)); connection.on('debug', (...args) => this.context.logger.debug('from connection ' + connectionId, ...args)); this._connections[connectionId] = connection; if (!connection) { this.context.logger.error('Could not initialise device', new Error('Connection ' + connOptions.connectionId + ' not initialised')); continue; } await connection.connect({ ...connOptions, oscSender: testOptions?.oscSenders?.[connOptions.connectionId] || undefined, }); } // note - we reset here but might still be missing some connections from tcp devices, not worth fixing right now this.context .resetToState(Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}]))) .catch((e) => this.context.logger.warning('Failed to reset state: ' + e)); return true; } async terminate() { for (const connection of Object.values(this._connections)) { connection.dispose(); } } get connected() { return false; } getStatus() { const status = { statusCode: timeline_state_resolver_types_1.StatusCode.GOOD, messages: [], }; for (const conn of Object.values(this._connections)) { if (!conn.connected) { status.statusCode = timeline_state_resolver_types_1.StatusCode.BAD; status.messages.push(`${conn.connectionId} is disconnected`); } } return status; } /** * Transform the timeline state into a device state, which is in this case also * a timeline state. * @param state */ convertTimelineStateToDeviceState(state, mappings) { const addrToOSCMessage = Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}])); const addrToPriority = Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}])); for (const layer of Object.values(state.layers)) { const mapping = mappings[layer.layer]; if (!mapping) continue; const connectionState = addrToOSCMessage[mapping.options.connectionId]; if (!connectionState) continue; if (layer.content.deviceType === timeline_state_resolver_types_1.DeviceType.OSC) { const content = { ...layer.content, connectionId: mapping.options.connectionId, fromTlObject: layer.id, }; if ((connectionState[content.path] && addrToPriority[mapping.options.connectionId][content.path] <= (layer.priority || 0)) || !connectionState[content.path]) { connectionState[content.path] = content; addrToPriority[mapping.options.connectionId][content.path] = layer.priority || 0; } } } return addrToOSCMessage; } /** * Compares the new timeline-state with the old one, and generates commands to account for the difference * @param oldOscSendState The assumed current state * @param newOscSendState The desired state of the device */ diffStates(oldOscSendState, newOscSendState) { // in this oscSend class, let's just cheat: const commands = []; for (const connectionId of Object.keys(this._connections)) { const oldConnectionState = oldOscSendState?.[connectionId]; const newConnectionState = newOscSendState[connectionId] ?? {}; for (const [address, newCommandContent] of Object.entries(newConnectionState)) { if (!newCommandContent) continue; const oldLayer = oldConnectionState?.[address]; if (!oldLayer) { // added! commands.push({ context: `added: ${newCommandContent.fromTlObject}`, timelineObjId: newCommandContent.fromTlObject, command: { // commandName: 'added', ...newCommandContent, connectionId: newCommandContent.connectionId, }, }); } else { // changed? if (!_.isEqual(oldLayer, newCommandContent)) { // changed! commands.push({ context: `changed: ${newCommandContent.fromTlObject}`, timelineObjId: newCommandContent.fromTlObject, command: { // commandName: 'changed', ...newCommandContent, connectionId: newCommandContent.connectionId, }, }); } } } } return commands; } async sendCommand(command) { this.context.logger.debug(command); this._commandQueue.push(command); this._processQueue(); } _processQueue() { if (this._commandQueueTimer) return; const nextCommand = this._commandQueue.shift(); if (!nextCommand) return; try { this._connections[nextCommand.command.connectionId]?.sendOsc({ address: nextCommand.command.path, args: nextCommand.command.values, }); } catch (e) { this.context.commandError(new Error('Command failed: ' + e), { ...nextCommand, command: nextCommand }); } this._commandQueueTimer = setTimeout(() => { this._commandQueueTimer = undefined; this._processQueue(); }, this._timeBetweenCommands || 0); } } exports.MultiOSCMessageDevice = MultiOSCMessageDevice; //# sourceMappingURL=index.js.map