From b6ee816fb3774f57628bc4b09c2b44657b91e88d Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 30 Mar 2018 16:07:56 -0500 Subject: [PATCH] Added decoding of messages Also moved things out to their own files and setup require()s to bring it all together. --- index.js | 75 +++----------------------- messages/SLChallengeMessage.js | 7 +++ messages/SLControllerConfigMessage.js | 78 +++++++++++++++++++++++++++ messages/SLLoginMessage.js | 12 +++++ messages/SLMessage.js | 57 ++++++++++++++++++++ messages/SLPoolStatusMessage.js | 70 ++++++++++++++++++++++++ messages/index.js | 4 ++ package.json | 1 + test.js | 41 +++++++------- 9 files changed, 259 insertions(+), 86 deletions(-) create mode 100755 messages/SLChallengeMessage.js create mode 100755 messages/SLControllerConfigMessage.js create mode 100755 messages/SLLoginMessage.js create mode 100755 messages/SLMessage.js create mode 100755 messages/SLPoolStatusMessage.js create mode 100755 messages/index.js diff --git a/index.js b/index.js index 71b43a6..29e5868 100644 --- a/index.js +++ b/index.js @@ -1,45 +1,7 @@ var dgram = require('dgram'); var net = require('net'); -const SmartBuffer = require('smart-buffer').SmartBuffer; const EventEmitter = require('events'); - -class SLMessage extends SmartBuffer { - constructor(senderId, messageId) { - super(); - this.writeUInt16LE(senderId); - this.writeUInt16LE(messageId); - - this._wroteSize = false; - } - - toBuffer() { - if (this._wroteSize === false) { - this.insertInt32LE(this.length - 4, 4); - this._wroteSize = true; - } else { - this.writeInt32LE(this.length - 8, 4); - } - - return super.toBuffer(); - } - - writeSLString(str) { - this.writeInt32LE(str.length); - this.writeString(str); - this.skip(4 - (str.length % 4)); - } - - writeSLBuffer(buf) { - this.writeInt32LE(buf.length); - this.writeBuffer(buf); - } - - skip(num) { - if (num > 0) { - this.writeBuffer(Buffer.alloc(num)); - } - } -} +const messages = require('./messages'); class FindUnits extends EventEmitter { constructor() { @@ -124,44 +86,22 @@ class UnitConnection extends EventEmitter { this.client.write('CONNECTSERVERHOST\r\n\r\n'); console.log('sending challenge message...'); - var buf = new SLMessage(0, 14); - this.client.write(buf.toBuffer()); + this.client.write(new messages.SLChallengeMessage().toBuffer()); } login() { console.log('sending login message...'); - var buf = new SLMessage(0, 27); - buf.writeInt32LE(348); // schema - buf.writeInt32LE(0); // connection type - buf.writeSLString('ScreenLogicConnect library'); // version - buf.writeSLBuffer(Buffer.alloc(16)); // encoded password. empty/unused for local connections - buf.writeInt32LE(2); // procID - this.client.write(buf.toBuffer()); + this.client.write(new messages.SLLoginMessage().toBuffer()); } getPoolStatus() { console.log('sending pool status query...'); - var buf = new SLMessage(0, 12526); - buf.writeInt32LE(0); - this.client.write(buf.toBuffer()); - } - - parsePoolStatus(msg) { - // todo: actually parse the message - this.emit('poolStatus', {}); + this.client.write(new messages.SLPoolStatusMessage().toBuffer()); } getControllerConfig() { console.log('sending controller config query...'); - var buf = new SLMessage(0, 12532); - buf.writeInt32LE(0); - buf.writeInt32LE(0); - this.client.write(buf.toBuffer()); - } - - parseControllerConfig(msg) { - // todo: actually parse the message - this.emit('controllerConfig', {}); + this.client.write(new messages.SLControllerConfigMessage().toBuffer()); } onClientMessage(msg) { @@ -175,10 +115,10 @@ class UnitConnection extends EventEmitter { this.emit('loggedIn'); } else if (msgType === 12527) { console.log(" it's pool status"); - this.parsePoolStatus(msg); + this.emit('poolStatus', new messages.SLPoolStatusMessage(msg)); } else if (msgType === 12533) { console.log(" it's controller configuration"); - this.parseControllerConfig(msg); + this.emit('controllerConfig', new messages.SLControllerConfigMessage(msg)); } } } @@ -191,6 +131,5 @@ for (const value of buf.values()) { module.exports = { FindUnits, - SLMessage, UnitConnection } diff --git a/messages/SLChallengeMessage.js b/messages/SLChallengeMessage.js new file mode 100755 index 0000000..739b223 --- /dev/null +++ b/messages/SLChallengeMessage.js @@ -0,0 +1,7 @@ +const SLMessage = require('./SLMessage.js').SLMessage; + +exports.SLChallengeMessage = class SLChallengeMessage extends SLMessage { + constructor() { + super(0, 14); + } +} diff --git a/messages/SLControllerConfigMessage.js b/messages/SLControllerConfigMessage.js new file mode 100755 index 0000000..82419ba --- /dev/null +++ b/messages/SLControllerConfigMessage.js @@ -0,0 +1,78 @@ +const SLMessage = require('./SLMessage.js').SLMessage; + +exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMessage { + constructor(buf) { + super(0, 12532); + if (!buf) { + this.writeInt32LE(0); + this.writeInt32LE(0); + } else { + this._wroteSize = true; + this.writeBuffer(buf, 0); + + this.decode(); + } + } + + decode() { + super.decode(); + + this.controllerId = this.readInt32LE(); + + this.minSetPoint = new Array(2); + this.maxSetPoint = new Array(2); + for (let i = 0; i < 2; i++) { + this.minSetPoint[i] = this.readUInt8(); + this.maxSetPoint[i] = this.readUInt8(); + } + + this.degC = this.readUInt8(); + this.controllerType = this.readUInt8(); + this.hwType = this.readUInt8(); + this.controllerData = this.readUInt8(); + this.equipFlags = this.readInt32LE(); + this.genCircuitName = this.readSLString(); + + let circuitCount = this.readInt32LE(); + this.bodyArray = new Array(circuitCount); + for (let i = 0; i < circuitCount; i++) { + this.bodyArray[i] = { + circuitId: this.readInt32LE(), + name: this.readSLString(), + nameIndex: this.readUInt8(), + function: this.readUInt8(), + interface: this.readUInt8(), + flags: this.readUInt8(), + colorSet: this.readUInt8(), + colorPos: this.readUInt8(), + colorStagger: this.readUInt8(), + deviceId: this.readUInt8(), + dfaultRt: this.readUInt16LE(), + pad1: this.readUInt8(), + pad2: this.readUInt8() + } + } + + let colorCount = this.readInt32LE(); + this.colorArray = new Array(colorCount); + for (let i = 0; i < colorCount; i++) { + this.colorArray[i] = { + name: this.readSLString(), + color: { + r: this.readInt32LE() & 0xFF, + g: this.readInt32LE() & 0xFF, + b: this.readInt32LE() & 0xFF + } + } + } + + let pumpCircCount = 8; + this.pumpCircArray = new Array(pumpCircCount); + for (let i = 0; i < pumpCircCount; i++) { + this.pumpCircArray[i] = this.readUInt8(); + } + + this.interfaceTabFlags = this.readInt32LE(); + this.showAlarms = this.readInt32LE(); + } +} diff --git a/messages/SLLoginMessage.js b/messages/SLLoginMessage.js new file mode 100755 index 0000000..0a5c798 --- /dev/null +++ b/messages/SLLoginMessage.js @@ -0,0 +1,12 @@ +const SLMessage = require('./SLMessage.js').SLMessage; + +exports.SLLoginMessage = class SLLoginMessage extends SLMessage { + constructor() { + super(0, 27); + this.writeInt32LE(348); // schema + this.writeInt32LE(0); // connection type + this.writeSLString('node-screenlogic'); // version + this.writeSLBuffer(Buffer.alloc(16)); // encoded password. empty/unused for local connections + this.writeInt32LE(2); // procID + } +} diff --git a/messages/SLMessage.js b/messages/SLMessage.js new file mode 100755 index 0000000..48d2ab3 --- /dev/null +++ b/messages/SLMessage.js @@ -0,0 +1,57 @@ +const SmartBuffer = require('smart-buffer').SmartBuffer; + +exports.SLMessage = class SLMessage extends SmartBuffer { + constructor(senderId, messageId) { + super(); + this.writeUInt16LE(senderId); + this.writeUInt16LE(messageId); + + this._wroteSize = false; + } + + toBuffer() { + if (this._wroteSize === false) { + this.insertInt32LE(this.length - 4, 4); + this._wroteSize = true; + } else { + this.writeInt32LE(this.length - 8, 4); + } + + return super.toBuffer(); + } + + writeSLString(str) { + this.writeInt32LE(str.length); + this.writeString(str); + if (str.length % 4 != 0) { + this.skipWrite(4 - (str.length % 4)); + } + } + + readSLString() { + var len = this.readInt32LE(); + var str = this.readString(len); + if (len % 4 != 0) { + this.readOffset += 4 - (len % 4); + } + return str; + } + + writeSLBuffer(buf) { + this.writeInt32LE(buf.length); + this.writeBuffer(buf); + } + + skipWrite(num) { + if (num > 0) { + this.writeBuffer(Buffer.alloc(num)); + } + } + + decode() { + this.readOffset = 0; + this.senderId = this.readUInt16LE(); + this.messageId = this.readUInt16LE(); + this.dataLength = this.readInt32LE(); + } +} diff --git a/messages/SLPoolStatusMessage.js b/messages/SLPoolStatusMessage.js new file mode 100755 index 0000000..0d2d6f3 --- /dev/null +++ b/messages/SLPoolStatusMessage.js @@ -0,0 +1,70 @@ +const SLMessage = require('./SLMessage.js').SLMessage; + +exports.SLPoolStatusMessage = class SLPoolStatusMessage extends SLMessage { + constructor(buf) { + super(0, 12526); + if (!buf) { + this.writeInt32LE(0); + } else { + this._wroteSize = true; + this.writeBuffer(buf, 0); + + this.decode(); + } + } + + decode() { + super.decode(); + + this.ok = this.readInt32LE(); + this.freezeMode = this.readUInt8(); + this.remotes = this.readUInt8(); + this.poolDelay = this.readUInt8(); + this.spaDelay = this.readUInt8(); + this.cleanerDelay = this.readUInt8(); + this.readOffset += 3; + this.airTemp = this.readInt32LE(); + + let bodiesCount = this.readInt32LE(); + if (bodiesCount > 2) { + bodiesCount = 2; + } + this.currentTemp = new Array(bodiesCount); + this.heatStatus = new Array(bodiesCount); + this.setPoint = new Array(bodiesCount); + this.coolSetPoint = new Array(bodiesCount); + this.heatMode = new Array(bodiesCount); + for (let i = 0; i < bodiesCount; i++) { + let bodyType = this.readInt32LE(); + if (bodyType < 0 || bodyType >= 2) { + bodyType = 0; + } + this.currentTemp[bodyType] = this.readInt32LE(); + this.heatStatus[bodyType] = this.readInt32LE(); + this.setPoint[bodyType] = this.readInt32LE(); + this.coolSetPoint[bodyType] = this.readInt32LE(); + this.heatMode[bodyType] = this.readInt32LE(); + } + + let circuitCount = this.readInt32LE(); + this.circuitArray = new Array(circuitCount); + for (let i = 0; i < circuitCount; i++) { + this.circuitArray[i] = { + id: this.readInt32LE(), + state: this.readInt32LE(), + colorSet: this.readUInt8(), + colorPos: this.readUInt8(), + colorStagger: this.readUInt8(), + delay: this.readUInt8() + } + } + + this.pH = this.readInt32LE(); + this.orp = this.readInt32LE(); + this.saturation = this.readInt32LE(); + this.saltPPM = this.readInt32LE(); + this.pHTank = this.readInt32LE(); + this.orpTank = this.readInt32LE(); + this.alarms = this.readInt32LE(); + } +} diff --git a/messages/index.js b/messages/index.js new file mode 100755 index 0000000..abe6af3 --- /dev/null +++ b/messages/index.js @@ -0,0 +1,4 @@ +exports.SLPoolStatusMessage = require("./SLPoolStatusMessage.js").SLPoolStatusMessage; +exports.SLControllerConfigMessage = require("./SLControllerConfigMessage.js").SLControllerConfigMessage; +exports.SLChallengeMessage = require("./SLChallengeMessage.js").SLChallengeMessage; +exports.SLLoginMessage = require("./SLLoginMessage.js").SLLoginMessage; diff --git a/package.json b/package.json index 7261953..07e5ed2 100755 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "license": "MIT", "repository": "https://github.com/parnic/ScreenLogicConnect-NodeJS.git", + "main": "index.js", "dependencies": { "smart-buffer": "~4.0.1" } diff --git a/test.js b/test.js index ffa3a9c..b228978 100755 --- a/test.js +++ b/test.js @@ -1,18 +1,23 @@ -const ScreenLogic = require('./index'); - -var finder = new ScreenLogic.FindUnits(); -finder.on('serverFound', function(server) { - var client = new ScreenLogic.UnitConnection(server); - client.on('loggedIn', function() { - this.getPoolStatus(); - this.getControllerConfig(); - }).on('poolStatus', function(status) { - }).on('controllerConfig', function(config) { - client.close(); - finder.close(); - }); - - client.connect(); -}); - -finder.search(); +const ScreenLogic = require('./index'); + +var finder = new ScreenLogic.FindUnits(); +finder.on('serverFound', function(server) { + var client = new ScreenLogic.UnitConnection(server); + client.on('loggedIn', function() { + this.getPoolStatus(); + this.getControllerConfig(); + }).on('poolStatus', function(status) { + console.log(" pool ok=" + status.ok); + console.log(" air temp=" + status.airTemp); + console.log(" salt ppm=" + status.saltPPM * 50); + console.log(" pH=" + status.pH / 100); + }).on('controllerConfig', function(config) { + console.log(" controller is in celsius=" + config.degC); + client.close(); + finder.close(); + }); + + client.connect(); +}); + +finder.search();