From fc5134cbee79257b9347f2a0320961219c82dfb9 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:01:42 +0100 Subject: [PATCH 01/22] Added set schedule + anti crash Squashed commit of the following: commit afd3f9814dcc33995899cab0a00d7d268ad96170 Author: Pim van Gennip Date: Sat Dec 9 22:04:17 2017 +0100 Proper result parsing of h: command commit 1ff8bb117008ccd4285beba43349ed21a23d2199 Author: Pim van Gennip Date: Sat Dec 9 16:00:51 2017 +0100 Updated hello command commit 1ca0f7c5a3292c3b5bb1def9ea6ed53e7695eb37 Author: Pim van Gennip Date: Sat Dec 2 00:01:37 2017 +0100 Added sayHello to update duty cycle commit c1b14fe237f83ee3c11cf6794e78ab949015363b Author: Pim van Gennip Date: Tue Nov 7 10:45:14 2017 +0100 Meta info serial changed to serial_number commit adbe86342286c289cea35f89ae5bc0e10f08ca08 Author: Pim van Gennip Date: Tue Nov 7 10:34:10 2017 +0100 Added serial and firmware version to Cube metaInfo commit 1c3f64fbe5bedfab05ed9d202ccc024331dace77 Author: Pim van Gennip Date: Fri Nov 3 16:59:56 2017 +0100 Only send continuous blocks, and make room_id hex commit c01c5a596b234922e06224d67794c8d7a6aee561 Author: Pim van Gennip Date: Fri Oct 27 10:19:18 2017 +0200 Cleaned up log entries commit 62e4661f275deeeb531f1bc31263da7cb4798921 Author: Pim van Gennip Date: Fri Oct 27 10:16:14 2017 +0200 Cleaned up code commit 39adc1a3688344751482b10bc7b03213abc040f7 Author: Pim van Gennip Date: Fri Oct 27 10:10:34 2017 +0200 Added battery_low and panel_locked to the standard deviceInfo commit 60f599c31c379df502974e73e2ae3425b4401425 Author: Pim van Gennip Date: Mon Oct 23 16:20:48 2017 +0200 Prevent Node from crashing upon cube timeout error commit 3493d88830d6854674df8c5535647f964ece9773 Author: Pim van Gennip Date: Sun Oct 22 23:15:43 2017 +0200 Created working schedule setting commit 87722f1045f3a3eb51e6d51033f90f80bf34f749 Author: Pim van Gennip Date: Thu Oct 19 23:11:26 2017 +0200 Working schedule commit 7374b810a1782f7a51e6216732e089d2ec6b161e Author: Pim van Gennip Date: Thu Oct 19 22:10:08 2017 +0200 Bugfix commit 1184eacd8130949c1a47582f0cb217071e579b86 Author: Pim van Gennip Date: Thu Oct 19 22:06:32 2017 +0200 Added setSchedule commit 1ec3276f867a86f0bb9b069fe3370dcd34f45db8 Author: Pim van Gennip Date: Fri Oct 13 14:47:17 2017 +0200 Added day program --- maxcube-commandfactory.js | 100 +++++++++++++++++++++++++++++++++++++- maxcube-lowlevel.js | 4 ++ maxcube.js | 64 +++++++++++++++++++++++- 3 files changed, 165 insertions(+), 3 deletions(-) diff --git a/maxcube-commandfactory.js b/maxcube-commandfactory.js index 99e967b..794a033 100644 --- a/maxcube-commandfactory.js +++ b/maxcube-commandfactory.js @@ -44,17 +44,113 @@ function generateSetTemperatureCommand (rfAdress, room_id, mode, temperature, un // '00' sets all temperature for all devices var room_id_padded = padLeft(room_id, 2); + var hexString = '000440000000' + rfAdress + room_id_padded + reqTempHex + date_until + time_until; + console.log(hexString); - var payload = new Buffer('000440000000' + rfAdress + room_id_padded + reqTempHex + date_until + time_until, 'hex').toString('base64'); + var payload = new Buffer(hexString, 'hex').toString('base64'); var data = 's:' + payload + '\r\n'; return data; } +// Source: https://github.com/Bouni/max-cube-protocol/blob/master/S-Message.md + +// Description Length Example Value +// ===================================================================== +// Base String 6 000410000000 +// RF Address 3 0FC380 +// Room Nr 1 01 +// Day of week 1 02 +// Temp and Time 2 4049 +// Temp and Time (2) 2 4c6e +// Temp and Time (3) 2 40cb +// Temp and Time (4) 2 4d20 +// Temp and Time (5) 2 4d20 +// Temp and Time (6) 2 4d20 +// Temp and Time (7) 2 4d02 + +// Day of week +// ===================================================================== +// hex: | 02 | +// dual: | 0000 0010 | +// |||| +// |+++-- day: 000: saturday +// | 001: sunday +// | 010: monday +// | 011: tuesday +// | 100: wednesday +// | 101: thursday +// | 110: friday +// | +// +----- telegram: 1: set +// 0: not set +// The meaning of telegram is unclear at the moment. + +// Temperature and Time +// ===================================================================== +// hex: | 40 | 49 | +// dual: | 0100 0000 | 0100 1001 | +// |||| |||| |||| |||| +// |||| |||+---++++-++++-- Time: 0 0100 1001: 06:05 +// |||| ||| +// |||| |||+-------------- Temperature: 0100 000: 16 +// This 16 bit word contains the temperature on the 7 MSB and Time until that temperature is set on the 9 LSB. Temperature value has to be divided by 2. +// 20 (hex) = 32 (decimal) -> 32/2 = 16 +// +// Time is the value * 5 minutes since midnight. +// 49 (hex) = 73 (decimal) -> 73*5 = 365 -> 6:05 +// 4d02 (hex) = 21:00, 19 deg + +function generateSetDayProgramCommand (rfAdress, room_id, weekday, temperaturesArray, timesArray) { + + // weekday: 0=mo,1=tu,..,6=su + // tempertures: [19.5,21,..] degrees Celsius (max 7) + // times: ['HH:mm',..] 24h format (max 7, same amount as temperatures) + + var dayArr = ['010','011','100','101','110','000','001']; // mo - su + var dayBin = dayArr[weekday]; + var reqDayBin = padLeft(dayBin, 8); + var reqDayHex = parseInt(reqDayBin, 2).toString(16); + + var hexTempTimeArr = []; + for (var i = 0; i < temperaturesArray.length; i++) + { + if (i < 6 || i == temperaturesArray.length-1) // max: 7, take 6 first and last + { + var temp = temperaturesArray[i]; + if (i < temperaturesArray.length-1 && temp == temperaturesArray[i+1]) + { + // temperature is the same as in the next time, so only set change @ the next time + } + else + { + var time = timesArray[i].split(':'); + var mins = ( parseInt(time[0]) * 60 + parseInt(time[1]) ); + var temB = padLeft(((temp || 0) * 2).toString(2), 7); + var timB = padLeft(Math.round(mins / 5).toString(2), 9); + var bin = temB + timB; + var hex = parseInt(bin, 2).toString(16); + + hexTempTimeArr.push(hex); + } + } + } + // to hex string + var reqTempTimeHex = hexTempTimeArr.join(''); + var room_id_padded = padLeft(room_id.toString(16), 2); + var req_day_padded = padLeft(reqDayHex, 2); + var hexString = '000410000000' + rfAdress + room_id_padded + req_day_padded + reqTempTimeHex; + var payload = new Buffer(hexString, 'hex').toString('base64'); + var data = 's:' + payload + '\r\n'; + + return data; +} + function padLeft(data, totalLength){ return Array(totalLength - String(data).length + 1).join('0') + data; } module.exports = { - generateSetTemperatureCommand: generateSetTemperatureCommand + generateSetTemperatureCommand: generateSetTemperatureCommand, + generateSetDayProgramCommand: generateSetDayProgramCommand }; diff --git a/maxcube-lowlevel.js b/maxcube-lowlevel.js index 09af07f..ea42475 100644 --- a/maxcube-lowlevel.js +++ b/maxcube-lowlevel.js @@ -10,6 +10,10 @@ function MaxCubeLowLevel(ip, port){ this.socket = new net.Socket(); this.isConnected = false; + process.on('uncaughtException', (err) => { + //console.error('Uncaught exception error'); + }); + initSocket.call(this); } diff --git a/maxcube.js b/maxcube.js index a4f64d3..dfe0542 100644 --- a/maxcube.js +++ b/maxcube.js @@ -19,6 +19,10 @@ function MaxCube(ip, port) { duty_cycle: 0, free_memory_slots: 0, } + this.metaInfo = { + serial: null, + firmware_version: null, + } this.roomCache = []; this.deviceCache = {}; @@ -47,8 +51,10 @@ function MaxCube(ip, port) { switch (command.type) { case 'H': { - self.commStatus.duty_cycle = parsedCommand.duty_cycle; + self.commStatus.duty_cycle = parsedCommand.duty_cycle; self.commStatus.free_memory_slots = parsedCommand.free_memory_slots; + self.metaInfo.serial_number = parsedCommand.serial_number; + self.metaInfo.firmware_version = parsedCommand.firmware_version; break; } case 'M': { @@ -57,8 +63,32 @@ function MaxCube(ip, port) { self.initialised = true; break; } + case 'L': { + self.updateDeviceInfo(parsedCommand); + + } } }); + + this.updateDeviceInfo = function(devices) + { + if (typeof devices != 'undefined') + { + for(var i=0; i < devices.length ; i++) + { + var deviceInfo = devices[i]; + var rf = deviceInfo.rf_address; + if (typeof self.deviceCache[rf] != 'undefined') + { + for(var item in deviceInfo) + { + var val = deviceInfo[item]; + self.deviceCache[rf][item] = val; + } + } + } + } + } } util.inherits(MaxCube, EventEmitter); @@ -124,12 +154,16 @@ MaxCube.prototype.getDeviceInfo = function(rf_address) { device_name: null, room_name: null, room_id: null, + battery_low: null, + panel_locked: null }; var device = this.deviceCache[rf_address]; if (device) { deviceInfo.device_type = device.device_type; deviceInfo.device_name = device.device_name; + deviceInfo.battery_low = device.battery_low; + deviceInfo.panel_locked= device.panel_locked; if (device.room_id && this.roomCache[device.room_id]) { var room = this.roomCache[device.room_id]; @@ -153,6 +187,16 @@ MaxCube.prototype.flushDeviceCache = function() { return send.call(this, 'm:\r\n'); }; +MaxCube.prototype.sayHello = function() { + checkInitialised.call(this); + + return send.call(this, 'h:\r\n', 'H').then(function (res) { + self.commStatus.duty_cycle = res.duty_cycle; + self.commStatus.free_memory_slots = res.free_memory_slots; + return true; + }); +}; + MaxCube.prototype.setTemperature = function(rf_address, degrees, mode, untilDate) { checkInitialised.call(this); @@ -166,6 +210,24 @@ MaxCube.prototype.setTemperature = function(rf_address, degrees, mode, untilDate }); }; +MaxCube.prototype.setSchedule = function(rf_address, room_id, weekday, temperaturesArray, timesArray) { + // weekday: 0=mo,1=tu,..,6=su + // temperaturesArray: [19.5,21,..] degrees Celsius (max 7) + // timesArray: ['HH:mm',..] 24h format (max 7, same amount as temperatures) + // the first time will be the time (from 00:00 to timesArray[0]) that the first temperature is active. Last possibe time of the day: 00:00 + + checkInitialised.call(this); + + var self = this; + + var command = MaxCubeCommandFactory.generateSetDayProgramCommand (rf_address, room_id, weekday, temperaturesArray, timesArray); + return send.call(this, command, 'S').then(function (res) { + self.commStatus.duty_cycle = res.duty_cycle; + self.commStatus.free_memory_slots = res.free_memory_slots; + return res.accepted; + }); +}; + MaxCube.prototype.close = function() { this.maxCubeLowLevel.close(); }; From d22d2c92ef7239b8a817368c4a32d544275e9ef4 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:03:47 +0100 Subject: [PATCH 02/22] fixed wall thermostat temp and setpoint Squashed commit of the following: commit 51b8003af1cf5c766276b229fbcbcc912eb7c91f Author: Luca Mazzilli Date: Mon Oct 16 23:31:58 2017 +0200 fixed wall thermostat temp and setpoint ivesdebruycker/node-red-contrib-maxcube#15 used info in https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md#actual-temperature-wallmountedthermostat to decode correctly the wall thermostat "Actual temperature" (temp) and "Temperature" (setpoint) --- maxcube-commandparser.js | 44 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index 08c4cb9..1505f44 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -207,7 +207,7 @@ function decodeDevice (payload) { switch (payload[0]) { case 8: deviceType = EQ3MAX_DEV_TYPE_PUSH_BUTTON; break; case 11: deviceType = EQ3MAX_DEV_TYPE_THERMOSTAT; deviceStatus = decodeDeviceThermostat (payload); break; - case 12: deviceType = EQ3MAX_DEV_TYPE_WALLTHERMOSTAT; deviceStatus = decodeDeviceThermostat (payload); break; + case 12: deviceType = EQ3MAX_DEV_TYPE_WALLTHERMOSTAT; deviceStatus = decodeDeviceWallThermostat (payload); break; default: deviceType = EQ3MAX_DEV_TYPE_UNKNOWN; break; } @@ -288,4 +288,44 @@ function decodeDeviceThermostat (payload) { return deviceStatus; } -exports.parse = parse; \ No newline at end of file +function decodeDeviceWallThermostat (payload) { + + //regular device parsing + var deviceStatus = decodeDeviceThermostat (payload); + + //wall thermostat has different temp and setpoint parsing: + //https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md#actual-temperature-wallmountedthermostat + + /* + Actual Temperature (WallMountedThermostat) + + 11 Actual Temperature 1 219 + Room temperature measured by the wall mounted thermostat in °C * 10. For example 0xDB = 219 = 21.9°C The temperature is represented by 9 bits; the 9th bit is available as the top bit at offset 8 + + offset| 8 | ... | 12 | + hex | B2 | | 24 | + binary| 1011 0010 | ... | 0010 0100 | + | || |||| |||| |||| + | ++-++++--------------------- temperature (°C*2): 110010 = 25.0°C + | |||| |||| + +-----------------++++-++++--- actual temperature (°C*10): 100100100 = 29.2°C + + */ + + //offset 8 binary to extract only needed bit + var off8Bin= (payload[8] >>> 0).toString(2); + + //offset8 without top bit (it is used by actual temperature and will corrupt the setpoint value) + var setPoint = parseInt(((off8Bin + '').substring(1)).replace(/[^01]/gi, ''), 2); + //C/2 + deviceStatus.setpoint = setPoint / 2; + + //get the TopBit and zero fill right to use it as 9 bit of offset 12 + var off8TopBit = parseInt(off8Bin.substring(0,1)) << 8; + //Bitwise OR offset 8/offset 12 and finally C/10 to read the actual temperature + deviceStatus.temp = (parseInt(off8TopBit) | parseInt(payload[12])) / 10; + + return deviceStatus; +} + +exports.parse = parse; From a3372e71ff44684e608e2d542ac73d5ad02234eb Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:14:49 +0100 Subject: [PATCH 03/22] Resolve merge conflicts Conflicts: maxcube-commandparser.js --- maxcube-commandparser.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index 1505f44..42a70b2 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -5,6 +5,7 @@ var EQ3MAX_DEV_TYPE_THERMOSTAT_PLUS = 2; var EQ3MAX_DEV_TYPE_WALLTHERMOSTAT = 3; var EQ3MAX_DEV_TYPE_SHUTTER_CONTACT = 4; var EQ3MAX_DEV_TYPE_PUSH_BUTTON = 5; +var EQ3MAX_DEV_TYPE_WINDOW_SWITCH = 6; var EQ3MAX_DEV_TYPE_UNKNOWN = 99; const StringDecoder = require('string_decoder').StringDecoder; @@ -205,6 +206,7 @@ function decodeDevice (payload) { var deviceStatus = {}; var deviceType = undefined; switch (payload[0]) { + case 6: deviceType = EQ3MAX_DEV_TYPE_WINDOW_SWITCH; deviceStatus = decodeDeviceWindowSwitch (payload); break; case 8: deviceType = EQ3MAX_DEV_TYPE_PUSH_BUTTON; break; case 11: deviceType = EQ3MAX_DEV_TYPE_THERMOSTAT; deviceStatus = decodeDeviceThermostat (payload); break; case 12: deviceType = EQ3MAX_DEV_TYPE_WALLTHERMOSTAT; deviceStatus = decodeDeviceWallThermostat (payload); break; @@ -216,6 +218,24 @@ function decodeDevice (payload) { return deviceStatus; } +function decodeDeviceWindowSwitch (payload) { + /* + According to https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md the information about + the window status is mapped in the lowest two bits in the flag word. + */ + var open = false; + + if ((payload[6] & (1 << 1)) > 0) { + open = true; + } + + var deviceStatus = { + rf_address: payload.slice(1, 4).toString('hex'), + open: open, + }; + return deviceStatus; +} + function decodeDeviceThermostat (payload) { /* source: http://www.domoticaforum.eu/viewtopic.php?f=66&t=6654 From d5c670ccbc2b87fb41b981bd58b352dd174b9f93 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:31:33 +0100 Subject: [PATCH 04/22] Update package and README --- README.md | 9 +++------ package.json | 14 ++++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a058084..b8902a0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ maxcube ======= -eQ-3 Max! Cube interface library. +eQ-3 Max! Cube interface library for Node.js v2 -For a cli, see [maxcube-cli](https://github.com/ivesdebruycker/maxcube-cli). If you want to integrate your MAX! Cube in node-red, use [node-red-node-maxcube](https://github.com/ivesdebruycker/node-red-node-maxcube). +This is a continuation of the work first started by https://github.com/ivesdebruycker/maxcube +It includes support for window sensors, wall thermostats and schedules in addition to the basic features of the previous version of this library. ## Example ``` @@ -76,7 +77,3 @@ myMaxCube.setTemperature('0dd6b5', 18).then(function (success) { } }); ``` - -## Related projects -* [maxcube-cli](https://github.com/ivesdebruycker/maxcube-cli): a command-line interface for eQ-3 Max! Cube -* [node-red-node-maxcube](https://github.com/ivesdebruycker/node-red-node-maxcube): a node for interfacing the eQ-3 Max! Cube using [node-red](https://github.com/node-red/node-red) diff --git a/package.json b/package.json index 24706f2..601eea0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "maxcube", - "version": "1.0.6", - "description": "eQ-3 Max! Cube interface", + "version": "2.0.0", + "description": "eq-3 Max! Cube interface", "main": "maxcube.js", "dependencies": { "bluebird": "^3.3.4", @@ -9,11 +9,17 @@ }, "repository": { "type": "git", - "url": "https://github.com/ivesdebruycker/maxcube.git" + "url": "https://github.com/normen/maxcube2.git" }, "author": "Ives De Bruycker", + "contributors": [ + "Normen Hansen", + "pvgennip", + "maxill1", + "Daniel Weeber" + ], "license": "ISC", "bugs": { - "url": "https://github.com/ivesdebruycker/maxcube/issues" + "url": "https://github.com/normen/maxcube2/issues" } } From d16f0344ddc84bb6adb41e4f8aa4cd3998b686d3 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:34:09 +0100 Subject: [PATCH 05/22] Update README name --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b8902a0..e5cef82 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -maxcube -======= +# MaxCube2 [![NPM Version](https://img.shields.io/npm/v/maxcube2.svg)](https://www.npmjs.com/package/maxcube2) eQ-3 Max! Cube interface library for Node.js v2 This is a continuation of the work first started by https://github.com/ivesdebruycker/maxcube -It includes support for window sensors, wall thermostats and schedules in addition to the basic features of the previous version of this library. +It includes support for window sensors, wall thermostats and schedules in addition to the basic features of the previous version of this library. The API didn't change so it's a drop-in replacement. ## Example ``` From d2d385bde4f8becacfcfce16f1bfc5c7aec8bf8f Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 22:43:00 +0100 Subject: [PATCH 06/22] Change npm package name --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 601eea0..807c24f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "maxcube", + "name": "maxcube2", "version": "2.0.0", "description": "eq-3 Max! Cube interface", "main": "maxcube.js", @@ -13,10 +13,10 @@ }, "author": "Ives De Bruycker", "contributors": [ - "Normen Hansen", "pvgennip", "maxill1", - "Daniel Weeber" + "Daniel Weeber", + "Normen Hansen" ], "license": "ISC", "bugs": { From a82983ab707349a3a8338e91ef04433a2c1a1ff1 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 23:35:29 +0100 Subject: [PATCH 07/22] Update README --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e5cef82..e171f27 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It includes support for window sensors, wall thermostats and schedules in additi ## Example ``` -var MaxCube = require('maxcube'); +var MaxCube = require('maxcube2'); var myMaxCube = new MaxCube('192.168.1.123', 62910); myMaxCube.on('connected', function () { @@ -76,3 +76,11 @@ myMaxCube.setTemperature('0dd6b5', 18).then(function (success) { } }); ``` + +### setSchedule(rf_address, room_id, weekday, temperaturesArray, timesArray) +Set a schedule for a device. + +- weekday: 0=mo,1=tu,..,6=su +- temperaturesArray: [19.5,21,..] degrees Celsius (max 7) +- timesArray: ['HH:mm',..] 24h format (max 7, same amount as temperatures) +- the first time will be the time (from 00:00 to timesArray[0]) that the first temperature is active. Last possibe time of the day: 00:00 From da83c0311ad237cc874e086254e8aca3fef322ed Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 5 Mar 2018 23:38:21 +0100 Subject: [PATCH 08/22] Update package info --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 807c24f..fed3667 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "maxcube2", - "version": "2.0.0", - "description": "eq-3 Max! Cube interface", + "version": "2.0.1", + "description": "eq-3 Max! Cube interface v2", "main": "maxcube.js", "dependencies": { "bluebird": "^3.3.4", @@ -13,8 +13,8 @@ }, "author": "Ives De Bruycker", "contributors": [ - "pvgennip", - "maxill1", + "Pim van Gennip", + "Luca Mazzilli", "Daniel Weeber", "Normen Hansen" ], From 10d5f6ccb652b602f837a37d65fd53786cbaeb10 Mon Sep 17 00:00:00 2001 From: normen Date: Tue, 6 Mar 2018 00:10:05 +0100 Subject: [PATCH 09/22] v2.0.1 * Remove console debug output --- maxcube-commandfactory.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/maxcube-commandfactory.js b/maxcube-commandfactory.js index 794a033..07e5733 100644 --- a/maxcube-commandfactory.js +++ b/maxcube-commandfactory.js @@ -45,7 +45,6 @@ function generateSetTemperatureCommand (rfAdress, room_id, mode, temperature, un // '00' sets all temperature for all devices var room_id_padded = padLeft(room_id, 2); var hexString = '000440000000' + rfAdress + room_id_padded + reqTempHex + date_until + time_until; - console.log(hexString); var payload = new Buffer(hexString, 'hex').toString('base64'); var data = 's:' + payload + '\r\n'; @@ -90,7 +89,7 @@ function generateSetTemperatureCommand (rfAdress, room_id, mode, temperature, un // ===================================================================== // hex: | 40 | 49 | // dual: | 0100 0000 | 0100 1001 | -// |||| |||| |||| |||| +// |||| |||| |||| |||| // |||| |||+---++++-++++-- Time: 0 0100 1001: 06:05 // |||| ||| // |||| |||+-------------- Temperature: 0100 000: 16 @@ -106,14 +105,14 @@ function generateSetDayProgramCommand (rfAdress, room_id, weekday, temperaturesA // weekday: 0=mo,1=tu,..,6=su // tempertures: [19.5,21,..] degrees Celsius (max 7) // times: ['HH:mm',..] 24h format (max 7, same amount as temperatures) - + var dayArr = ['010','011','100','101','110','000','001']; // mo - su var dayBin = dayArr[weekday]; var reqDayBin = padLeft(dayBin, 8); var reqDayHex = parseInt(reqDayBin, 2).toString(16); - + var hexTempTimeArr = []; - for (var i = 0; i < temperaturesArray.length; i++) + for (var i = 0; i < temperaturesArray.length; i++) { if (i < 6 || i == temperaturesArray.length-1) // max: 7, take 6 first and last { From b587ceca559ab2421734a998e6a3c9df31451c15 Mon Sep 17 00:00:00 2001 From: normen Date: Tue, 6 Mar 2018 00:14:44 +0100 Subject: [PATCH 10/22] Add NPM keywords --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index fed3667..4bcc71f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,16 @@ "Daniel Weeber", "Normen Hansen" ], + "keywords": [ + "eq-3", + "eq3", + "maxcube", + "thermostat", + "home", + "heating", + "max", + "elv" + ], "license": "ISC", "bugs": { "url": "https://github.com/normen/maxcube2/issues" From 9021e08bd9a545a33356f82e95f237077c86fdc9 Mon Sep 17 00:00:00 2001 From: normen Date: Tue, 6 Mar 2018 04:27:06 +0100 Subject: [PATCH 11/22] 2.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bcc71f..a0e6865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxcube2", - "version": "2.0.1", + "version": "2.0.2", "description": "eq-3 Max! Cube interface v2", "main": "maxcube.js", "dependencies": { From b2a8555297e25a1e2d42add2a22c6a967325ddb5 Mon Sep 17 00:00:00 2001 From: normen Date: Tue, 6 Mar 2018 23:32:35 +0100 Subject: [PATCH 12/22] 2.0.3 - Different way of reading wall thermostat --- maxcube-commandparser.js | 37 +++---------------------------------- package.json | 2 +- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index 42a70b2..ca8cfef 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -93,7 +93,7 @@ function parseCommandMetadata (payload) { for (var i = 0; i < device_count; i++) { var device_type = decodedPayload[currentIndex + 1]; var rf_address = decodedPayload.slice(currentIndex + 2, currentIndex + 5).toString('hex'); - var serialnumber = decodedPayload.slice(currentIndex + 5, currentIndex + 15).toString(); + var serial_number = decodedPayload.slice(currentIndex + 5, currentIndex + 15).toString(); var device_name_length = decodedPayload[currentIndex + 15]; var device_name = decodeStringPayload(decodedPayload.slice(currentIndex + 16, currentIndex + 16 + device_name_length)); var room_id = decodedPayload[currentIndex + 16 + device_name_length]; @@ -101,7 +101,7 @@ function parseCommandMetadata (payload) { var deviceData = { device_type: device_type, rf_address: rf_address, - serialnumber: serialnumber, + serial_number: serial_number, device_name: device_name, room_id: room_id, }; @@ -309,41 +309,10 @@ function decodeDeviceThermostat (payload) { } function decodeDeviceWallThermostat (payload) { - //regular device parsing var deviceStatus = decodeDeviceThermostat (payload); - //wall thermostat has different temp and setpoint parsing: - //https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md#actual-temperature-wallmountedthermostat - - /* - Actual Temperature (WallMountedThermostat) - - 11 Actual Temperature 1 219 - Room temperature measured by the wall mounted thermostat in °C * 10. For example 0xDB = 219 = 21.9°C The temperature is represented by 9 bits; the 9th bit is available as the top bit at offset 8 - - offset| 8 | ... | 12 | - hex | B2 | | 24 | - binary| 1011 0010 | ... | 0010 0100 | - | || |||| |||| |||| - | ++-++++--------------------- temperature (°C*2): 110010 = 25.0°C - | |||| |||| - +-----------------++++-++++--- actual temperature (°C*10): 100100100 = 29.2°C - - */ - - //offset 8 binary to extract only needed bit - var off8Bin= (payload[8] >>> 0).toString(2); - - //offset8 without top bit (it is used by actual temperature and will corrupt the setpoint value) - var setPoint = parseInt(((off8Bin + '').substring(1)).replace(/[^01]/gi, ''), 2); - //C/2 - deviceStatus.setpoint = setPoint / 2; - - //get the TopBit and zero fill right to use it as 9 bit of offset 12 - var off8TopBit = parseInt(off8Bin.substring(0,1)) << 8; - //Bitwise OR offset 8/offset 12 and finally C/10 to read the actual temperature - deviceStatus.temp = (parseInt(off8TopBit) | parseInt(payload[12])) / 10; + deviceStatus.temp = (payload[11]?25.5:0) + payload[12] / 10; return deviceStatus; } diff --git a/package.json b/package.json index a0e6865..92958c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxcube2", - "version": "2.0.2", + "version": "2.0.3", "description": "eq-3 Max! Cube interface v2", "main": "maxcube.js", "dependencies": { From 5645b7976970a22645ff422b2aa085ec608b2623 Mon Sep 17 00:00:00 2001 From: normen Date: Sat, 10 Mar 2018 23:16:48 +0100 Subject: [PATCH 13/22] Merge changes from dev branch - add more callbacks - fix serial number name - parity with original maxcube files --- .gitignore | 2 +- README.md | 19 +++++++++++-- maxcube-commandfactory.js | 8 +++--- maxcube-commandparser.js | 10 +++---- maxcube-lowlevel.js | 4 +-- maxcube.js | 58 +++++++++++++++++++++++++++++++++++---- package.json | 4 +-- 7 files changed, 82 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 40b878d..c2658d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -node_modules/ \ No newline at end of file +node_modules/ diff --git a/README.md b/README.md index e171f27..93c9993 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,18 @@ # MaxCube2 [![NPM Version](https://img.shields.io/npm/v/maxcube2.svg)](https://www.npmjs.com/package/maxcube2) -eQ-3 Max! Cube interface library for Node.js v2 +eQ-3 Max! Cube interface library for homebridge-platform-maxcube -This is a continuation of the work first started by https://github.com/ivesdebruycker/maxcube +This is a fork of the work first started by https://github.com/ivesdebruycker/maxcube -It includes support for window sensors, wall thermostats and schedules in addition to the basic features of the previous version of this library. The API didn't change so it's a drop-in replacement. +## Introduction +### History +Why this library is called maxcube_2_? Because the maxcube project seemed to be dead without response to issues or PRs for over half a year and I needed it fixed for my homebridge plugin. So I finally decided to continue its legacy as "maxcube2" but then it suddenly got revived. Now I won't change the name of this library anymore and keep this fork for the homebridge-platform-maxcube project - still as a proper merge-able fork of maxcube however. + +### Changes from maxcube +- More events (error, device_list etc.) +- Getting device configurations (min/max/eco/comfort temperatures etc.) + +The old API didn't change currently so it's a drop-in replacement. ## Example ``` @@ -28,6 +36,11 @@ myMaxCube.on('closed', function () { ## Events * connected * closed +* error +* hello (arg = hello object) +* meta_data (arg = meta data object) +* device_list (arg = list of devices) +* configuration (arg = configuration object for a single device) ## API ### getConnection() diff --git a/maxcube-commandfactory.js b/maxcube-commandfactory.js index 07e5733..7d0a602 100644 --- a/maxcube-commandfactory.js +++ b/maxcube-commandfactory.js @@ -89,7 +89,7 @@ function generateSetTemperatureCommand (rfAdress, room_id, mode, temperature, un // ===================================================================== // hex: | 40 | 49 | // dual: | 0100 0000 | 0100 1001 | -// |||| |||| |||| |||| +// |||| |||| |||| |||| // |||| |||+---++++-++++-- Time: 0 0100 1001: 06:05 // |||| ||| // |||| |||+-------------- Temperature: 0100 000: 16 @@ -105,14 +105,14 @@ function generateSetDayProgramCommand (rfAdress, room_id, weekday, temperaturesA // weekday: 0=mo,1=tu,..,6=su // tempertures: [19.5,21,..] degrees Celsius (max 7) // times: ['HH:mm',..] 24h format (max 7, same amount as temperatures) - + var dayArr = ['010','011','100','101','110','000','001']; // mo - su var dayBin = dayArr[weekday]; var reqDayBin = padLeft(dayBin, 8); var reqDayHex = parseInt(reqDayBin, 2).toString(16); - + var hexTempTimeArr = []; - for (var i = 0; i < temperaturesArray.length; i++) + for (var i = 0; i < temperaturesArray.length; i++) { if (i < 6 || i == temperaturesArray.length-1) // max: 7, take 6 first and last { diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index ca8cfef..e085e16 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -93,7 +93,7 @@ function parseCommandMetadata (payload) { for (var i = 0; i < device_count; i++) { var device_type = decodedPayload[currentIndex + 1]; var rf_address = decodedPayload.slice(currentIndex + 2, currentIndex + 5).toString('hex'); - var serial_number = decodedPayload.slice(currentIndex + 5, currentIndex + 15).toString(); + var serialnumber = decodedPayload.slice(currentIndex + 5, currentIndex + 15).toString(); var device_name_length = decodedPayload[currentIndex + 15]; var device_name = decodeStringPayload(decodedPayload.slice(currentIndex + 16, currentIndex + 16 + device_name_length)); var room_id = decodedPayload[currentIndex + 16 + device_name_length]; @@ -101,7 +101,7 @@ function parseCommandMetadata (payload) { var deviceData = { device_type: device_type, rf_address: rf_address, - serial_number: serial_number, + serialnumber: serialnumber, device_name: device_name, room_id: room_id, }; @@ -309,11 +309,9 @@ function decodeDeviceThermostat (payload) { } function decodeDeviceWallThermostat (payload) { - //regular device parsing + //regular device parsing, only temp is in a different location var deviceStatus = decodeDeviceThermostat (payload); - - deviceStatus.temp = (payload[11]?25.5:0) + payload[12] / 10; - + deviceStatus.temp = (payload[11]?25.5:0) + payload[12] / 10; return deviceStatus; } diff --git a/maxcube-lowlevel.js b/maxcube-lowlevel.js index ea42475..aaf9a11 100644 --- a/maxcube-lowlevel.js +++ b/maxcube-lowlevel.js @@ -11,7 +11,7 @@ function MaxCubeLowLevel(ip, port){ this.isConnected = false; process.on('uncaughtException', (err) => { - //console.error('Uncaught exception error'); + console.error('Uncaught exception error: ', err); }); initSocket.call(this); @@ -91,4 +91,4 @@ MaxCubeLowLevel.prototype.close = close; MaxCubeLowLevel.prototype.send = send; MaxCubeLowLevel.prototype.isConnected = isConnected; -module.exports = MaxCubeLowLevel; \ No newline at end of file +module.exports = MaxCubeLowLevel; diff --git a/maxcube.js b/maxcube.js index dfe0542..780ec73 100644 --- a/maxcube.js +++ b/maxcube.js @@ -15,6 +15,8 @@ function MaxCube(ip, port) { this.waitForCommandResolver = undefined; this.initialised = false; + this.setMaxListeners(1024); + this.commStatus = { duty_cycle: 0, free_memory_slots: 0, @@ -25,6 +27,7 @@ function MaxCube(ip, port) { } this.roomCache = []; this.deviceCache = {}; + this.configCache = {}; this.maxCubeLowLevel.on('closed', function () { self.initialised = false; @@ -41,6 +44,11 @@ function MaxCube(ip, port) { } }); + this.maxCubeLowLevel.on('error', function () { + self.initialised = false; + self.emit('error'); + }); + this.maxCubeLowLevel.on('command', function (command) { var parsedCommand = MaxCubeCommandParser.parse(command.type, command.payload); if (self.waitForCommandType === command.type && self.waitForCommandResolver) { @@ -55,17 +63,25 @@ function MaxCube(ip, port) { self.commStatus.free_memory_slots = parsedCommand.free_memory_slots; self.metaInfo.serial_number = parsedCommand.serial_number; self.metaInfo.firmware_version = parsedCommand.firmware_version; + self.emit('hello', parsedCommand); break; } case 'M': { self.roomCache = parsedCommand.rooms; self.deviceCache = parsedCommand.devices; self.initialised = true; + self.emit('meta_data', parsedCommand); break; } case 'L': { self.updateDeviceInfo(parsedCommand); - + self.emit('device_list', parsedCommand); + break; + } + case 'C': { + self.updateDeviceConfig(parsedCommand); + self.emit('configuration', parsedCommand); + break; } } }); @@ -89,6 +105,13 @@ function MaxCube(ip, port) { } } } + + this.updateDeviceConfig = function(deviceConfig){ + if (typeof deviceConfig != 'undefined'){ + var rf = deviceConfig.rf_address; + self.configCache[rf] = deviceConfig; + } + } } util.inherits(MaxCube, EventEmitter); @@ -140,12 +163,24 @@ MaxCube.prototype.getDeviceStatus = function(rf_address) { }); }; +MaxCube.prototype.updateDeviceStatus = function() { + checkInitialised.call(this); + + send.call(this, 'l:\r\n'); +}; + MaxCube.prototype.getDevices = function() { checkInitialised.call(this); return this.deviceCache; }; +MaxCube.prototype.getDevice = function(rf_address) { + checkInitialised.call(this); + + return this.deviceCache[rf_address]; +}; + MaxCube.prototype.getDeviceInfo = function(rf_address) { checkInitialised.call(this); @@ -171,10 +206,23 @@ MaxCube.prototype.getDeviceInfo = function(rf_address) { deviceInfo.room_id = room.room_id; } } - + return deviceInfo; }; +MaxCube.prototype.getDeviceConfiguration = function(rf_address) { + checkInitialised.call(this); + var deviceConfig = {} + var config = this.configCache[rf_address]; + if (config) { + for(var item in config) { + var val = config[item]; + deviceConfig[item] = val; + } + } + return deviceConfig; +}; + MaxCube.prototype.getRooms = function() { checkInitialised.call(this); @@ -214,12 +262,12 @@ MaxCube.prototype.setSchedule = function(rf_address, room_id, weekday, temperatu // weekday: 0=mo,1=tu,..,6=su // temperaturesArray: [19.5,21,..] degrees Celsius (max 7) // timesArray: ['HH:mm',..] 24h format (max 7, same amount as temperatures) - // the first time will be the time (from 00:00 to timesArray[0]) that the first temperature is active. Last possibe time of the day: 00:00 + // the first time will be the time (from 00:00 to timesArray[0]) that the first temperature is active. Last possibe time of the day: 00:00 checkInitialised.call(this); var self = this; - + var command = MaxCubeCommandFactory.generateSetDayProgramCommand (rf_address, room_id, weekday, temperaturesArray, timesArray); return send.call(this, command, 'S').then(function (res) { self.commStatus.duty_cycle = res.duty_cycle; @@ -232,4 +280,4 @@ MaxCube.prototype.close = function() { this.maxCubeLowLevel.close(); }; -module.exports = MaxCube; \ No newline at end of file +module.exports = MaxCube; diff --git a/package.json b/package.json index 92958c5..4f706d0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "maxcube2", - "version": "2.0.3", - "description": "eq-3 Max! Cube interface v2", + "version": "2.1.0-beta.2", + "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { "bluebird": "^3.3.4", From 40ddaa46777a9bc4614012ab1f6768d3d85e8503 Mon Sep 17 00:00:00 2001 From: normen Date: Sat, 10 Mar 2018 23:19:34 +0100 Subject: [PATCH 14/22] 2.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f706d0..8b7285a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxcube2", - "version": "2.1.0-beta.2", + "version": "2.1.0", "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { From 3a1fdf7a4e3aa54bbe8f13ae29e94d4fe2fc20c1 Mon Sep 17 00:00:00 2001 From: normen Date: Sun, 11 Mar 2018 22:52:05 +0100 Subject: [PATCH 15/22] Add resetError --- maxcube.js | 6 ++++++ package.json | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/maxcube.js b/maxcube.js index 780ec73..19b17a6 100644 --- a/maxcube.js +++ b/maxcube.js @@ -235,6 +235,12 @@ MaxCube.prototype.flushDeviceCache = function() { return send.call(this, 'm:\r\n'); }; +MaxCube.prototype.resetError = function() { + checkInitialised.call(this); + + return send.call(this, 'r:\r\n', 'S'); +}; + MaxCube.prototype.sayHello = function() { checkInitialised.call(this); diff --git a/package.json b/package.json index 8b7285a..4f99727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "maxcube2", +<<<<<<< HEAD "version": "2.1.0", +======= + "version": "2.1.1-beta.2", +>>>>>>> dev "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { From 1818f467cb6de741ebfeb6af967cfd5d953f2e0f Mon Sep 17 00:00:00 2001 From: normen Date: Sun, 11 Mar 2018 22:53:25 +0100 Subject: [PATCH 16/22] 2.1.1 --- package.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/package.json b/package.json index 4f99727..efa233f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,6 @@ { "name": "maxcube2", -<<<<<<< HEAD - "version": "2.1.0", -======= - "version": "2.1.1-beta.2", ->>>>>>> dev + "version": "2.1.1", "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { From 250d6fbeb4f3daa2b3ea94d6ecd78922a57da682 Mon Sep 17 00:00:00 2001 From: normen Date: Mon, 12 Mar 2018 00:35:56 +0100 Subject: [PATCH 17/22] 2.1.2 --- maxcube-commandfactory.js | 10 +++++++++- maxcube.js | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/maxcube-commandfactory.js b/maxcube-commandfactory.js index 7d0a602..d208208 100644 --- a/maxcube-commandfactory.js +++ b/maxcube-commandfactory.js @@ -145,11 +145,19 @@ function generateSetDayProgramCommand (rfAdress, room_id, weekday, temperaturesA return data; } +function generateResetCommand (rfAdress) { + var hexString = rfAdress + var payload = new Buffer(hexString, 'hex').toString('base64'); + var data = 'r:01,' + payload + '\r\n'; + return data; +} + function padLeft(data, totalLength){ return Array(totalLength - String(data).length + 1).join('0') + data; } module.exports = { generateSetTemperatureCommand: generateSetTemperatureCommand, - generateSetDayProgramCommand: generateSetDayProgramCommand + generateSetDayProgramCommand: generateSetDayProgramCommand, + generateResetCommand:generateResetCommand }; diff --git a/maxcube.js b/maxcube.js index 19b17a6..8a36f58 100644 --- a/maxcube.js +++ b/maxcube.js @@ -235,10 +235,10 @@ MaxCube.prototype.flushDeviceCache = function() { return send.call(this, 'm:\r\n'); }; -MaxCube.prototype.resetError = function() { +MaxCube.prototype.resetError = function(rf_address) { checkInitialised.call(this); - return send.call(this, 'r:\r\n', 'S'); + return send.call(this, MaxCubeCommandFactory.generateResetCommand(rf_address, this.deviceCache[rf_address].room_id), 'S'); }; MaxCube.prototype.sayHello = function() { diff --git a/package.json b/package.json index efa233f..c06fdd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxcube2", - "version": "2.1.1", + "version": "2.1.2", "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { From aa2bf2ace1cceeda6a4460da45d1174c79b43eef Mon Sep 17 00:00:00 2001 From: Luca Mazzilli Date: Sun, 2 Sep 2018 01:49:42 +0200 Subject: [PATCH 18/22] some cleanup of WT parsing --- maxcube-commandparser.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index 42a70b2..2a39447 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -329,22 +329,10 @@ function decodeDeviceWallThermostat (payload) { | ++-++++--------------------- temperature (°C*2): 110010 = 25.0°C | |||| |||| +-----------------++++-++++--- actual temperature (°C*10): 100100100 = 29.2°C - */ - - //offset 8 binary to extract only needed bit - var off8Bin= (payload[8] >>> 0).toString(2); - - //offset8 without top bit (it is used by actual temperature and will corrupt the setpoint value) - var setPoint = parseInt(((off8Bin + '').substring(1)).replace(/[^01]/gi, ''), 2); - //C/2 - deviceStatus.setpoint = setPoint / 2; - - //get the TopBit and zero fill right to use it as 9 bit of offset 12 - var off8TopBit = parseInt(off8Bin.substring(0,1)) << 8; - //Bitwise OR offset 8/offset 12 and finally C/10 to read the actual temperature - deviceStatus.temp = (parseInt(off8TopBit) | parseInt(payload[12])) / 10; - + //removing first and second bit from offset 8 00111111 & 10110010 = 00110010 + deviceStatus.setpoint = (63 & payload[8]) / 2; + deviceStatus.temp = (payload[8]>=128 ? 25.5 : 0) + payload[12] / 10; return deviceStatus; } From 857a9aee95b6bb4ebff14bca5032f9e58252bf13 Mon Sep 17 00:00:00 2001 From: Luca Mazzilli Date: Wed, 12 Sep 2018 23:52:38 +0200 Subject: [PATCH 19/22] parse weekly schedule from configuration command --- maxcube-commandparser.js | 61 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index e085e16..ad5567b 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -144,8 +144,47 @@ function parseCommandConfiguration (payload) { The five least significant bits (LSB) are presenting the time (in hours) 27 1 FF Maximum Valve setting; *(100/255) to get in % 1C 1 00 Valve Offset ; *(100/255) to get in % - 1D ? 44 48 ... Weekly program (see The weekly program) - */ + 1d 182 Weekly Program Schedule of 26 bytes for + each day starting with + Saturday. Each schedule + consists of 13 words + (2 bytes) e.g. set points. + 1 set point consist of + 7 MSB bits is temperature + set point (in degrees * 2) + 9 LSB bits is until time + (in minutes * 5) + + +*/ + var weekly_program_days = ['saturday', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday' ]; + var parseDayProgram = function(dayPayload){ + var temperaturesArray = []; + var timesArray = []; + var debug = []; + for (var i = 1; i <= 13; i++) { + var length = 2; + var offset = i*2; + // Weekly program 41 20 0100000 100100000 -> 16 degrees, until 24:00 + var msb = dayPayload[offset]>>1; + var lsb = (dayPayload[offset]&1)<<8; + + var setpoint = msb/2; + var minutes = (lsb+dayPayload[offset+1])*5; + var time = Math.floor(minutes / 60)+':'+(minutes%60==0?'00':(minutes%60<10?'0'+minutes%60:minutes%60)); + + //if a day has less than 13 setpoints, last one repeats until we reach 13 + if(setpoint > 0 && time !==undefined && setpoint !== temperaturesArray[temperaturesArray.length-1] && time !== timesArray[timesArray.length-1]){ + temperaturesArray.push(setpoint); + timesArray.push(time); + } + } + + return { + temperaturesArray: temperaturesArray, + timesArray: timesArray + }; + } var payloadArr = payload.split(","); var rf_address = payloadArr[0].slice(0, 6).toString('hex'); @@ -165,6 +204,24 @@ function parseCommandConfiguration (payload) { max_valve: decodedPayload[27] * (100/255) }; + try { + if(dataObj.device_type !== 5){ + dataObj.weekly_program = {}; + var length = 26; + var offset = 27; + for (var i = 0; i < 7; i++) { + const bf = Buffer.alloc(length); + var end = offset + length; + decodedPayload.copy(bf, 0, offset, offset+length); + dataObj.weekly_program[weekly_program_days[i]] = parseDayProgram(bf); + offset = end; + } + + } + } catch (e) { + console.log("Error getting weekly program for device "+dataObj.rf_address, dataObj); + } + return dataObj; } From 1af9e1222f4bec9affb0cbdaf4bd0f43d4506342 Mon Sep 17 00:00:00 2001 From: Luca Mazzilli Date: Fri, 14 Sep 2018 00:56:34 +0200 Subject: [PATCH 20/22] testing two way to read WT #27 --- maxcube-commandparser.js | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index 2a39447..38e0406 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -310,29 +310,34 @@ function decodeDeviceThermostat (payload) { function decodeDeviceWallThermostat (payload) { - //regular device parsing + //regular device parsing, only temp is in a different location var deviceStatus = decodeDeviceThermostat (payload); + deviceStatus.temp = (payload[11]?25.5:0) + payload[12] / 10; + + //alternative parsing if setpoint is 60-80°C + if(payload[8]>=128){ + //wall thermostat has different temp and setpoint parsing: + //https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md#actual-temperature-wallmountedthermostat + + /* + Actual Temperature (WallMountedThermostat) + + 11 Actual Temperature 1 219 + Room temperature measured by the wall mounted thermostat in °C * 10. For example 0xDB = 219 = 21.9°C The temperature is represented by 9 bits; the 9th bit is available as the top bit at offset 8 + + offset| 8 | ... | 12 | + hex | B2 | | 24 | + binary| 1011 0010 | ... | 0010 0100 | + | || |||| |||| |||| + | ++-++++--------------------- temperature (°C*2): 110010 = 25.0°C + | |||| |||| + +-----------------++++-++++--- actual temperature (°C*10): 100100100 = 29.2°C + */ - //wall thermostat has different temp and setpoint parsing: - //https://github.com/Bouni/max-cube-protocol/blob/master/L-Message.md#actual-temperature-wallmountedthermostat - - /* - Actual Temperature (WallMountedThermostat) - - 11 Actual Temperature 1 219 - Room temperature measured by the wall mounted thermostat in °C * 10. For example 0xDB = 219 = 21.9°C The temperature is represented by 9 bits; the 9th bit is available as the top bit at offset 8 - - offset| 8 | ... | 12 | - hex | B2 | | 24 | - binary| 1011 0010 | ... | 0010 0100 | - | || |||| |||| |||| - | ++-++++--------------------- temperature (°C*2): 110010 = 25.0°C - | |||| |||| - +-----------------++++-++++--- actual temperature (°C*10): 100100100 = 29.2°C - */ - //removing first and second bit from offset 8 00111111 & 10110010 = 00110010 - deviceStatus.setpoint = (63 & payload[8]) / 2; - deviceStatus.temp = (payload[8]>=128 ? 25.5 : 0) + payload[12] / 10; + //removing first and second bit from offset 8 00111111 & 10110010 = 00110010 + deviceStatus.setpoint = (63 & payload[8]) / 2; + deviceStatus.temp = (payload[8]>=128 ? 25.5 : 0) + payload[12] / 10; + } return deviceStatus; } From 78d0b32f0533f6293824361ddecc9e50f581c06d Mon Sep 17 00:00:00 2001 From: normen Date: Thu, 24 Jan 2019 18:16:47 +0100 Subject: [PATCH 21/22] v2.1.3 - fixes Buffer error in newer node.js versions --- maxcube-commandparser.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maxcube-commandparser.js b/maxcube-commandparser.js index e085e16..6f961cc 100644 --- a/maxcube-commandparser.js +++ b/maxcube-commandparser.js @@ -63,7 +63,7 @@ function parseCommandHello (payload) { function parseCommandMetadata (payload) { var payloadArr = payload.split(","); - var decodedPayload = new Buffer(payloadArr[2], 'base64'); + var decodedPayload = Buffer.from(payloadArr[2], 'base64'); var room_count = decodedPayload[2]; var currentIndex = 3; diff --git a/package.json b/package.json index c06fdd0..6ada84a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxcube2", - "version": "2.1.2", + "version": "2.1.3", "description": "eq-3 Max! Cube interface for homebridge-platform-maxcube", "main": "maxcube.js", "dependencies": { From a6aee33871bc66ddcd717117d44433d1e68606ae Mon Sep 17 00:00:00 2001 From: Luca Mazzilli Date: Fri, 8 Mar 2019 21:19:05 +0100 Subject: [PATCH 22/22] emit error on command parsing (cube not initialized or cube self-reset...) --- maxcube.js | 66 +++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/maxcube.js b/maxcube.js index 8a36f58..d48f2ea 100644 --- a/maxcube.js +++ b/maxcube.js @@ -50,39 +50,43 @@ function MaxCube(ip, port) { }); this.maxCubeLowLevel.on('command', function (command) { - var parsedCommand = MaxCubeCommandParser.parse(command.type, command.payload); - if (self.waitForCommandType === command.type && self.waitForCommandResolver) { - self.waitForCommandResolver.resolve(parsedCommand); - self.waitForCommandType = undefined; - self.waitForCommandResolver = undefined; - } - - switch (command.type) { - case 'H': { - self.commStatus.duty_cycle = parsedCommand.duty_cycle; - self.commStatus.free_memory_slots = parsedCommand.free_memory_slots; - self.metaInfo.serial_number = parsedCommand.serial_number; - self.metaInfo.firmware_version = parsedCommand.firmware_version; - self.emit('hello', parsedCommand); - break; - } - case 'M': { - self.roomCache = parsedCommand.rooms; - self.deviceCache = parsedCommand.devices; - self.initialised = true; - self.emit('meta_data', parsedCommand); - break; + try { + var parsedCommand = MaxCubeCommandParser.parse(command.type, command.payload); + if (self.waitForCommandType === command.type && self.waitForCommandResolver) { + self.waitForCommandResolver.resolve(parsedCommand); + self.waitForCommandType = undefined; + self.waitForCommandResolver = undefined; } - case 'L': { - self.updateDeviceInfo(parsedCommand); - self.emit('device_list', parsedCommand); - break; - } - case 'C': { - self.updateDeviceConfig(parsedCommand); - self.emit('configuration', parsedCommand); - break; + + switch (command.type) { + case 'H': { + self.commStatus.duty_cycle = parsedCommand.duty_cycle; + self.commStatus.free_memory_slots = parsedCommand.free_memory_slots; + self.metaInfo.serial_number = parsedCommand.serial_number; + self.metaInfo.firmware_version = parsedCommand.firmware_version; + self.emit('hello', parsedCommand); + break; + } + case 'M': { + self.roomCache = parsedCommand.rooms; + self.deviceCache = parsedCommand.devices; + self.initialised = true; + self.emit('meta_data', parsedCommand); + break; + } + case 'L': { + self.updateDeviceInfo(parsedCommand); + self.emit('device_list', parsedCommand); + break; + } + case 'C': { + self.updateDeviceConfig(parsedCommand); + self.emit('configuration', parsedCommand); + break; + } } + } catch (e) { + self.emit('error', "Problem while parsing the command '" +command.type+"': " + e.stack); } });