From 4111c1fa5ae64245e3e540a72472e6e8d99421e2 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:18:59 +0300 Subject: [PATCH 01/10] Update deviceFactory.js --- app/deviceFactory.js | 237 ++++++++++++++++++++++++++++++++----------- 1 file changed, 175 insertions(+), 62 deletions(-) diff --git a/app/deviceFactory.js b/app/deviceFactory.js index 3fba592..f6c7562 100644 --- a/app/deviceFactory.js +++ b/app/deviceFactory.js @@ -1,16 +1,8 @@ 'use strict'; -const net = require('net'); const dgram = require('dgram'); const socket = dgram.createSocket('udp4'); -const encryptionService = require('./encryptionService')(); +//const encryptionService = require('./encryptionService')(); const cmd = require('./commandEnums'); -const _ = require('lodash'); - -const utils = require("./utils"); - -const client = new net.Socket(); - - /** * Class representing a single connected device @@ -30,16 +22,11 @@ class Device { // Set defaults this.options = { host: options.host || '192.168.111.255', - onStatus: options.onStatus || function() {}, - onUpdate: options.onUpdate || function() {}, - onConnected: options.onConnected || function() {} - }; - client.on('data', (msg, rinfo) => this._handleResponse(msg, rinfo)); - - client.on('listening', () => { - const address = client.address(); - console.log(`server listening ${address.address}:${address.port}`); - }); + onStatus: options.onStatus || function () {}, + onUpdate: options.onUpdate || function () {}, + onConnected: options.onConnected || function () {} + } + /** * Device object * @typedef {object} Device @@ -51,11 +38,11 @@ class Device { * @property {object} props - Properties */ this.device = {}; - // + this.defaultPort = 12414; this.defaultDiscoveryPort = 2415; - this.deviceStatusPort = 12416 - // Initialize connection and bind with device + this.deviceStatusPort = 12416; + // Initialize connection and bind with device this._connectToDevice(this.options.host); // Handle incoming messages @@ -86,7 +73,7 @@ class Device { console.log('[UDP] Connected to device at %s', address); }); } catch (err) { - const timeout = 60 + const timeout = 60; console.log('[UDP] Unable to connect (' + err.message + '). Retrying in ' + timeout + 's...'); setTimeout(() => { @@ -118,21 +105,22 @@ class Device { * @param {Device} device Device object */ _sendBindRequest(device) { - /*const message = { - mac: this.device.id, - t: 'bind', - uid: 0 - }; - const encryptedBoundMessage = encryptionService.encrypt(message); - const request = { - cid: 'app', - i: 1, - t: 'pack', - uid: 0, - pack: encryptedBoundMessage - }; - const toSend = new Buffer(JSON.stringify(request)); - socket.send(toSend, 0, toSend.length, device.port, device.address);*/ + /* const message = { + mac: this.device.id, + t: 'bind', + uid: 0 + }; + const encryptedBoundMessage = encryptionService.encrypt(message); + const request = { + cid: 'app', + i: 1, + t: 'pack', + uid: 0, + pack: encryptedBoundMessage + }; + const toSend = Buffer.from(JSON.stringify(request)); + socket.send(toSend, 0, toSend.length, device.port, device.address); + */ } /** @@ -141,21 +129,21 @@ class Device { * @param {String} key - Encryption key */ _confirmBinding(id, key) { - /* this.device.bound = true; - this.device.key = key; - console.log('[UDP] Device %s is bound!', this.device.name);*/ + /*this.device.bound = true; + this.device.key = key; + console.log('[UDP] Device %s is bound!', this.device.name);*/ } /** * Confirm device is bound and update device status on list * @param {Device} device - Device */ - _requestDeviceStatus(device, that) { + _requestDeviceStatus(device) { console.log("--in _requestDeviceStatus"); let serializedRequest = new Buffer([0xAA, 0xAA, 0x12, 0xA0, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A]); if (!this.isConnected) { - client.connect(this.deviceStatusPort, device.address, function(data) { + client.connect(this.deviceStatusPort, device.address, function (data) { console.log('Connected to tcp port'); this.isConnected = true; client.write(serializedRequest); @@ -164,8 +152,6 @@ class Device { console.log("-- already connected"); client.write(serializedRequest); } - - // socket.send(serializedRequest, 0, serializedRequest.length, device.port, device.ip); } /** @@ -184,6 +170,7 @@ class Device { this._requestDeviceStatus(this.device, this); this.options.onConnected(this.device); // this._sendBindRequest(this.device); + return; } else { console.log("received status msg."); let statusMessage = utils.parseMessage(msg); @@ -191,7 +178,10 @@ class Device { this.device.lastCmd = msg; this.device.props = statusMessage; this.options.onStatus(this.device); + return; } + + console.log('[UDP] Unknown message of type %s: %s, %s', pack.t, message, pack); } /** @@ -200,12 +190,12 @@ class Device { * @param {number[]} values List of values */ _sendCommand(commands = [], values = []) { - /* const message = { - opt: commands, - p: values, - t: 'cmd' - }; - this._sendRequest(message);*/ + /* const message = { + opt: commands, + p: values, + t: 'cmd' + }; + this._sendRequest(message);*/ }; /** @@ -217,7 +207,18 @@ class Device { * @param {string} [address] IP/host address * @param {number} [port] Port number */ - _sendRequest(message, address = this.device.address, port = this.device.port) {}; + _sendRequest(message, address = this.device.address, port = this.device.port) { + /* const encryptedMessage = encryptionService.encrypt(message, this.device.key); + const request = { + cid: 'app', + i: 0, + t: 'pack', + uid: 0, + pack: encryptedMessage + }; + const serializedRequest = Buffer.from(JSON.stringify(request)); + socket.send(serializedRequest, 0, serializedRequest.length, port, address);*/ + }; /** * Turn on/off @@ -227,7 +228,7 @@ class Device { console.log('--In setPower: ' + value); if (this.device.lastCmd) client.write(utils.cmd01(this.device.lastCmd, value)); - }; + } /** * Set temperature @@ -238,40 +239,152 @@ class Device { console.log('--In setTemp: ' + value); if (this.device.lastCmd) client.write(utils.cmd07(this.device.lastCmd, value, false)); - - }; + } /** * Set mode * @param {number} value Mode value (0-4) */ setMode(value) { + console.log('--In setMode: ' + value); this._sendCommand( [cmd.mode.code], [value] ); - }; + } /** * Set fan speed * @param {number} value Fan speed value (0-5) */ setFanSpeed(value) { + console.log('--In setFanSpeed: ' + value); this._sendCommand( - [cmd.fanSpeed.code], [value] + [cmd.fanSpeed.code], + [value] ); - }; + } + + /** + * Set horizontal swing + * @param {number} value Horizontal swing value (0-7) + */ + setSwingHor(value) { + console.log('--In setSwingHor: ' + value); + this._sendCommand( + [cmd.swingHor.code], + [value] + ); + } /** * Set vertical swing * @param {number} value Vertical swing value (0-11) */ setSwingVert(value) { + console.log('--In setSwingVert: ' + value); this._sendCommand( - [cmd.swingVert.code], [value] + [cmd.swingVert.code], + [value] ); - }; -}; + } + + /** + * Set power save mode + * @param {boolean} value on/off + */ + setPowerSave(value) { + console.log('--In setPowerSave: ' + value); + this._sendCommand( + [cmd.energySave.code], + [value ? 1 : 0] + ); + } + + /** + * Set lights on/off + * @param {boolean} value on/off + */ + setLights(value) { + console.log('--In setLights: ' + value); + this._sendCommand( + [cmd.lights.code], + [value ? 1 : 0] + ); + } + + /** + * Set health mode + * @param {boolean} value on/off + */ + setHealthMode(value) { + console.log('--In setLights: ' + value); + this._sendCommand( + [cmd.health.code], + [value ? 1 : 0] + ); + } + + /** + * Set quiet mode + * @param {boolean} value on/off + */ + setQuietMode(value) { + console.log('--In setQuietMode: ' + value); + this._sendCommand( + [cmd.quiet.code], + [value] + ); + } + + /** + * Set blow mode + * @param {boolean} value on/off + */ + setBlow(value) { + console.log('--In setBlow: ' + value); + this._sendCommand( + [cmd.blow.code], + [value ? 1 : 0] + ); + } + + /** + * Set air valve mode + * @param {boolean} value on/off + */ + setAir(value) { + console.log('--In setAir: ' + value); + this._sendCommand( + [cmd.air.code], + [value] + ); + } + + /** + * Set sleep mode + * @param {boolean} value on/off + */ + setSleepMode(value) { + console.log('--In setSleepMode: ' + value); + this._sendCommand( + [cmd.sleep.code], + [value ? 1 : 0] + ); + } + + /** + * Set turbo mode + * @param {boolean} value on/off + */ + setTurbo(value) { + console.log('--In setTurbo: ' + value); + this._sendCommand( + [cmd.turbo.code], + [value ? 1 : 0] + ); + } +} -module.exports.connect = function(options) { +module.exports.connect = function (options) { return new Device(options); }; From ad21fdf921e9a3f25d706582e4f8941bd6a85c66 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:19:11 +0300 Subject: [PATCH 02/10] Update commandEnums.js --- app/commandEnums.js | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/app/commandEnums.js b/app/commandEnums.js index 58eebd7..8d8b398 100644 --- a/app/commandEnums.js +++ b/app/commandEnums.js @@ -13,11 +13,11 @@ module.exports = { mode: { code: 'runMode', value: { - auto: "000", - cool: "001", - dry: "010", - wind: "011", - heat: "100", + auto: '000', + cool: '001', + dry: '010', + wind: '011', + heat: '100' } }, // temperature unit (must be together with set temperature) @@ -30,11 +30,11 @@ module.exports = { }, // set temperature (must be together with temperature unit) temperature: { - code: 'indoorTemperature' //SetTem + code: 'indoorTemperature' }, // fan speed fanSpeed: { - code: 'windMode', //WdSpd + code: 'windMode', value: { auto: 0, //fan auto low: 1, //1 @@ -48,14 +48,16 @@ module.exports = { } }, // fresh air valve - airVale: { + air: { code: 'Air', value: { off: 0, - on: 1 + inside: 1, + outside: 2, + mode3: 3 } }, - // "Blow" or "X-Fan", this function keeps the fan running for a while after shutting down. Only usable in Dry and Cool mode + // keeps the fan running for a while after shutting down (also called "X-Fan", only usable in Dry and Cool mode) blow: { code: 'Blo', value: { @@ -65,7 +67,7 @@ module.exports = { }, // controls Health ("Cold plasma") mode, only for devices equipped with "anion generator", which absorbs dust and kills bacteria health: { - code: 'healthy', //Health + code: 'healthy', value: { off: 0, on: 1 @@ -91,8 +93,14 @@ module.exports = { swingHor: { code: 'SwingLfRig', value: { - default: 0 - // TODO: remaining values + default: 0, + //full: 1, // swing in full range + fixedLeft: 2, // fixed in leftmost position (1/5) + fixedMidLeft: 3, // fixed in middle-left postion (2/5) + fixedMid: 4, // fixed in middle position (3/5) + fixedMidRight: 5, // fixed in middle-right postion (4/5) + fixedRight: 6, // fixed in rightmost position (5/5) + full: 7 // swing in full range (seems to be same as full) } }, // controls the swing mode of the vertical air blades @@ -118,7 +126,9 @@ module.exports = { code: 'Quiet', value: { off: 0, - on: 1 + mode1: 1, + mode2: 2, + mode3: 3 } }, // sets fan speed to the maximum. Fan speed cannot be changed while active and only available in Dry and Cool mode From d24161afa64c6b046b26318b202e5b8929e87547 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:19:44 +0300 Subject: [PATCH 03/10] Update utils.js --- app/utils.js | 174 +++++++++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 88 deletions(-) diff --git a/app/utils.js b/app/utils.js index 9deb26b..411b5f7 100644 --- a/app/utils.js +++ b/app/utils.js @@ -84,9 +84,9 @@ module.exports = { }, num10THexStr: function(nums) { - let str = '' + let str = ''; for (let index = 0; index < nums.length; index++) { - let hex = this.num10THex(nums[index]) + let hex = this.num10THex(nums[index]); str = str + '' + (hex.length == 1 ? '0' + hex : hex); } return str; @@ -95,8 +95,8 @@ module.exports = { cmd20: function(value) { let arr = [] for (let i = 0; i < value.length; i++) { - let val = value.charCodeAt(i) - arr.push(val) + let val = value.charCodeAt(i); + arr.push(val); } return arr; }, @@ -224,7 +224,7 @@ module.exports = { wdNumberMode: bit[4] + '' + bit[5], sleep: bit[6], eco: bit[7], - } + }; }, parseData789: function(byte7, byte8, byte9) { @@ -397,17 +397,17 @@ module.exports = { } if (val === '010') { - nowCmd = this.cmd03(nowCmd, 1, true) - nowCmd = this.cmd06(nowCmd, 0, true) - nowCmd = this.cmd02(nowCmd, 0, true) + nowCmd = this.cmd03(nowCmd, 1, true); + nowCmd = this.cmd06(nowCmd, 0, true); + nowCmd = this.cmd02(nowCmd, 0, true); } else if (windLevel > -1) { if (windLevel <= 6) { - nowCmd = this.cmd03(nowCmd, windLevel, true) - nowCmd = this.cmd06(nowCmd, 0, true) - nowCmd = this.cmd02(nowCmd, 0, true) + nowCmd = this.cmd03(nowCmd, windLevel, true); + nowCmd = this.cmd06(nowCmd, 0, true); + nowCmd = this.cmd02(nowCmd, 0, true); if (windLevel == 6 && wujiNum > -1) { - nowCmd = this.cmd04(nowCmd, wujiNum, true) + nowCmd = this.cmd04(nowCmd, wujiNum, true); } } else if (windLevel == 7) { nowCmd = this.cmd03(nowCmd, 0, true); @@ -437,199 +437,197 @@ module.exports = { cmd06: function(nowCmd, val, modify = false) { let cmdPos = 4 + 4; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[1] = val // + bit[1] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); if (!modify) { - this.cmd02(nowCmd, 0, true) - this.cmd03(nowCmd, 0, true) + this.cmd02(nowCmd, 0, true); + this.cmd03(nowCmd, 0, true); } - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd08: function(nowCmd, val, modify = false) { let cmdPos = 4 + 4; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[2] = val // + bit[2] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd07: function(nowCmd, val, modify = false) { let cmdPos = 4 + 4; - let tempSt = this.parseData4(nowCmd[cmdPos]) - let val2 = '' + let tempSt = this.parseData4(nowCmd[cmdPos]); + let val2 = ''; if (!tempSt.temtyp) { - let _val = val >= 16 ? val - 16 : val - val2 = _.padStart(_val.toString(2), 5, '0') + let _val = val >= 16 ? val - 16 : val; + val2 = _.padStart(_val.toString(2), 5, '0'); } else { - val2 = array_['' + val] + val2 = array_['' + val]; } - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[3] = +val2[0] // - bit[4] = +val2[1] // - bit[5] = +val2[2] // - bit[6] = +val2[3] // - bit[7] = +val2[4] // + bit[3] = +val2[0]; // + bit[4] = +val2[1]; // + bit[5] = +val2[2]; // + bit[6] = +val2[3]; // + bit[7] = +val2[4]; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd09: function(nowCmd, val, modify = false) { - val = typeof val === 'number' ? _.padStart(val.toString(2), 4, '0') : val + val = typeof val === 'number' ? _.padStart(val.toString(2), 4, '0') : val; let cmdPos = 4 + 5; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[0] = +val[0] // - bit[1] = +val[1] // - bit[2] = +val[2] // - bit[3] = +val[3] // + bit[0] = +val[0]; // + bit[1] = +val[1]; // + bit[2] = +val[2]; // + bit[3] = +val[3]; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd10: function(nowCmd, val, modify = false) { - val = typeof val === 'number' ? _.padStart(val.toString(2), 4, '0') : val + val = typeof val === 'number' ? _.padStart(val.toString(2), 4, '0') : val; let cmdPos = 4 + 5; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[4] = +val[0] // - bit[5] = +val[1] // - bit[6] = +val[2] // - bit[7] = +val[3] // + bit[4] = +val[0]; // + bit[5] = +val[1]; // + bit[6] = +val[2]; // + bit[7] = +val[3]; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd11: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[0] = val // - nowCmd[cmdPos] = this.byteTohex(bit.join('')) + bit[0] = val; // + nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd12: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[1] = val // + bit[1] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd13: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[2] = val // + bit[2] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd14: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; let byte = nowCmd[cmdPos] let bit = this.intToBit(byte); - bit[3] = val // + bit[3] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd15: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[4] = +val[0] // - bit[5] = +val[1] // + bit[4] = +val[0]; // + bit[5] = +val[1]; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); - return !modify ? cmd(nowCmd) : nowCmd + return !modify ? cmd(nowCmd) : nowCmd; }, cmd16: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[6] = val // + bit[6] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); if (val) { - this.cmd17(nowCmd, 0, true) + this.cmd17(nowCmd, 0, true); } - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd17: function(nowCmd, val, modify = false) { let cmdPos = 4 + 6; - let byte = nowCmd[cmdPos] + let byte = nowCmd[cmdPos]; let bit = this.intToBit(byte); - bit[7] = val // + bit[7] = val; // nowCmd[cmdPos] = this.byteTohex(bit.join('')); if (val) { - this.cmd16(nowCmd, 0, true) - this.cmd03(nowCmd, 0, true) + this.cmd16(nowCmd, 0, true); + this.cmd03(nowCmd, 0, true); } - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmd18: function(nowCmd, bootEnabled, bootTime, shutEnabled, shutTime, modify = false) { - let _bootTime = bootTime.split(':') - let booSec = parseInt(_bootTime[0]) * 60 + parseInt(_bootTime[1]) - let _shutTime = shutTime.split(':') - let shutSec = parseInt(_shutTime[0]) * 60 + parseInt(_shutTime[1]) - let bootStr = _.padStart(booSec.toString(2), 11, '0') - let shutStr = _.padStart(shutSec.toString(2), 11, '0') + let _bootTime = bootTime.split(':'); + let booSec = parseInt(_bootTime[0]) * 60 + parseInt(_bootTime[1]); + let _shutTime = shutTime.split(':'); + let shutSec = parseInt(_shutTime[0]) * 60 + parseInt(_shutTime[1]); + let bootStr = _.padStart(booSec.toString(2), 11, '0'); + let shutStr = _.padStart(shutSec.toString(2), 11, '0'); nowCmd[4 + 7] = this.byteTohex('' + (shutEnabled ? 1 : 0) + shutStr.substring(0, 3) + (bootEnabled ? 1 : 0) + bootStr.substring(0, 3)); nowCmd[4 + 8] = this.byteTohex(bootStr.substring(3, 11)); nowCmd[4 + 9] = this.byteTohex(shutStr.substring(3, 11)); - return !modify ? this.cmd(nowCmd) : nowCmd + return !modify ? this.cmd(nowCmd) : nowCmd; }, cmdQuery: function() { - let nowCmd = [0xAA, 0xAA, 0x12, 0xA0, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A] - return nowCmd + let nowCmd = [0xAA, 0xAA, 0x12, 0xA0, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A]; + return nowCmd; }, cmd19: function(endpoint) { - let arr = this.cmd20(endpoint) + let arr = this.cmd20(endpoint); - let nowCmd = [0xAC, 0xAC, 0x00, 0xB4, ...arr, 0x00, 0x00] - nowCmd[2] = nowCmd.length - 3 + let nowCmd = [0xAC, 0xAC, 0x00, 0xB4, ...arr, 0x00, 0x00]; + nowCmd[2] = nowCmd.length - 3; var code = 0x00; for (var i = 0; i < nowCmd.length - 1; i++) { - code += nowCmd[i] + code += nowCmd[i]; } nowCmd[nowCmd.length - 1] = code & 0xFF; // - return nowCmd - }, - - + return nowCmd; + } } From f42984a6ed583945d6a95170d65132cba4fbb427 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:21:58 +0300 Subject: [PATCH 04/10] Update config.json --- config.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 622a894..09615da 100644 --- a/config.json +++ b/config.json @@ -1,22 +1,29 @@ { "name": "Cooper&Huntrer HVAC MQTT bridge", - "version": "1.0.1", + "version": "1.1.2.1", "slug": "cooper_hunter_hvac_mqtt_bridge", "description": "Hass.io addon for controlling Cooper&Huntrer air conditioners using the MQTT climate platform", "startup": "application", "boot": "auto", + "arch": [ "aarch64", "amd64", "armhf", "armv7", "i386" ], + "hassio_api": true, + "hassio_role": "default", "options": { "hvac_host": "192.168.107.49", "mqtt": { - "broker_url": "mqtt://192.168.111.100", - "topic_prefix": "home/greehvac" + "broker_url": "mqtt://localhost", + "topic_prefix": "home/greehvac", + "username": "", + "password": "" } }, "schema": { "hvac_host": "str", "mqtt": { "broker_url": "str", - "topic_prefix": "str" + "topic_prefix": "str", + "username": "str?", + "password": "str?" } } } From 670dd4b13bf18b202ed0bd53bd36632e1778a0d4 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:22:37 +0300 Subject: [PATCH 05/10] Update Dockerfile --- Dockerfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index a050dde..ef41f9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM homeassistant/amd64-base:latest +ARG BUILD_FROM +FROM $BUILD_FROM ENV LANG C.UTF-8 @@ -6,12 +7,8 @@ RUN apk add --no-cache jq nodejs nodejs-npm && \ npm set unsafe-perm true # Copy data for add-on -RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN chmod +x run.sh -RUN npm install - -#CMD [ "./run.sh" ] -ENTRYPOINT ./run.sh +CMD [ "./run.sh" ] From 215248d126c5d5bd0145b2babb8de144cc0024f3 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:22:53 +0300 Subject: [PATCH 06/10] Update index.js --- index.js | 192 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 122 insertions(+), 70 deletions(-) diff --git a/index.js b/index.js index 582aaa2..aacb8c5 100644 --- a/index.js +++ b/index.js @@ -1,60 +1,77 @@ #!/usr/bin/env node - 'use strict'; const mqtt = require('mqtt'); const commands = require('./app/commandEnums'); const argv = require('minimist')(process.argv.slice(2), { - string: ['hvac-host', 'mqtt-broker-url', 'mqtt-topic-prefix'], - '--': true, + string: [ 'hvac-host', 'mqtt-broker-url', 'mqtt-topic-prefix', 'mqtt-username', 'mqtt-password'], + '--': true, }); /** * Helper: get property key for value * @param {*} value */ -Object.prototype.getKeyByValue = function(value) { - for (var prop in this) { - if (this.hasOwnProperty(prop)) { - if (this[prop] === value) - return prop; - } - } -} +Object.prototype.getKeyByValue = function( value ) { + for( var prop in this ) { + if( this.hasOwnProperty( prop ) ) { + if( this[ prop ] === value ) + return prop; + } + } +}; /** * Connect to device */ const mqttTopicPrefix = argv['mqtt-topic-prefix']; const deviceOptions = { - host: argv['hvac-host'], - onStatus: (deviceModel) => { - client.publish(mqttTopicPrefix + '/temperature_in/get', deviceModel.props[commands.temperature.code].toString()); - client.publish(mqttTopicPrefix + '/temperature/get', deviceModel.props["wdNumber"].toString()); - client.publish(mqttTopicPrefix + '/fanspeed/get', commands.fanSpeed.value.getKeyByValue(deviceModel.props[commands.fanSpeed.code]).toString()); - // client.publish(mqttTopicPrefix + '/swingvert/get', commands.swingVert.value.getKeyByValue(deviceModel.props[commands.swingVert.code]).toString()); - client.publish(mqttTopicPrefix + '/power/get', commands.power.value.getKeyByValue(deviceModel.props[commands.power.code]).toString()); + host: argv['hvac-host'], + onStatus: (deviceModel) => { + client.publish(mqttTopicPrefix + '/temperature/get', deviceModel.props[commands.temperature.code].toString()); + client.publish(mqttTopicPrefix + '/fanspeed/get', commands.fanSpeed.value.getKeyByValue(deviceModel.props[commands.fanSpeed.code]).toString()); + client.publish(mqttTopicPrefix + '/swinghor/get', commands.swingHor.value.getKeyByValue(deviceModel.props[commands.swingHor.code]).toString()); + client.publish(mqttTopicPrefix + '/swingvert/get', commands.swingVert.value.getKeyByValue(deviceModel.props[commands.swingVert.code]).toString()); + client.publish(mqttTopicPrefix + '/power/get', commands.power.value.getKeyByValue(deviceModel.props[commands.power.code]).toString()); + client.publish(mqttTopicPrefix + '/health/get', commands.health.value.getKeyByValue(deviceModel.props[commands.health.code]).toString()); + client.publish(mqttTopicPrefix + '/powersave/get', commands.energySave.value.getKeyByValue(deviceModel.props[commands.energySave.code]).toString()); + client.publish(mqttTopicPrefix + '/lights/get', commands.lights.value.getKeyByValue(deviceModel.props[commands.lights.code]).toString()); + client.publish(mqttTopicPrefix + '/quiet/get', commands.quiet.value.getKeyByValue(deviceModel.props[commands.quiet.code]).toString()); + client.publish(mqttTopicPrefix + '/blow/get', commands.blow.value.getKeyByValue(deviceModel.props[commands.blow.code]).toString()); + client.publish(mqttTopicPrefix + '/air/get', commands.air.value.getKeyByValue(deviceModel.props[commands.air.code]).toString()); + client.publish(mqttTopicPrefix + '/sleep/get', commands.sleep.value.getKeyByValue(deviceModel.props[commands.sleep.code]).toString()); + client.publish(mqttTopicPrefix + '/turbo/get', commands.turbo.value.getKeyByValue(deviceModel.props[commands.turbo.code]).toString()); - /* - * Handle "none" mode status - * Hass.io MQTT climate control doesn't support power commands through GUI, - * so an additional pseudo mode is added - */ - client.publish(mqttTopicPrefix + '/mode/get', (deviceModel.props[commands.power.code] === commands.power.value.on) ? - commands.mode.value.getKeyByValue(deviceModel.props[commands.mode.code]).toString() : 'none' - ); - }, - onUpdate: (deviceModel) => { - console.log('[UDP] Status updated on %s', deviceModel.name); - }, - onConnected: (deviceModel) => { - console.log("--in option.onConnected"); - client.subscribe(mqttTopicPrefix + '/temperature/set'); - client.subscribe(mqttTopicPrefix + '/mode/set'); - client.subscribe(mqttTopicPrefix + '/fanspeed/set'); - client.subscribe(mqttTopicPrefix + '/swingvert/set'); - client.subscribe(mqttTopicPrefix + '/power/set'); - } + /** + * Handle "off" mode status + * Hass.io MQTT climate control doesn't support power commands through GUI, + * so an additional pseudo mode is added + */ + client.publish(mqttTopicPrefix + '/mode/get', + (deviceModel.props[commands.power.code] === commands.power.value.on) + ? commands.mode.value.getKeyByValue(deviceModel.props[commands.mode.code]).toString() + : 'off' + ); + }, + onUpdate: (deviceModel) => { + console.log('[UDP] Status updated on %s', deviceModel.name); + }, + onConnected: (deviceModel) => { + client.subscribe(mqttTopicPrefix + '/temperature/set'); + client.subscribe(mqttTopicPrefix + '/mode/set'); + client.subscribe(mqttTopicPrefix + '/fanspeed/set'); + client.subscribe(mqttTopicPrefix + '/swinghor/set'); + client.subscribe(mqttTopicPrefix + '/swingvert/set'); + client.subscribe(mqttTopicPrefix + '/power/set'); + client.subscribe(mqttTopicPrefix + '/health/set'); + client.subscribe(mqttTopicPrefix + '/powersave/set'); + client.subscribe(mqttTopicPrefix + '/lights/set'); + client.subscribe(mqttTopicPrefix + '/quiet/set'); + client.subscribe(mqttTopicPrefix + '/blow/set'); + client.subscribe(mqttTopicPrefix + '/air/set'); + client.subscribe(mqttTopicPrefix + '/sleep/set'); + client.subscribe(mqttTopicPrefix + '/turbo/set'); + } }; let hvac; @@ -62,41 +79,76 @@ let hvac; /** * Connect to MQTT broker */ -const client = mqtt.connect(argv['mqtt-broker-url']); + +const mqttOptions = {}; +let authLog = ''; +if (argv['mqtt-username'] && argv['mqtt-password']) { + mqttOptions.username = argv['mqtt-username']; + mqttOptions.password = argv['mqtt-password']; + authLog = ' as "' + mqttOptions.username + '"'; +} +const client = mqtt.connect(argv['mqtt-broker-url'], mqttOptions); client.on('connect', () => { - console.log('[MQTT] Connected to broker on ' + argv['mqtt-broker-url']); - hvac = require('./app/deviceFactory').connect(deviceOptions); + console.log('[MQTT] Connected to broker on ' + argv['mqtt-broker-url'] + authLog); + hvac = require('./app/deviceFactory').connect(deviceOptions); }); client.on('message', (topic, message) => { - message = message.toString(); - console.log('[MQTT] Message "%s" received for %s', message, topic); + message = message.toString(); + console.log('[MQTT] Message "%s" received for %s', message, topic); - switch (topic) { - case mqttTopicPrefix + '/temperature/set': - hvac.setTemp(parseInt(message)); - return; - case mqttTopicPrefix + '/mode/set': - if (message === 'none' || message == "off") { - // Power off when "none" mode - hvac.setPower(commands.power.value.off); // - } else { - // Power on and set mode if other than 'none' - if (hvac.device.props[commands.power.code] === commands.power.value.off) { - hvac.setPower(commands.power.value.on); // - } - hvac.setMode(commands.mode.value[message]); - } - return; - case mqttTopicPrefix + '/fanspeed/set': - hvac.setFanSpeed(commands.fanSpeed.value[message]); - return; - case mqttTopicPrefix + '/swingvert/set': - hvac.setSwingVert(commands.swingVert.value[message]); - return; - case mqttTopicPrefix + '/power/set': - hvac.setPower(commands.power.value[message]); - return; - } - console.log('[MQTT] No handler for topic %s', topic); + switch (topic) { + case mqttTopicPrefix + '/temperature/set': + hvac.setTemp(parseInt(message)); + return; + case mqttTopicPrefix + '/mode/set': + if (message === 'off') { + // Power off when "off" mode + hvac.setPower(commands.power.value.off); + } else { + // Power on and set mode if other than 'off' + if (hvac.device.props[commands.power.code] === commands.power.value.off) { + hvac.setPower(commands.power.value.on); + } + hvac.setMode(commands.mode.value[message]); + } + return; + case mqttTopicPrefix + '/fanspeed/set': + hvac.setFanSpeed(commands.fanSpeed.value[message]); + return; + case mqttTopicPrefix + '/swinghor/set': + hvac.setSwingHor(commands.swingHor.value[message]); + return; + case mqttTopicPrefix + '/swingvert/set': + hvac.setSwingVert(commands.swingVert.value[message]); + return; + case mqttTopicPrefix + '/power/set': + hvac.setPower(parseInt(message)); + return; + case mqttTopicPrefix + '/health/set': + hvac.setHealthMode(parseInt(message)); + return; + case mqttTopicPrefix + '/powersave/set': + hvac.setPowerSave(parseInt(message)); + return; + case mqttTopicPrefix + '/lights/set': + hvac.setLights(parseInt(message)); + return; + case mqttTopicPrefix + '/quiet/set': + hvac.setQuietMode(parseInt(message)); + return; + case mqttTopicPrefix + '/blow/set': + hvac.setBlow(parseInt(message)); + return; + case mqttTopicPrefix + '/air/set': + hvac.setAir(parseInt(message)); + return; + case mqttTopicPrefix + '/sleep/set': + hvac.setSleepMode(parseInt(message)); + return; + case mqttTopicPrefix + '/turbo/set': + hvac.setTurbo(parseInt(message)); + return; + } + console.log('[MQTT] No handler for topic %s', topic); }); From 6894e73c37ecc411f6d7a33919f90b1f9798982a Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:24:03 +0300 Subject: [PATCH 07/10] Update package.json --- package.json | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ed764a0..2d82f2a 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,25 @@ { - "name": "cooper_hunter-hvac-mqtt-bridge", - "version": "1.0.4", + "name": "gree-hvac-mqtt-bridge", + "version": "1.1.2.1", "description": "MQTT Bridge for controlling Cooper&Hunter smart air conditioners", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], - "author": "T-REX-XP", + "author": "Arthur Krupa ", "license": "MIT", "dependencies": { - "crypto-js": "^3.1.9-1", - "dgram": "^1.0.1", - "lodash": "^4.17.10", "minimist": "^1.2.0", - "mqtt": "^2.15.0", - "net": "^1.0.2" + "mqtt": "^2.15.0" }, "engines": { "node": ">=8.11.0" }, "os": [ "darwin", - "linux" + "linux", + "win32" ], "private": true } From a1ea50548acf8dbed1bcb9b3966fe3344279cb72 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:24:34 +0300 Subject: [PATCH 08/10] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 432c627..415dee6 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ climate: ## Changelog +[1.1.2.1] - Updated sources from orig + [1.0.3] - Fixed power off command [1.0.2] - Fixed modes and WindModes From 1844b7682149bb16956c548bf4c781b713cdbc0e Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:26:16 +0300 Subject: [PATCH 09/10] Update repository.json --- repository.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repository.json b/repository.json index 7b80208..173bf20 100644 --- a/repository.json +++ b/repository.json @@ -1,5 +1,5 @@ { "name": "Gree HVAC MQTT bridge", - "url": "https://github.com/arthurkrupa/gree-hvac-mqtt-bridge", - "maintainer": "Arthur Krupa " -} \ No newline at end of file + "url": "https://github.com/T-REX-XP/cooper_hunter-hvac-mqtt-bridge", + "maintainer": "T-REX-XP <4560084+T-REX-XP@users.noreply.github.com>" +} From 8938db15487e4383769090260d81a4b7bb356e69 Mon Sep 17 00:00:00 2001 From: T-REX-XP <4560084+T-REX-XP@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:26:43 +0300 Subject: [PATCH 10/10] Update run.sh --- run.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/run.sh b/run.sh index aa2aefe..fffebe4 100755 --- a/run.sh +++ b/run.sh @@ -1,13 +1,18 @@ #!/bin/sh set -e -CONFIG_PATH=./data/options.json +CONFIG_PATH=/data/options.json HVAC_HOST=$(jq --raw-output ".hvac_host" $CONFIG_PATH) MQTT_BROKER_URL=$(jq --raw-output ".mqtt.broker_url" $CONFIG_PATH) MQTT_TOPIC_PREFIX=$(jq --raw-output ".mqtt.topic_prefix" $CONFIG_PATH) +MQTT_USERNAME=$(jq --raw-output ".mqtt.username" $CONFIG_PATH) +MQTT_PASSWORD=$(jq --raw-output ".mqtt.password" $CONFIG_PATH) +npm install node index.js \ --hvac-host="${HVAC_HOST}" \ --mqtt-broker-url="${MQTT_BROKER_URL}" \ - --mqtt-topic-prefix="${MQTT_TOPIC_PREFIX}" + --mqtt-topic-prefix="${MQTT_TOPIC_PREFIX}" \ + --mqtt-username="${MQTT_USERNAME}" \ + --mqtt-password="${MQTT_PASSWORD}"