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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
node_modules/
node_modules/
33 changes: 25 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
maxcube
=======
# MaxCube2 [![NPM Version](https://img.shields.io/npm/v/maxcube2.svg)](https://www.npmjs.com/package/maxcube2)

eQ-3 Max! Cube interface library.
eQ-3 Max! Cube interface library for homebridge-platform-maxcube

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 fork of the work first started by https://github.com/ivesdebruycker/maxcube

## 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
```
var MaxCube = require('maxcube');
var MaxCube = require('maxcube2');
var myMaxCube = new MaxCube('192.168.1.123', 62910);

myMaxCube.on('connected', function () {
Expand All @@ -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()
Expand Down Expand Up @@ -77,6 +90,10 @@ 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)
### 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
11 changes: 9 additions & 2 deletions maxcube-commandfactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -146,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
};
122 changes: 86 additions & 36 deletions maxcube-commandparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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');
Expand All @@ -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;
}

Expand Down Expand Up @@ -310,41 +367,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

*/

//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;
}

Expand Down
4 changes: 2 additions & 2 deletions maxcube-lowlevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -91,4 +91,4 @@ MaxCubeLowLevel.prototype.close = close;
MaxCubeLowLevel.prototype.send = send;
MaxCubeLowLevel.prototype.isConnected = isConnected;

module.exports = MaxCubeLowLevel;
module.exports = MaxCubeLowLevel;
Loading