From 3eafdc1ab5e0bd7d027d282786d3111ecc5b4ff7 Mon Sep 17 00:00:00 2001 From: Bannsaenger Date: Tue, 1 Jun 2021 20:19:23 +0200 Subject: [PATCH] introduced encoders --- README.md | 3 + io-package.json | 3 + lib/console_layout.json | 4 +- lib/encoder_mapping.json | 218 +++++++++++++++++++++++ lib/midi_mapping.json | 4 +- lib/objects_templates.json | 39 +++-- main.js | 351 +++++++++++++++++++++++++++++++++---- package.json | 2 +- 8 files changed, 569 insertions(+), 55 deletions(-) create mode 100644 lib/encoder_mapping.json diff --git a/README.md b/README.md index ef47a19..9260fb2 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ Communicate with a Behringer X-Touch Control Surface (DAW Controller) ### 0.1.0 * (Bannsaenger) introduced channel and page switching +### 0.2.0 +* (Bannsaenger) introduced encoders + ## License MIT License diff --git a/io-package.json b/io-package.json index 236c734..e52adb3 100644 --- a/io-package.json +++ b/io-package.json @@ -29,6 +29,9 @@ }, "0.1.0": { "en": "introduced channel and page switching" + }, + "0.2.0": { + "en": "introduced encoders" } }, "title": "Behringer X-Touch", diff --git a/lib/console_layout.json b/lib/console_layout.json index 3a0ccd3..fb0b953 100644 --- a/lib/console_layout.json +++ b/lib/console_layout.json @@ -64,11 +64,11 @@ "transport.stop", "transport.play", "transport.record", + "transport.scrub", "navigation.up", "navigation.left", "navigation.zoom", "navigation.right", - "navigation.down", - "navigation.scrub" + "navigation.down" ] } \ No newline at end of file diff --git a/lib/encoder_mapping.json b/lib/encoder_mapping.json new file mode 100644 index 0000000..d4f10aa --- /dev/null +++ b/lib/encoder_mapping.json @@ -0,0 +1,218 @@ +{ + "mode_0": { + "0": { + "0": 1, + "1": 0 + }, + "1": { + "0": 2, + "1": 0 + }, + "2": { + "0": 4, + "1": 0 + }, + "3": { + "0": 8, + "1": 0 + }, + "4": { + "0": 16, + "1": 0 + }, + "5": { + "0": 32, + "1": 0 + }, + "6": { + "0": 64, + "1": 0 + }, + "7": { + "0": 0, + "1": 1 + }, + "8": { + "0": 0, + "1": 2 + }, + "9": { + "0": 0, + "1": 4 + }, + "10": { + "0": 0, + "1": 8 + }, + "11": { + "0": 0, + "1": 16 + }, + "12": { + "0": 0, + "1": 32 + } + }, + "mode_1": { + "0": { + "0": 63, + "1": 0 + }, + "1": { + "0": 62, + "1": 0 + }, + "2": { + "0": 60, + "1": 0 + }, + "3": { + "0": 56, + "1": 0 + }, + "4": { + "0": 48, + "1": 0 + }, + "5": { + "0": 32, + "1": 0 + }, + "6": { + "0": 64, + "1": 0 + }, + "7": { + "0": 0, + "1": 1 + }, + "8": { + "0": 0, + "1": 3 + }, + "9": { + "0": 0, + "1": 7 + }, + "10": { + "0": 0, + "1": 15 + }, + "11": { + "0": 0, + "1": 31 + }, + "12": { + "0": 0, + "1": 63 + } + }, + "mode_2": { + "0": { + "0": 1, + "1": 0 + }, + "1": { + "0": 3, + "1": 0 + }, + "2": { + "0": 7, + "1": 0 + }, + "3": { + "0": 15, + "1": 0 + }, + "4": { + "0": 31, + "1": 0 + }, + "5": { + "0": 63, + "1": 0 + }, + "6": { + "0": 127, + "1": 0 + }, + "7": { + "0": 127, + "1": 1 + }, + "8": { + "0": 127, + "1": 3 + }, + "9": { + "0": 127, + "1": 7 + }, + "10": { + "0": 127, + "1": 15 + }, + "11": { + "0": 127, + "1": 31 + }, + "12": { + "0": 127, + "1": 63 + } + }, + "mode_3": { + "0": { + "0": 64, + "1": 0 + }, + "1": { + "0": 96, + "1": 1 + }, + "2": { + "0": 96, + "1": 1 + }, + "3": { + "0": 112, + "1": 3 + }, + "4": { + "0": 112, + "1": 3 + }, + "5": { + "0": 120, + "1": 7 + }, + "6": { + "0": 120, + "1": 7 + }, + "7": { + "0": 124, + "1": 15 + }, + "8": { + "0": 124, + "1": 15 + }, + "9": { + "0": 126, + "1": 31 + }, + "10": { + "0": 126, + "1": 31 + }, + "11": { + "0": 127, + "1": 63 + }, + "12": { + "0": 127, + "1": 63 + } + } +} \ No newline at end of file diff --git a/lib/midi_mapping.json b/lib/midi_mapping.json index 232dbbf..6774db0 100644 --- a/lib/midi_mapping.json +++ b/lib/midi_mapping.json @@ -103,6 +103,7 @@ "93": "transport.stop", "94": "transport.play", "95": "transport.record", + "101": "transport.scrub", "46": "page.faderBankDec", "47": "page.faderBankInc", "48": "page.channelDec", @@ -111,6 +112,5 @@ "98": "navigation.left", "100": "navigation.zoom", "99": "navigation.right", - "97": "navigation.down", - "101": "navigation.scrub" + "97": "navigation.down" } \ No newline at end of file diff --git a/lib/objects_templates.json b/lib/objects_templates.json index 0f8f71b..4b94f97 100644 --- a/lib/objects_templates.json +++ b/lib/objects_templates.json @@ -406,7 +406,7 @@ "type": "channel", "common": { "role": "level.volume", - "name": "Master fader" + "name": "Channel fader" }, "native": {} } @@ -912,6 +912,15 @@ }, "native": {} }, + { + "_id": "scrub", + "type": "channel", + "common": { + "role": "button", + "name": "Button SCRUB (RED)" + }, + "native": {} + }, { "_id": "encoder", "type": "channel", @@ -982,15 +991,6 @@ "name": "Button v (DOWN) (ORANGE)" }, "native": {} - }, - { - "_id": "scrub", - "type": "channel", - "common": { - "role": "button", - "name": "Button SCRUB (RED)" - }, - "native": {} } ], "levelVolume": [ @@ -1310,7 +1310,20 @@ "type": "state", "common": { "role": "value", - "name": "If true controller is actually pressed", + "name": "If true encoder is actually pressed", + "type": "boolean", + "read": true, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "enabled", + "type": "state", + "common": { + "role": "value", + "name": "If true the encoder is working and will be displayed with the LED ring", "type": "boolean", "read": true, "write": true, @@ -1387,7 +1400,7 @@ "type": "state", "common": { "role": "value", - "name": "If controller is one step moved counter clockwise", + "name": "If controller is moved one step counter clockwise", "type": "boolean", "read": true, "write": false, @@ -1400,7 +1413,7 @@ "type": "state", "common": { "role": "value", - "name": "If controller is one step moved clockwise", + "name": "If controller is moved one step clockwise", "type": "boolean", "read": true, "write": false, diff --git a/main.js b/main.js index 43ff1d7..46c6ba7 100644 --- a/main.js +++ b/main.js @@ -52,11 +52,15 @@ class XTouch extends utils.Adapter { this.objects2Midi = {}; // and layout this.consoleLayout = JSON.parse(fs.readFileSync(__dirname + '/lib/console_layout.json', 'utf8')); + // mapping of the encoder modes to LED values + this.encoderMapping = JSON.parse(fs.readFileSync(__dirname + '/lib/encoder_mapping.json', 'utf8')); // devices object, key is ip address. Values are connection and memberOfGroup this.devices = []; - this.nextDevice = 0; // next device index for db creation + this.nextDevice = 0; // next device index for db creation this.deviceGroups = []; + this.timers = {}; // a place to store timers + this.timers.encoderWheels = {}; // e.g. encoder wheel reset timers by device group // Send buffer (Array of sendData objects) // sendData = { @@ -168,8 +172,14 @@ class XTouch extends utils.Adapter { self.deviceGroups[device_state._id] = device_state; tempObj = await self.getStateAsync(device_state._id); // @ts-ignore - self.deviceGroups[device_state._id].val = (tempObj && tempObj.val) ? tempObj.val : ''; + self.deviceGroups[device_state._id].val = (tempObj && tempObj.val !== undefined) ? tempObj.val : ''; self.deviceGroups[device_state._id].helperBool = false; // used for e.g. autoToggle + self.deviceGroups[device_state._id].helperNum = -1; // used for e.g. display of encoders + } + + // create a timer to reset the encoder state for each device group + for (let index = 0; index < self.config.deviceGroups; index++) { + self.timers.encoderWheels[index] = setTimeout(self.onEncoderWheelTimeoutExceeded.bind(self, index.toString()), 1000); } self.log.info('X-Touch got ' + Object.keys(self.deviceGroups).length + ' states from the db'); @@ -300,6 +310,16 @@ class XTouch extends utils.Adapter { this.setConnection(deviceAddress, 0, false); } + /** + * Is called when the encoder wheel values must be resetted to false + * @param {string} deviceGroup + */ + onEncoderWheelTimeoutExceeded(deviceGroup) { + this.log.debug(`X-Touch encoder wheel from device group ${deviceGroup}" reached inactivity timeout`); + this.setState(`deviceGroups.${deviceGroup}.transport.encoder.cw`, false, true); // reset the + this.setState(`deviceGroups.${deviceGroup}.transport.encoder.ccw`, false, true); // state values + } + /** * Is called on new datagram msg from server * @param {Buffer} msg the message content received by the server socket @@ -359,7 +379,7 @@ class XTouch extends utils.Adapter { await self.deviceSwitchChannels(action, info.address); } else { - await self.handleButton(baseId , undefined, actPressed ? 'pressed' : 'released', info.address); + await self.handleButton(baseId, undefined, actPressed ? 'pressed' : 'released', info.address); } break; @@ -370,10 +390,27 @@ class XTouch extends utils.Adapter { } else { baseId += '.banks.0.channels.' + (Number(midiMsg.channel) + 1) + '.fader'; } - await self.handleFader(baseId , midiMsg.value, 'fader', info.address); + await self.handleFader(baseId, midiMsg.value, 'fader', info.address); break; case 'ControlChange': // Encoders do that + baseId = self.namespace + '.deviceGroups.' + memberOfGroup; + if ((Number(midiMsg.controller) >= 16) && + (Number(midiMsg.controller) <= 23)){ // Channel encoder + baseId += '.banks.0.channels.' + (Number(midiMsg.controller) - 15) + '.encoder'; + } else { + baseId += '.transport.encoder'; + } + self.log.info(`midi message controller ${midiMsg.controller} value ${midiMsg.value}`); + let stepsTaken = 1; + let direction = 'cw'; + if (midiMsg.value < 65) { + stepsTaken = midiMsg.value; + } else { + stepsTaken = midiMsg.value - 64; + direction = 'ccw'; + } + await self.handleEncoder(baseId, stepsTaken, direction, info.address); break; } } @@ -642,7 +679,7 @@ class XTouch extends utils.Adapter { /** * handle the display status and call the send back if someting is changed - * @param {string} displayId only called via onStateChange + * @param {string} displayId only when called via onStateChange * @param {any | null | undefined} value */ async handleDisplay(displayId, value = undefined) { @@ -714,6 +751,160 @@ class XTouch extends utils.Adapter { } } + /** + * handle the encoder status and call the send back if someting is changed + * @param {string} encoderId only when called via onStateChange + * @param {any | null | undefined} value + * @param {string} event pressed, released or value (value = when called via onStateChange) + * @param {string} deviceAddress only chen called via onServerMessage + */ + async handleEncoder(encoderId, value = undefined, event = 'value', deviceAddress = '') { + const self = this; + try { + let baseId; + let stateName = ''; // the name of the particular state when called via onStateChange + const encoderArr = encoderId.split('.'); + let activeBank = 0; + let activeBaseChannel = 1; + let deviceGroup = encoderArr[3]; + let actVal; + let isDirty = false; // if true the encoder states has changed and must be sent + + if (encoderId === '') { + self.log.debug('X-Touch encoder not supported'); + return; + } + + if (event === 'value') { // when called via onStateChange there is the full encoder id, cut the last part for baseId + baseId = encoderId.substr(0, encoderId.lastIndexOf('.')); + stateName = encoderId.substr(encoderId.lastIndexOf('.') + 1); + + if (stateName === '') { + self.log.error('handleEncoder called with value and only baseId'); + return; // if no value part provided throw an error + } + switch (stateName) { + + case 'cw': // if wheel movement is simulated via database + case 'ccw': // only on encoder wheel possible + self.timers.devicegroup[deviceGroup].refresh(); // restart/refresh the timer + return; + + case 'enabled': + if (self.deviceGroups[baseId + '.enabled'].val != Boolean(value)) { // if changed send + self.deviceGroups[baseId + '.enabled'].val = Boolean(value); + isDirty = true; + } + break; + + case 'mode': + if ((value < 0) || (value > 3) || !Number.isInteger(value)) value = 0; // correct ? + if (self.deviceGroups[baseId + '.mode'].val != value) { // if changed send + self.deviceGroups[baseId + '.mode'].val = value; + isDirty = true; + } + break; + + case 'pressed': // reset if sent via database + self.setState(baseId + '.pressed', false, true); + return; + + case 'stepsPerTick': // check and correct + actVal = value; + if (value < 0) actVal = 0; + if (value > 1000) actVal = 1000; + if (!Number.isInteger(value)) actVal = parseInt(value, 10); + if (value != actVal) { // value corrected ? + await self.setStateAsync(baseId + '.stepsPerTick', Number(actVal), true); + } + if (self.deviceGroups[baseId + '.stepsPerTick'].val != actVal) { + self.deviceGroups[baseId + '.stepsPerTick'].val = actVal; + self.log.info(`handleEncoder changed the stepsPerTick to "${actVal}"`); + } + return; + + case 'value': + if (value < 0) value = 0; + if (value > 1000) value = 1000; + if (!Number.isInteger(value)) value = parseInt(value, 10); + if (self.deviceGroups[baseId + '.value'].val != value) { + self.deviceGroups[baseId + '.value'].val = value; + await self.setStateAsync(baseId + '.value', Number(value), true); + } + break; + } + + } else { // when called by midiMsg determine the real channel + if ((deviceAddress !== '') && self.devices[deviceAddress]) { + activeBank = self.devices[deviceAddress].activeBank; + activeBaseChannel = self.devices[deviceAddress].activeBaseChannel; + } + if (encoderArr[4] === 'banks') { // replace bank and baseChannel on channel encoders + encoderArr[5] = activeBank.toString(); + encoderArr[7] = (Number(encoderArr[7]) + activeBaseChannel - 1).toString(); + } + baseId = encoderArr.join('.'); + } + + if (encoderArr[5] === 'encoder') { // only on encoder wheel + switch (event) { + case 'cw': + await self.setStateAsync(baseId + '.cw', true, true); + self.timers.encoderWheels[deviceGroup].refresh(); // restart/refresh the timer + return; // nothing more to do + + case 'ccw': + await self.setStateAsync(baseId + '.ccw', true, true); + self.timers.encoderWheels[deviceGroup].refresh(); // restart/refresh the timer + return; // nothing more to do + + default: + self.log.error(`handleEncoder called with unknown event ${event} on encoder wheel`); + } + } + + if (self.deviceGroups[baseId + '.enabled'].val !== true) return; // no farther processing + + actVal = self.deviceGroups[baseId + '.value'].val; + + if (self.deviceGroups[baseId + '.value'].helperNum == -1) { // first call + self.deviceGroups[baseId + '.value'].helperNum = self.calculateEncoderValue(actVal); + } + + switch (event) { + case 'cw': // rotate to increment value + actVal += (self.deviceGroups[baseId + '.stepsPerTick'].val * value); // value contains the steps taken + if (actVal > 1000) actVal = 1000; + break; + + case 'ccw': // rotate to decrement value + actVal -= (self.deviceGroups[baseId + '.stepsPerTick'].val * value); + if (actVal < 0) actVal = 0; + break; + } + + self.deviceGroups[baseId + '.value'].val = actVal; + await self.setStateAsync(baseId + '.value', actVal, true); + + if (self.deviceGroups[baseId + '.value'].helperNum != this.calculateEncoderValue(actVal)) { + self.deviceGroups[baseId + '.value'].helperNum = this.calculateEncoderValue(actVal); + // if display value changed send + isDirty = true; + } + + let logStr = `handleEncoder event: ${event} new value ${actVal} `; + if (isDirty) { + logStr += `going to send ${self.deviceGroups[baseId + '.value'].helperNum}`; + self.sendEncoder(baseId); + } + self.log.debug(logStr); + // ToDo: handle syncGlobal + + } catch (err) { + self.errorHandler(err, 'handleEncoder'); + } + } + /******************************************************************************** * send functions to send back data to the device e.g. devices in the group ******************************************************************************** @@ -840,8 +1031,6 @@ class XTouch extends utils.Adapter { async sendDisplay(displayId, deviceAddress = '') { const self = this; try { - self.log.silly('Now send back state of display: "' + displayId + '"'); - let selectedBank; let channelInBank; let actDeviceGroup; @@ -868,6 +1057,8 @@ class XTouch extends utils.Adapter { let line2 = self.deviceGroups[baseId + '.line2'].val || ''; let line2_ct = self.deviceGroups[baseId + '.line2_ct'].val; + self.log.silly(`Now send back state of display: "${displayId}", Color: "${color}", Lines: "${line1}, ${line2}"`); + let midiString = 'F000006658'; midiString += ('20' + (32 + (Number(channelInBank) - 1)).toString(16)).slice(-2); // Add channel 20 - 27 if (inverted) { @@ -916,6 +1107,78 @@ class XTouch extends utils.Adapter { } } + /** + * send back the encoder status + * @param {string} encoderId + * @param {string} deviceAddress only chen called via deviceUpdatexx + */ + async sendEncoder(encoderId, deviceAddress = '') { + const self = this; + try { + let selectedBank; + let channelInBank; + let actDeviceGroup; + const encoderArr = encoderId.split('.'); + const realChannel = encoderArr[7]; + if (encoderArr[4] === 'banks') { // replace bank and baseChannel on channel buttons + actDeviceGroup = encoderArr[3]; + selectedBank = encoderArr[5]; + channelInBank = (Number(encoderArr[7]) % 8) == 0 ? '8' : (Number(encoderArr[7]) % 8).toString(); + } else { + self.log.error('sendEncoder called with a displayId with no banks identifier in it'); + return; + } + + let baseId = encoderId.substr(0, encoderId.lastIndexOf('.')); + const stateName = encoderArr.length > 9 ? encoderArr[9] : ''; + if (stateName === '') { + baseId = encoderId; // if called with no substate + } + + if (self.deviceGroups[baseId + '.value'].helperNum == -1) { // first call + self.deviceGroups[baseId + '.value'].helperNum = self.calculateEncoderValue(self.deviceGroups[baseId + '.value'].val); + } + + const dispVal = self.deviceGroups[baseId + '.value'].helperNum; + const ccByte1Left = Number(channelInBank) + 47; // controller 48 - 55 + const ccByte1Right = Number(channelInBank) + 55; // controller 56 - 63 + let ccByte2Left = self.encoderMapping['mode_' + self.deviceGroups[baseId + '.mode'].val][dispVal][0]; + let ccByte2Right = self.encoderMapping['mode_' + self.deviceGroups[baseId + '.mode'].val][dispVal][1]; + + if (self.deviceGroups[baseId + '.enabled'].val != true) { + self.log.debug(`encoder "${baseId}" disabled. switch off`); + ccByte2Left = 0; + ccByte2Right = 0; + } + + const midiCommand1 = new Uint8Array([0xB0, ccByte1Left, ccByte2Left]); + const midiCommand2 = new Uint8Array([0xB0, ccByte1Right, ccByte2Right]); + + self.log.debug(`Now send back state of encoder: "${encoderId}", cc: "${ccByte1Left}:${ccByte1Right}", values: "${ccByte2Left}:${ccByte2Right}"`); + + if (deviceAddress) { // only send to this device (will only called with display which will be seen on this device) + self.deviceSendData(midiCommand1, deviceAddress, self.devices[deviceAddress].port); + self.deviceSendData(midiCommand2, deviceAddress, self.devices[deviceAddress].port); + } else { // send to all connected devices on which this display is seen + for (const device of Object.keys(self.devices)) { + if ((actDeviceGroup == self.devices[device].memberOfGroup) && + (selectedBank == self.devices[device].activeBank) && + (Number(realChannel) >= self.devices[device].activeBaseChannel) && + (Number(realChannel) <= (self.devices[device].activeBaseChannel + 8)) && + (self.devices[device].connection)) { // only if display seen on console and device connected + self.deviceSendData(midiCommand1, self.devices[device].ipAddress, self.devices[device].port); + self.deviceSendData(midiCommand2, self.devices[device].ipAddress, self.devices[device].port); + } + } + } + + // ToDo: handle syncGlobal + + } catch (err) { + self.errorHandler(err, 'sendEncoder'); + } + } + /** * switch the bank up and down * @param {string} action bankUp, bankDown, channelUp, channelDown, none. action none used for illuminate the bank switches @@ -1063,7 +1326,11 @@ class XTouch extends utils.Adapter { case 'info.display': self.handleDisplay(id, state.val); break; - } + + case 'encoder': + self.handleEncoder(id, state.val); + break; + } } if (/illuminate|max/.test(id)) { self.log.warn(`X-Touch state ${id} changed. Please restart instance`); @@ -1082,31 +1349,11 @@ class XTouch extends utils.Adapter { } } - /** - * called for sending data (adding to the queue) - * @param {Buffer | Uint8Array | Array} data - * @param {string} deviceAddress - * @param {string | number} devicePort - */ - deviceSendData(data, deviceAddress, devicePort = 10111) { - const sendData = { - 'data': data, - 'address' : deviceAddress, - 'port': devicePort - }; - // Add sendData to the buffer - this.sendBuffer.push(sendData); - - if (!this.sendActive) { // if sending is possible - this.deviceSendNext(); - } - } - /** * called for sending all elements on status update * @param {string} deviceAddress */ - async deviceUpdateDevice(deviceAddress) { + async deviceUpdateDevice(deviceAddress) { const self = this; const activeGroup = self.devices[deviceAddress].memberOfGroup; try { @@ -1145,6 +1392,7 @@ class XTouch extends utils.Adapter { const baseId = self.namespace + '.deviceGroups.' + activeGroup + '.banks.' + activeBank + '.channels.' + (activeBaseChannel - 1 + baseChannel) + '.' + actElement; switch (actElement) { case 'encoder': + self.sendEncoder(baseId, deviceAddress); break; case 'display': @@ -1165,6 +1413,26 @@ class XTouch extends utils.Adapter { } } + /** + * called for sending data (adding to the queue) + * @param {Buffer | Uint8Array | Array} data + * @param {string} deviceAddress + * @param {string | number} devicePort + */ + deviceSendData(data, deviceAddress, devicePort = 10111) { + const sendData = { + 'data': data, + 'address' : deviceAddress, + 'port': devicePort + }; + // Add sendData to the buffer + this.sendBuffer.push(sendData); + + if (!this.sendActive) { // if sending is possible + this.deviceSendNext(); + } + } + /** * send next data in the queue * @param {any} err @@ -1175,10 +1443,12 @@ class XTouch extends utils.Adapter { self.log.error('X-Touch received an error on sending data'); } else { if (self.sendBuffer.length > 0) { - const localBuffer = self.sendBuffer.shift(); - const logData = localBuffer.data.toString('hex').toUpperCase(); - self.log.debug('X-Touch send data: "' + logData + '" to device: "' + localBuffer.address + '"'); - self.server.send(localBuffer.data, localBuffer.port, localBuffer.address, self.deviceSendNext.bind(self, err)); + self.sendActive = true; // for now only push to sendqueue possible + const locLen = self.sendBuffer.length; + const locBuffer = self.sendBuffer.shift(); + const logData = locBuffer.data.toString('hex').toUpperCase(); + self.log.debug(`X-Touch send data: "${logData}" to device: "${locBuffer.address}" Send Buffer length: ${locLen}`); + self.server.send(locBuffer.data, locBuffer.port, locBuffer.address, self.deviceSendNext.bind(self, err)); } else { self.log.silly('X-Touch send queue now empty'); self.sendActive = false; // queue is empty for now @@ -1294,9 +1564,8 @@ class XTouch extends utils.Adapter { const self = this; self.log.debug('Extron start to create/update the database'); - /* - create the device groups - */ + + // create the device groups for (let index = 0; index < self.config.deviceGroups; index++) { await self.createDeviceGroupAsync(index.toString()); } @@ -1634,6 +1903,14 @@ class XTouch extends utils.Adapter { return locObj; } + /** + * calculate encoder display value 0 - 1000 to 0 - 12 + * @param {*} value + */ + calculateEncoderValue(value) { + return parseInt((value / 77).toString(), 10); + } + /** * Called on error situations and from catch blocks * @param {Error} err @@ -1666,7 +1943,7 @@ class XTouch extends utils.Adapter { * Is called when adapter shuts down - callback has to be called under any circumstances! * @param {() => void} callback */ - onUnload(callback) { + onUnload(callback) { try { const self = this; // Reset the connection indicator diff --git a/package.json b/package.json index ac6d1c2..822c686 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.x-touch", - "version": "0.1.0", + "version": "0.2.0", "description": "Communicate with a Behringer X-Touch Control Surface (DAW Controller)", "author": { "name": "Bannsaenger",