├── .gitignore ├── .npmignore ├── AUTHOR.md ├── DeviceCommands.md ├── LICENSE ├── README.md ├── bin ├── find-rfxcom.js └── set-protocols.js ├── changelog.md ├── index.js ├── lib ├── activLink.js ├── asyncConfig.js ├── asyncData.js ├── blinds1.js ├── blinds2.js ├── camera1.js ├── chime1.js ├── curtain1.js ├── defines.js ├── edisio.js ├── fan.js ├── funkbus.js ├── homeConfort.js ├── hunterFan.js ├── index.js ├── lighting1.js ├── lighting2.js ├── lighting3.js ├── lighting4.js ├── lighting5.js ├── lighting6.js ├── radiator1.js ├── rawtx.js ├── remote.js ├── rfxcom.js ├── rfy.js ├── security1.js ├── thermostat1.js ├── thermostat2.js ├── thermostat3.js ├── thermostat4.js ├── thermostat5.js └── transmitter.js ├── package-lock.json ├── package.json ├── spec └── support │ └── jasmine.json └── test ├── asyncConfig.spec.js ├── asyncData.spec.js ├── blinds1.spec.js ├── blinds2.spec.js ├── camera1.spec.js ├── chime1.spec.js ├── curtain1.spec.js ├── edisio.spec.js ├── fan.spec.js ├── funkbus.spec.js ├── helper.js ├── homeConfort.spec.js ├── hunterFan.spec.js ├── lighting1.spec.js ├── lighting2.spec.js ├── lighting3.spec.js ├── lighting4.spec.js ├── lighting5.spec.js ├── lighting6.spec.js ├── matchers.js ├── radiator1.spec.js ├── rawtx.spec.js ├── remote.spec.js ├── rfxcom.spec.js ├── rfy.spec.js ├── security1.spec.js ├── thermostat1.spec.js ├── thermostat2.spec.js ├── thermostat3.spec.js ├── thermostat4.spec.js ├── thermostat5.spec.js └── transmitter.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | development_docs 3 | .idea 4 | debug 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | development_docs/ 3 | test/ 4 | .idea/ 5 | debug/ -------------------------------------------------------------------------------- /AUTHOR.md: -------------------------------------------------------------------------------- 1 | Kevin McDermott [bigkevmcd](https://github.com/bigkevmcd) 2 | 3 | Stian Eikeland [stianeikeland](https://github.com/stianeikeland) 4 | 5 | Nicholas Humfrey [njh](https://github.com/njh) 6 | 7 | Ian Gregory [iangregory](https://github.com/iangregory) 8 | 9 | bwired-nl [bwired-nl](https://github.com/bwired-nl) 10 | 11 | Max Hadley [maxwellhadley](https://github.com/maxwellhadley) 12 | 13 | Michael Smith [emes](https://github.com/emes) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2017 Kevin McDermott. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /bin/find-rfxcom.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // Lists all RFXCOM devices found by serialport 5 | 6 | const 7 | rfxcom = require('../'); 8 | 9 | console.log("Scanning for RFXCOM devices..."); 10 | let devices = [], 11 | headerPrinted = false; 12 | rfxcom.RfxCom.list(function (err, ports) { 13 | if (err) { 14 | console.log("Scan failed - " + err.message); 15 | } else if (ports.length === 0) { 16 | console.log(" None found") 17 | } else { 18 | ports.forEach(port => { 19 | let device = {}; 20 | device.rfxtrx = new rfxcom.RfxCom(port.path); 21 | device.message = " " + port.path + (port.pnpId !== undefined ? " (" + port.pnpId + ")" : ""); 22 | device.rfxtrx.on("status", evt => { 23 | if (headerPrinted === false) { 24 | console.log("Devices found at:"); 25 | headerPrinted = true; 26 | } 27 | console.log(device.message + "\n " + evt.receiverType + " hardware version " + evt.hardwareVersion + 28 | ", firmware version " + evt.firmwareVersion + " " + evt.firmwareType); 29 | console.log(" Receiver type 0x" + evt.receiverTypeCode.toString(16)); 30 | console.log(" Enabled protocols: " + (evt.enabledProtocols).sort()); 31 | let disabledProtocols = []; 32 | if (typeof rfxcom.protocols[evt.receiverTypeCode] == "object") { 33 | for (let key in rfxcom.protocols[evt.receiverTypeCode]) { 34 | if (rfxcom.protocols[evt.receiverTypeCode].hasOwnProperty(key)) { 35 | if (evt.enabledProtocols.indexOf(key) < 0) { 36 | disabledProtocols.push(key); 37 | } 38 | } 39 | } 40 | }; 41 | console.log(" Disabled protocols: " + disabledProtocols.sort()); 42 | }); 43 | device.rfxtrx.on("connectfailed", () => { 44 | console.log(device.message + "\n Unable to open serial port, RFXCOM device may be in use!") 45 | }); 46 | device.rfxtrx.initialise(() => { 47 | device.rfxtrx.close()}); 48 | devices.push(device); 49 | }); 50 | } 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /bin/set-protocols.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // Command to set & optionally save the enabled receive protocols in an RFXCOM device 5 | 6 | const 7 | rfxcom = require("../"), 8 | usage = ("usage: npm run set-protocols -- --list device_port\n" + 9 | " npm run set-protocols -- --save device_port\n" + 10 | " npm run set-protocols -- [--enable protocol_list] [--disable protocol_list] [--save] device_port"), 11 | STATE = { 12 | INITIALISING: 0, 13 | INITIALISED: 1, 14 | SENDING_ENABLE: 2, 15 | WAIT_FOR_ENABLE_RESPONSE: 3, 16 | SENDING_RESTART: 4, 17 | WAIT_FOR_RESTART_RESPONSE: 5, 18 | SENDING_SAVE: 6, 19 | WAIT_FOR_SAVE_RESPONSE: 7 20 | }; 21 | 22 | 23 | let 24 | currentState = STATE.INITIALISING, 25 | saveProtocols = false, 26 | readingEnabledProtocols = false, 27 | protocolsToEnable = [], 28 | enabledProtocols = [], 29 | readingDisabledProtocols = false, 30 | protocolsToDisable = [], 31 | disabledProtocols = [], 32 | listProtocols = false, 33 | device = ""; 34 | 35 | const args = process.argv.slice(2); 36 | if (args.length < 2) { 37 | console.log("set-protocols: Missing parameters\n" + usage); 38 | process.exit(0); 39 | } else if (args.length === 2) { 40 | device = args[1]; 41 | if (args[0] === "--list" || args[0] === "-l") { 42 | listProtocols = true; 43 | } else if (args[0] === "--save" || args[0] === "-s") { 44 | saveProtocols = true; 45 | } else { 46 | console.log("set-protocols: Invalid use of switch '" + args[0] + "'\n" + usage); 47 | process.exit(0); 48 | } 49 | } else { 50 | let lastArgProcessed = -1; 51 | for (let i = 0; i < args.length; i++) { 52 | if (args[i] === "--save" || args[i] === "-s") { 53 | saveProtocols = true; 54 | readingEnabledProtocols = false; 55 | readingDisabledProtocols = false; 56 | lastArgProcessed = i; 57 | } else if (args[i] === "--enable" || args[i] === "-e") { 58 | readingEnabledProtocols = true; 59 | readingDisabledProtocols = false; 60 | lastArgProcessed = i; 61 | } else if (args[i] === "--disable" || args[i] === "-d") { 62 | readingEnabledProtocols = false; 63 | readingDisabledProtocols = true; 64 | lastArgProcessed = i; 65 | } else if (args[i] === "--list" || args[i] === "-l") { 66 | console.log("set-protocols: Invalid use of '" + args[i] + "' switch\n" + usage); 67 | process.exit(0); 68 | } else if (readingEnabledProtocols) { 69 | protocolsToEnable = protocolsToEnable.concat(args[i].split(",").filter(str => {return str !== ""})); 70 | readingEnabledProtocols = false; 71 | lastArgProcessed = i; 72 | } else if (readingDisabledProtocols) { 73 | protocolsToDisable = protocolsToDisable.concat(args[i].split(",").filter(str => {return str !== ""})); 74 | readingDisabledProtocols = false; 75 | lastArgProcessed = i; 76 | } 77 | } 78 | if (lastArgProcessed === args.length - 1) { 79 | console.log("set-protocols: Missing device port\n" + usage); 80 | process.exit(0); 81 | } else { 82 | device = args[args.length-1]; 83 | } 84 | } 85 | 86 | // Create the RFXCOM object and install its event handlers 87 | console.log("Trying to open RFXCOM device on " + device + "..."); 88 | const rfxtrx = new rfxcom.RfxCom(device); 89 | let receiverTypeCode = -1; 90 | 91 | rfxtrx.on("connecting", () => {console.log("Serial port open, initialising RFXCOM device...")}); 92 | 93 | rfxtrx.on("connectfailed", () => { 94 | console.log(device.message + "\n Unable to open serial port, RFXCOM device may be in use!"); 95 | process.exit(0); 96 | }); 97 | 98 | rfxtrx.on("receiverstarted", () => { 99 | switch (currentState) { 100 | case STATE.INITIALISED: 101 | if (protocolsToEnable.length > 0 || protocolsToDisable.length > 0) { 102 | protocolsToEnable.sort(); 103 | protocolsToDisable.sort(); 104 | // Check these protocols actually exist 105 | const unrecognisedProtocols = 106 | protocolsToDisable.filter((entry) => {return !rfxcom.protocols[receiverTypeCode].hasOwnProperty(entry)}). 107 | concat(protocolsToEnable.filter((entry) => {return !rfxcom.protocols[receiverTypeCode].hasOwnProperty(entry)})); 108 | if (unrecognisedProtocols.length === 1) { 109 | console.log("Protocol not supported by receiver type 0x" + receiverTypeCode.toString(16) + ": " + unrecognisedProtocols[0]); 110 | rfxtrx.close(); 111 | process.exit(0); 112 | } else if (unrecognisedProtocols.length > 1){ 113 | console.log("Protocols not supported by receiver type 0x" + receiverTypeCode.toString(16) + ": " + unrecognisedProtocols.sort()); 114 | rfxtrx.close(); 115 | process.exit(0); 116 | } else { 117 | process.nextTick(sendEnable); 118 | currentState = STATE.SENDING_ENABLE; 119 | }; 120 | } else if (listProtocols) { 121 | console.log("Enabled protocols: " + enabledProtocols); 122 | console.log("Disabled protocols: " + disabledProtocols); 123 | process.nextTick(() => {rfxtrx.close()}); 124 | } else if (saveProtocols) { 125 | process.nextTick(sendSave); 126 | currentState = STATE.SENDING_SAVE; 127 | } 128 | break; 129 | 130 | case STATE.WAIT_FOR_RESTART_RESPONSE: 131 | if (saveProtocols) { 132 | process.nextTick(sendSave); 133 | currentState = STATE.SENDING_SAVE; 134 | } else { 135 | process.nextTick(() => {rfxtrx.close()}); 136 | } 137 | break; 138 | 139 | default: 140 | break; 141 | } 142 | }); 143 | 144 | rfxtrx.on("status", evt => { 145 | switch (currentState) { 146 | case STATE.INITIALISING: 147 | console.log(evt.receiverType + " hardware version " + evt.hardwareVersion + 148 | ", firmware version " + evt.firmwareVersion + " " + evt.firmwareType); 149 | receiverTypeCode = evt.receiverTypeCode; 150 | console.log("Receiver type 0x" + receiverTypeCode.toString(16)); 151 | enabledProtocols = evt.enabledProtocols.sort(); 152 | if (typeof rfxcom.protocols[receiverTypeCode] == "object") { 153 | for (let key in rfxcom.protocols[receiverTypeCode]) { 154 | if (rfxcom.protocols[receiverTypeCode].hasOwnProperty(key)) { 155 | if (enabledProtocols.indexOf(key) < 0) { 156 | disabledProtocols.push(key); 157 | } 158 | } 159 | } 160 | }; 161 | disabledProtocols.sort(); 162 | if (listProtocols === false) { 163 | console.log("Currently enabled: " + enabledProtocols); 164 | } 165 | currentState = STATE.INITIALISED; 166 | break; 167 | 168 | case STATE.WAIT_FOR_ENABLE_RESPONSE: 169 | // note we must wait for the start receiver command response as well, before its OK to close 170 | currentState = STATE.WAIT_FOR_RESTART_RESPONSE; 171 | break; 172 | 173 | case STATE.WAIT_FOR_SAVE_RESPONSE: 174 | console.log("Saved to non-volatile memory"); 175 | process.nextTick(() => {rfxtrx.close()}); 176 | break; 177 | 178 | default: 179 | break; 180 | } 181 | }); 182 | 183 | const sendEnable = function () { 184 | // Remove duplicate entries from each list of protocols 185 | protocolsToEnable = protocolsToEnable.filter((entry, index, entries) => {return index === 0 || entry !== entries[index-1]}); 186 | protocolsToDisable = protocolsToDisable.filter((entry, index, entries) => {return index === 0 || entry !== entries[index-1]}); 187 | // Remove entries which appear in both lists 188 | const validEnables = protocolsToEnable.map((entry) => {return protocolsToDisable.indexOf(entry) < 0}); 189 | const validDisables = protocolsToDisable.map((entry) => {return protocolsToEnable.indexOf(entry) < 0}); 190 | protocolsToEnable = protocolsToEnable.filter((entry, index) => {return validEnables[index]}); 191 | protocolsToDisable = protocolsToDisable.filter((entry, index) => {return validDisables[index]}); 192 | // Remove those entries in the enabledProtocols list which appear in the protocolsToDisable list... 193 | protocolsToEnable = enabledProtocols.filter((entry) => {return protocolsToDisable.indexOf(entry) < 0}). 194 | // ...concatenate the protocolsToEnable list... 195 | concat(protocolsToEnable). 196 | // ...sort and remove duplicates 197 | sort(). 198 | filter((entry, index, entries) => {return index === 0 || entry !== entries[index-1]}); 199 | console.log(" Change to: " + protocolsToEnable); 200 | 201 | 202 | // Send the command 203 | rfxtrx.enableRFXProtocols(protocolsToEnable.map(entry => rfxcom.protocols[receiverTypeCode][entry])); 204 | currentState = STATE.WAIT_FOR_ENABLE_RESPONSE; 205 | }; 206 | 207 | const sendSave = function () { 208 | rfxtrx.saveRFXProtocols(); 209 | currentState = STATE.WAIT_FOR_SAVE_RESPONSE; 210 | }; 211 | 212 | // Start the rfxtrx device to kick everything off 213 | rfxtrx.initialise(); 214 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /lib/activLink.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Honeywell ActivLink doorbells & chimes 9 | */ 10 | class ActivLink extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "activLink"; 14 | this.packetNumber = 0x1d; 15 | } 16 | 17 | /* 18 | * Splits the device id and returns the components. 19 | * Throws an Error if the format is invalid. 20 | */ 21 | _splitDeviceId(deviceId) { 22 | let id; 23 | const parts = Transmitter.splitAtSlashes(deviceId); 24 | if (parts.length !== 1) { 25 | throw new Error("Invalid deviceId format"); 26 | } else { 27 | id = RfxCom.stringToBytes(parts[0], 3); 28 | if ((id.value <= 0) || id.value > 0x0fffff) { 29 | Transmitter.addressError(id); 30 | } 31 | } 32 | return { 33 | idBytes: id.bytes, 34 | }; 35 | } 36 | 37 | _sendCommand(deviceId, alert, knock, callback) { 38 | const device = this._splitDeviceId(deviceId); 39 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], alert, knock, 0, 0]; 40 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 41 | }; 42 | 43 | chime(deviceId, knock, alert, callback) { 44 | if (callback === undefined && typeof(alert) === "function") { 45 | callback = alert; 46 | alert = defines.HONEYWELL_NORMAL; 47 | } 48 | if (this.isSubtype("ACTIV_LINK_CHIME") && (knock < 0 || knock > 1)) { 49 | throw new Error("Invalid knock: value must be 0 or 1"); 50 | } else if (this.isSubtype("ACTIV_LINK_CHIME") && (alert < 0 || alert > 4)) { 51 | throw new Error("Invalid alert: value must be in range 0-4"); 52 | } 53 | return this._sendCommand(deviceId, alert, knock, callback); 54 | } 55 | 56 | } 57 | 58 | module.exports = ActivLink; 59 | -------------------------------------------------------------------------------- /lib/asyncConfig.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for setting the Async port configuration (RFXtrx433XL only) 9 | */ 10 | class AsyncConfig extends Transmitter { 11 | constructor (rfxcom, subtype, options) { 12 | super (rfxcom, subtype, options); 13 | this.packetType = "asyncconfig"; 14 | this.packetNumber = 0x61; 15 | } 16 | 17 | } 18 | 19 | module.exports = AsyncConfig; 20 | -------------------------------------------------------------------------------- /lib/asyncData.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for sending data to the Async port (RFXtrx433XL only) 9 | */ 10 | class AsyncData extends Transmitter { 11 | constructor (rfxcom, subtype, options) { 12 | super (rfxcom, subtype, options); 13 | this.packetType = "asyncdata"; 14 | this.packetNumber = 0x62; 15 | } 16 | 17 | } 18 | 19 | module.exports = AsyncData; 20 | -------------------------------------------------------------------------------- /lib/blinds1.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling multiple types of autmated blinds 9 | */ 10 | class Blinds1 extends Transmitter { 11 | constructor (rfxcom, subtype, options) { 12 | super (rfxcom, subtype, options); 13 | this.packetType = "blinds1"; 14 | this.packetNumber = 0x19; 15 | } 16 | 17 | /* 18 | * Splits the device id and returns the components. 19 | * Throws an Error if the format is invalid. 20 | * Least significant nibble placed in the top 4 bits of the last id.byte for BLINDS_T6, BLINDS_T7 & BLINDS_T9 21 | */ 22 | _splitDeviceId(deviceId) { 23 | let id, unitCode; 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (this.isSubtype(["BLINDS_T2", "BLINDS_T4", "BLINDS_T5", "BLINDS_T10", "BLINDS_T18"])) { 26 | unitCode = 0; 27 | } else if (parts.length === 2) { 28 | unitCode = parseInt(parts[1]); 29 | if (isNaN(unitCode) || unitCode < 0) { 30 | throw new Error("Invalid unit code " + parts[1]); 31 | } else if (this.isSubtype(["BLINDS_T0", "BLINDS_T1", "BLINDS_T6", "BLINDS_T7", "BLINDS_T19"])) { 32 | if (unitCode > 15) { 33 | throw new Error("Invalid unit code " + parts[1]); 34 | } 35 | } else if (this.isSubtype(["BLINDS_T3", "BLINDS_T14"])) { 36 | if (unitCode === 0) { 37 | unitCode = 0x10; 38 | } else if (unitCode > 16) { 39 | throw new Error("Invalid unit code " + parts[1]); 40 | } else { 41 | unitCode = unitCode - 1; 42 | } 43 | } else if (this.isSubtype("BLINDS_T8")) { 44 | if (unitCode === 0) { 45 | throw new Error("Subtype doesn't support group commands"); 46 | } else if (unitCode > 6) { 47 | throw new Error("Invalid unit code " + parts[1]); 48 | } 49 | unitCode = unitCode - 1; 50 | } else if (this.isSubtype(["BLINDS_T9", "BLINDS_T11", "BLINDS_T16"])) { 51 | if (unitCode > 6) { 52 | throw new Error("Invalid unit code " + parts[1]); 53 | } 54 | } else if (this.isSubtype(["BLINDS_T12", "BLINDS_T15"])) { 55 | if (unitCode === 0) { 56 | unitCode = 0x0f; 57 | } else if (unitCode > 15) { 58 | throw new Error("Invalid unit code " + parts[1]); 59 | } else { 60 | unitCode = unitCode - 1; 61 | } 62 | } else if (this.isSubtype("BLINDS_T13")) { 63 | if (unitCode > 99) { 64 | throw new Error("Invalid unit code " + parts[1]); 65 | } 66 | } else if (this.isSubtype("BLINDS_T17")) { 67 | if (unitCode > 5) { 68 | throw new Error("Invalid unit code " + parts[1]); 69 | } 70 | } else if (this.isSubtype("BLINDS_T20")) { 71 | if (unitCode === 0) { 72 | throw new Error("Subtype doesn't support group commands"); 73 | } else if (unitCode > 9) { 74 | throw new Error("Invalid unit code " + parts[1]); 75 | } 76 | } 77 | } else { 78 | throw new Error("Invalid deviceId format"); 79 | } 80 | if (this.isSubtype(["BLINDS_T6", "BLINDS_T7", "BLINDS_T9"])) { 81 | id = RfxCom.stringToBytes(parts[0] + "0", 4); // Nibble shift 82 | id.value = id.value/16; 83 | } else { 84 | id = RfxCom.stringToBytes(parts[0], 3); 85 | } 86 | if ((id.value === 0) || 87 | ((this.isSubtype(["BLINDS_T0", "BLINDS_T1", "BLINDS_T12", "BLINDS_T13", 88 | "BLINDS_T15", "BLINDS_T16", "BLINDS_T19", "BLINDS_T20"])) && id.value > 0xffff) || 89 | ((this.isSubtype(["BLINDS_T2", "BLINDS_T3", "BLINDS_T4", "BLINDS_T5", 90 | "BLINDS_T10", "BLINDS_T11", "BLINDS_T14", "BLINDS_T17"])) && id.value > 0xffffff) || 91 | ((this.isSubtype(["BLINDS_T6", "BLINDS_T7"])) && id.value > 0xfffffff) || 92 | ((this.isSubtype("BLINDS_T8")) && id.value > 0xfff) || 93 | ((this.isSubtype("BLINDS_T9")) && id.value > 0xfffff) || 94 | ((this.isSubtype("BLINDS_T18")) && (id.value < 0x103000 || id.value > 0x103fff))) { 95 | Transmitter.addressError(id); 96 | } 97 | return { 98 | idBytes: id.bytes, 99 | unitCode: unitCode 100 | }; 101 | } 102 | 103 | _sendCommand(deviceId, command, callback) { 104 | const device = this._splitDeviceId(deviceId); 105 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.unitCode, command, 0]; 106 | if (this.isSubtype("BLINDS_T6") || this.isSubtype("BLINDS_T7") || this.isSubtype("BLINDS_T9")) { 107 | buffer[3] = buffer[3] | device.idBytes[3]; 108 | } 109 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 110 | }; 111 | 112 | /* 113 | * Open deviceId. 114 | */ 115 | open(deviceId, callback) { 116 | if (this.isSubtype("BLINDS_T19")) { 117 | throw new Error("Device does not support open()"); 118 | } else { 119 | return this._sendCommand(deviceId, defines.BLINDS_OPEN, callback); 120 | } 121 | }; 122 | 123 | /* 124 | * Close deviceId. 125 | */ 126 | close(deviceId, direction, callback) { 127 | if (callback === undefined && typeof direction === "function") { 128 | callback = direction; 129 | direction = "CW" 130 | } 131 | let command = defines.BLINDS_CLOSE; 132 | if (this.isSubtype("BLINDS_T19")) { 133 | if (direction === "CW") { 134 | command = defines.BLINDS_T19_CLOSE_CW; 135 | } else if (direction === "CCW") { 136 | command = defines.BLINDS_T19_CLOSE_CCW; 137 | } else { 138 | throw new Error("Close direction must be 'CW' or 'CCW") 139 | } 140 | } 141 | return this._sendCommand(deviceId, command, callback); 142 | }; 143 | 144 | /* 145 | * Stop deviceId. 146 | */ 147 | stop(deviceId, callback) { 148 | if (this.isSubtype(["BLINDS_T13", "BLINDS_T19"])) { 149 | throw new Error("Device does not support stop()"); 150 | } else { 151 | return this._sendCommand(deviceId, defines.BLINDS_STOP, callback); 152 | } 153 | }; 154 | 155 | /* 156 | * Confirm button 157 | */ 158 | // noinspection JSUnusedGlobalSymbols 159 | confirm(deviceId, callback) { 160 | if (this.isSubtype(["BLINDS_T5", "BLINDS_T8"])) { 161 | throw new Error("Device does not support confirm()"); 162 | } else if (this.isSubtype("BLINDS_T13")) { 163 | return this._sendCommand(deviceId, defines.BLINDS_T13_CONFIRM, callback); 164 | } else if (this.isSubtype("BLINDS_T19")) { 165 | return this._sendCommand(deviceId, defines.BLINDS_T19_CONFIRM, callback); 166 | } else { 167 | return this._sendCommand(deviceId, defines.BLINDS_CONFIRM, callback); 168 | } 169 | }; 170 | 171 | /* 172 | * Set (upper) limit. 173 | */ 174 | setLimit(deviceId, callback) { 175 | if (this.isSubtype(["BLINDS_T0", "BLINDS_T1", "BLINDS_T4", "BLINDS_T9"])) { 176 | return this._sendCommand(deviceId, defines.BLINDS_SET_LIMIT, callback); 177 | } else { 178 | throw new Error("Device does not support setLimit()"); 179 | } 180 | }; 181 | 182 | /* 183 | * Set lower limit. 184 | */ 185 | setLowerLimit(deviceId, callback) { 186 | if (this.isSubtype(["BLINDS_T4", "BLINDS_T9"])) { 187 | return this._sendCommand(deviceId, defines.BLINDS_SET_LOWER_LIMIT, callback); 188 | } else { 189 | throw new Error("Device does not support setLowerLimit()"); 190 | } 191 | }; 192 | 193 | /* 194 | * Reverse direction. 195 | */ 196 | // noinspection JSUnusedGlobalSymbols 197 | reverse(deviceId, callback) { 198 | if (this.isSubtype("BLINDS_T4")) { 199 | return this._sendCommand(deviceId, defines.BLINDS_T4_REVERSE, callback); 200 | } else if (this.isSubtype(["BLINDS_T9", "BLINDS_T10"])) { 201 | return this._sendCommand(deviceId, defines.BLINDS_REVERSE, callback); 202 | } else if (this.isSubtype("BLINDS_T16")) { 203 | return this._sendCommand(deviceId, defines.BLINDS_T16_REVERSE, callback); 204 | } else { 205 | throw new Error("Device does not support reverse()"); 206 | } 207 | }; 208 | 209 | /* 210 | * Down deviceId. 211 | */ 212 | down(deviceId, callback) { 213 | if (this.isSubtype(["BLINDS_T5", "BLINDS_T8", "BLINDS_T10"])) { 214 | return this._sendCommand(deviceId, defines.BLINDS_DOWN, callback); 215 | } else { 216 | throw new Error("Device does not support down()"); 217 | } 218 | }; 219 | 220 | /* 221 | * Up deviceId. 222 | */ 223 | up(deviceId, callback) { 224 | if (this.isSubtype(["BLINDS_T5", "BLINDS_T8", "BLINDS_T10"])) { 225 | return this._sendCommand(deviceId, defines.BLINDS_UP, callback); 226 | } else { 227 | throw new Error("Device does not support up()"); 228 | } 229 | }; 230 | 231 | /* 232 | * Go to intermediate position 233 | */ 234 | intermediatePosition(deviceId, position, callback) { 235 | if (callback === undefined && typeof position === "function") { 236 | callback = position; 237 | if (this.isSubtype("BLINDS_T19")) { 238 | position = 90; 239 | } else { 240 | position = 2; 241 | } 242 | } else if (typeof position === "string") { 243 | position = parseInt(position); 244 | } else if (typeof position === "number") { 245 | position = Math.round(position); 246 | } 247 | if (this.isSubtype("BLINDS_T9")) { 248 | if ((typeof position != "number") || (isNaN(position)) || (position < 1) || (position > 3)) { 249 | throw new Error("Invalid position: value must be in range 1-3"); 250 | } else { 251 | return this._sendCommand(deviceId, 0x06 + position, callback); 252 | } 253 | } else if (this.isSubtype("BLINDS_T19")) { 254 | if (typeof position != "number" || isNaN(position) || 255 | !(position === 45 || position === 90 || position === 135)) { 256 | throw new Error("Invalid position: value must be 45, 90, or 135"); 257 | } else { 258 | return this._sendCommand(deviceId, 0x01 + position/45, callback); 259 | } 260 | } else if (this.isSubtype(["BLINDS_T6", "BLINDS_T18"])) { 261 | return this._sendCommand(deviceId, 0x04, callback); 262 | } else if (this.isSubtype("BLINDS_T17")) { 263 | return this._sendCommand(deviceId, 0x05, callback); 264 | } else { 265 | throw new Error("Device does not support intermediatePosition()"); 266 | } 267 | } 268 | 269 | /* 270 | * T13 (screenline) adjust venetian blind angle 271 | */ 272 | venetianIncreaseAngle(deviceId, callback) { 273 | if (this.isSubtype("BLINDS_T13")) { 274 | return this._sendCommand(deviceId, 0x04, callback); 275 | } else { 276 | throw new Error("Device does not support venetianIncreaseAngle()"); 277 | } 278 | }; 279 | 280 | venetianDecreaseAngle(deviceId, callback) { 281 | if (this.isSubtype("BLINDS_T13")) { 282 | return this._sendCommand(deviceId, 0x05, callback); 283 | } else { 284 | throw new Error("Device does not support venetianDecreaseAngle()"); 285 | } 286 | }; 287 | 288 | /* 289 | * T6 Light command 290 | */ 291 | toggleLightOnOff(deviceId, callback) { 292 | if (this.isSubtype("BLINDS_T6")) { 293 | return this._sendCommand(deviceId, 0x05, callback); 294 | } else { 295 | throw new Error("Device does not support toggleLightOnOff()"); 296 | } 297 | }; 298 | } 299 | 300 | module.exports = Blinds1; 301 | -------------------------------------------------------------------------------- /lib/blinds2.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling 'bidirectional' automated blinds 9 | */ 10 | class Blinds2 extends Transmitter { 11 | constructor (rfxcom, subtype, options) { 12 | super (rfxcom, subtype, options); 13 | this.packetType = "blinds2"; 14 | this.packetNumber = 0x31; 15 | } 16 | 17 | /* 18 | * Splits the device id and returns the components. 19 | * Throws an Error if the format is invalid. 20 | */ 21 | _splitDeviceId(deviceId) { 22 | let id, unitCode; 23 | const parts = Transmitter.splitAtSlashes(deviceId); 24 | if (parts.length === 2) { 25 | id = RfxCom.stringToBytes(parts[0], 4); 26 | if (id.value <= 0 || id.value > 0xffffffff) { 27 | Transmitter.addressError(id) 28 | } 29 | unitCode = parseInt(parts[1]); 30 | if (isNaN(unitCode) || unitCode < 0 || unitCode > 16) { 31 | throw new Error("Invalid unit code " + parts[1]); 32 | } else if (unitCode === 0) { 33 | unitCode = 0x10; 34 | } else { 35 | unitCode = unitCode - 1; 36 | } 37 | } else { 38 | throw new Error("Invalid deviceId format"); 39 | } 40 | return { 41 | idBytes: id.bytes, 42 | unitCode: unitCode 43 | }; 44 | }; 45 | 46 | _sendCommand(deviceId, command, percent, angle, callback) { 47 | const device = this._splitDeviceId(deviceId); 48 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], 49 | device.unitCode, command, percent, angle, 0]; 50 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 51 | }; 52 | 53 | _checkPercent(percent) { 54 | if (typeof percent != "number" || isNaN(percent) || percent < 0 || percent > 100) { 55 | throw new Error("Invalid percentage: must be in range 0-100"); 56 | } else { 57 | percent = Math.round(percent); 58 | } 59 | return percent; 60 | }; 61 | 62 | _checkAngle(angle) { 63 | if (typeof angle != "number" || isNaN(angle) || angle < 0 || angle > 180) { 64 | throw new Error("Invalid angle: must be in range 0-180"); 65 | } else { 66 | angle = Math.round(angle); 67 | } 68 | return angle; 69 | }; 70 | 71 | open(deviceId, callback) { 72 | return this._sendCommand(deviceId, defines.BLINDS_OPEN, 0, 0, callback); 73 | }; 74 | 75 | up(deviceId, callback) { 76 | return this._sendCommand(deviceId, defines.BLINDS_OPEN, 0, 0, callback); 77 | }; 78 | 79 | close(deviceId, callback) { 80 | return this._sendCommand(deviceId, defines.BLINDS_CLOSE, 0, 0, callback); 81 | }; 82 | 83 | down(deviceId, callback) { 84 | return this._sendCommand(deviceId, defines.BLINDS_CLOSE, 0, 0, callback); 85 | }; 86 | 87 | stop(deviceId, callback) { 88 | return this._sendCommand(deviceId, defines.BLINDS_STOP, 0, 0, callback); 89 | }; 90 | 91 | confirm(deviceId, callback) { 92 | return this._sendCommand(deviceId, defines.BLINDS_CONFIRM, 0, 0, callback); 93 | }; 94 | 95 | setPercent(deviceId, percent, callback) { 96 | if (callback === undefined && typeof percent === "function") { 97 | callback = percent; 98 | percent = 50; 99 | } 100 | percent = this._checkPercent(percent); 101 | return this._sendCommand(deviceId, defines.BLINDS_SET_PERCENT, percent, 0, callback); 102 | }; 103 | 104 | setAngle(deviceId, angle, callback) { 105 | if (callback === undefined && typeof angle === "function") { 106 | callback = angle; 107 | angle = 90; 108 | } 109 | angle = this._checkAngle(angle); 110 | return this._sendCommand(deviceId, defines.BLINDS_SET_ANGLE, 0, angle, callback); 111 | }; 112 | 113 | setPercentAndAngle(deviceId, percent, angle, callback) { 114 | if (angle === undefined && typeof percent === "function") { 115 | callback = percent; 116 | percent = 50; 117 | angle = 90; 118 | } else if (callback === undefined && typeof angle === "function") { 119 | callback = angle; 120 | angle = 90; 121 | } 122 | percent = this._checkPercent(percent); 123 | angle = this._checkAngle(angle); 124 | return this._sendCommand(deviceId, defines.BLINDS_SET_PERCENT_AND_ANGLE, percent, angle, callback); 125 | }; 126 | 127 | } 128 | 129 | module.exports = Blinds2; 130 | -------------------------------------------------------------------------------- /lib/camera1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'); 7 | 8 | /* 9 | * This is a class for controlling security cameras 10 | */ 11 | class Camera1 extends Transmitter { 12 | constructor(rfxcom, subtype, options) { 13 | super(rfxcom, subtype, options); 14 | this.packetType = "camera1"; 15 | this.packetNumber = 0x28; 16 | }; 17 | 18 | /* 19 | * Extracts the device houseCode. 20 | * 21 | */ 22 | _splitDeviceId(deviceId) { 23 | const parts = Transmitter.splitAtSlashes(deviceId); 24 | if (parts.length !== 1) { 25 | throw new Error("Invalid deviceId format"); 26 | } 27 | const houseCode = parts[0].toUpperCase().charCodeAt(0); 28 | if (this.isSubtype('X10_NINJA') && (houseCode < 0x41 || houseCode > 0x50)) { 29 | throw new Error("Invalid house code '" + parts[0] + "'"); 30 | } 31 | return { 32 | houseCode: houseCode 33 | }; 34 | }; 35 | 36 | _sendCommand(deviceId, command, callback) { 37 | const device = this._splitDeviceId(deviceId); 38 | let buffer = [device.houseCode, command, 0]; 39 | 40 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 41 | }; 42 | 43 | panLeft(deviceId, callback) { 44 | return this._sendCommand(deviceId, 0x0, callback); 45 | }; 46 | 47 | panRight(deviceId, callback) { 48 | return this._sendCommand(deviceId, 0x1, callback); 49 | }; 50 | 51 | tiltUp(deviceId, callback) { 52 | return this._sendCommand(deviceId, 0x2, callback); 53 | }; 54 | 55 | tiltDown(deviceId, callback) { 56 | return this._sendCommand(deviceId, 0x3, callback); 57 | }; 58 | 59 | goToPosition(deviceId, position, callback) { 60 | let command = 0; 61 | if (position === 0) { 62 | command = 0xc; 63 | } else if (position >= 1 && position <= 4) { 64 | command = 0x2 + 2*Math.round(position); 65 | } else { 66 | throw new Error("Invalid position: value must be in range 0-4"); 67 | } 68 | return this._sendCommand(deviceId, command, callback); 69 | }; 70 | 71 | programPosition(deviceId, position, callback) { 72 | let command = 0; 73 | if (position === 0) { 74 | command = 0xd; 75 | } else if (position >= 1 && position <= 4) { 76 | command = 0x3 + 2*Math.round(position); 77 | } else { 78 | throw new Error("Invalid position: value must be in range 0-4"); 79 | } 80 | return this._sendCommand(deviceId, command, callback); 81 | }; 82 | 83 | sweep(deviceId, callback) { 84 | return this._sendCommand(deviceId, 0xe, callback); 85 | }; 86 | 87 | programSweep(deviceId, callback) { 88 | return this._sendCommand(deviceId, 0xf, callback); 89 | }; 90 | 91 | } 92 | 93 | module.exports = Camera1; 94 | -------------------------------------------------------------------------------- /lib/chime1.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling doorbell chimes 9 | */ 10 | class Chime1 extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "chime1"; 14 | this.packetNumber = 0x16; 15 | } 16 | 17 | /* 18 | * validate the unitCode based on the subtype (handle a 1-element array as well) 19 | * 20 | */ 21 | _splitDeviceId(deviceId) { 22 | let id = {}; 23 | const parts = Transmitter.splitAtSlashes(deviceId); 24 | if (parts.length !== 1) { 25 | throw new Error("Invalid device ID format"); 26 | } 27 | id = RfxCom.stringToBytes(parts[0], 3); 28 | if (this.isSubtype("BYRON_SX")) { 29 | id = RfxCom.stringToBytes(parts[0], 2); 30 | } else if (this.isSubtype("BYRON_MP001")) { 31 | // Expect a string of exactly six ones & zeros 32 | if (parts[0].match(/[01][01][01][01][01][01]/) === null) { 33 | throw new Error("Invalid device ID format"); 34 | } 35 | id = { bytes: [(parts[0].charAt(0) === '0' ? 1 : 0)*64 + 36 | (parts[0].charAt(1) === '0' ? 1 : 0)*16 + 37 | (parts[0].charAt(2) === '0' ? 1 : 0)*4 + 38 | (parts[0].charAt(3) === '0' ? 1 : 0), 39 | (parts[0].charAt(4) === '0' ? 1 : 0)*64 + 40 | (parts[0].charAt(5) === '0' ? 1 : 0)*16 + 15, 41 | 0x54] }; 42 | } else if (this.isSubtype("BYRON_BY")) { 43 | id.bytes[0] = (id.value & 0x1fe00) >> 9; 44 | id.bytes[1] = (id.value & 0x001fe) >> 1; 45 | id.bytes[2] = (id.value & 0x00001) << 7; 46 | } else if (this.isSubtype(["QH_A19", "BYRON_DBY"])) { 47 | id = RfxCom.stringToBytes(parts[0], 4) 48 | } 49 | if ((this.isSubtype("BYRON_SX") && id.value > 0xff) || 50 | (this.isSubtype("BYRON_BY") && id.value > 0x1ffff) || 51 | (this.isSubtype(["ENVIVO", "ALFAWISE"]) && id.value > 0xffffff) || 52 | (this.isSubtype("SELECT_PLUS") && id.value > 0x03ffff) || 53 | (this.isSubtype("QH_A19") && (id.value > 0xffffff03 || id.bytes[3] > 3)) || 54 | (this.isSubtype("BYRON_DBY") && id.value > 0xffffffff)) { 55 | Transmitter.deviceIdError(id); 56 | } 57 | return { 58 | idBytes: id.bytes 59 | }; 60 | }; 61 | 62 | _sendCommand(deviceId, command, callback) { 63 | const device = this._splitDeviceId(deviceId); 64 | if (command === undefined) { 65 | command = 0x05; 66 | } 67 | if (device.idBytes.length === 2) { 68 | device.idBytes.push(command); 69 | } 70 | if (this.isSubtype(["QH_A19", "BYRON_DBY"])) { 71 | var buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], 0]; 72 | } else { 73 | var buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], 0]; 74 | } 75 | if (this.isSubtype("BYRON_BY")) { 76 | buffer[2] |= command; 77 | } 78 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 79 | }; 80 | 81 | chime(deviceId, tone, callback) { 82 | if (tone === undefined) { 83 | tone = 0x5; 84 | } else if (callback === undefined && typeof tone === "function") { 85 | callback = tone; 86 | tone = 0x5; 87 | } 88 | if (this.isSubtype("BYRON_SX") && (tone < 0x00 || tone > 0x0f)) { 89 | throw new Error("Invalid tone: value must be in range 0-15"); 90 | } else if (this.isSubtype("BYRON_BY") && (tone < 0x00 || tone > 0x07)) { 91 | throw new Error("Invalid tone: value must be in range 0-7"); 92 | } 93 | return this._sendCommand(deviceId, tone, callback); 94 | }; 95 | 96 | } 97 | 98 | module.exports = Chime1; 99 | -------------------------------------------------------------------------------- /lib/curtain1.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'); 5 | 6 | /* 7 | * This is a class for controlling Harrison curtain controllers.. 8 | */ 9 | class Curtain1 extends Transmitter { 10 | constructor(rfxcom, subtype, options) { 11 | super(rfxcom, subtype, options); 12 | this.packetType = "curtain1"; 13 | this.packetNumber = 0x18; 14 | } 15 | 16 | /* 17 | * Splits the device id x/y and returns the components, the deviceId will be 18 | * returned as the component bytes, ready for sending. 19 | * 20 | * Throws an Error if the format is invalid. 21 | */ 22 | _splitDeviceId(deviceId) { 23 | let parts, unitCode; 24 | if (Array.isArray(deviceId)) { 25 | parts = deviceId; 26 | if (parts.length !== 2) { 27 | throw new Error("Invalid deviceId format"); 28 | } 29 | unitCode = parseInt(parts[1]); 30 | } else { 31 | parts = deviceId.split(""); 32 | unitCode = parseInt(parts.slice(1).join(""), 10); 33 | } 34 | const houseCode = parts[0].toUpperCase().charCodeAt(0); 35 | if (houseCode < 0x41 || (this.isSubtype("HARRISON") && houseCode > 0x50)) { 36 | throw new Error("Invalid house code '" + parts[0] + "'"); 37 | } 38 | if (unitCode < 1 || unitCode > 16) { 39 | throw new Error("Invalid unit code " + parts[1]); 40 | } 41 | return { 42 | houseCode: houseCode, 43 | unitCode: unitCode 44 | }; 45 | }; 46 | 47 | _sendCommand(deviceId, command, callback) { 48 | const device = this._splitDeviceId(deviceId); 49 | let buffer = [device.houseCode, device.unitCode, command, 0]; 50 | 51 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 52 | }; 53 | 54 | /* 55 | * Open deviceId. 56 | */ 57 | open(deviceId, callback) { 58 | return this._sendCommand(deviceId, defines.CURTAIN_OPEN, callback); 59 | }; 60 | 61 | /* 62 | * Close deviceId. 63 | */ 64 | close(deviceId, callback) { 65 | return this._sendCommand(deviceId, defines.CURTAIN_CLOSE, callback); 66 | }; 67 | 68 | /* 69 | * Stop deviceId. 70 | */ 71 | stop(deviceId, callback) { 72 | return this._sendCommand(deviceId, defines.CURTAIN_STOP, callback); 73 | }; 74 | 75 | /* 76 | * Programme deviceId. 77 | */ 78 | program(deviceId, callback) { 79 | return this._sendCommand(deviceId, defines.CURTAIN_PROGRAM, callback); 80 | }; 81 | 82 | } 83 | 84 | module.exports = Curtain1; 85 | -------------------------------------------------------------------------------- /lib/defines.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | INTERFACE_CONTROL: 0, 4 | INTERFACE_MESSAGE: 1, 5 | TRANSCEIVER_MESSAGE: 2, 6 | 7 | LIGHTING1: 0x10, 8 | LIGHTING2: 0x11, 9 | LIGHTING3: 0x12, 10 | LIGHTING4: 0x13, 11 | LIGHTING5: 0x14, 12 | LIGHTING6: 0x15, 13 | CHIME1: 0x16, 14 | FAN: 0x17, 15 | CURTAIN1: 0x18, 16 | BLINDS1: 0x19, 17 | RFY: 0x1A, 18 | HOMECONFORT: 0x1B, 19 | SECURITY1: 0x20, 20 | CAMERA1: 0x28, 21 | REMOTE: 0x30, 22 | THERMOSTAT1: 0x40, 23 | THERMOSTAT2: 0x41, 24 | THERMOSTAT3: 0x42, 25 | THERMOSTAT4: 0x43, 26 | THERMOSTAT5: 0x44, 27 | RADIATOR1: 0x48, 28 | ELEC2: 0x5a, 29 | 30 | HONEYWELL_NORMAL: 0x00, 31 | HONEYWELL_RIGHT_HALO: 0x01, 32 | HONEYWELL_LEFT_HALO: 0x02, 33 | HONEYWELL_FULL_VOLUME: 0x03, 34 | HONEYWELL_SECRET_KNOCK: 0x01, 35 | 36 | BLINDS_OPEN: 0x00, 37 | BLINDS_DOWN: 0x00, 38 | BLINDS_CLOSE: 0x01, 39 | BLINDS_UP: 0x01, 40 | BLINDS_STOP: 0x02, 41 | BLINDS_CONFIRM: 0x03, 42 | BLINDS_T13_CONFIRM: 0x02, 43 | BLINDS_SET: 0x04, 44 | BLINDS_SET_LIMIT: 0x04, 45 | BLINDS_SET_PERCENT: 0x04, 46 | BLINDS_SET_LOWER_LIMIT: 0x05, 47 | BLINDS_SET_ANGLE: 0x05, 48 | BLINDS_DELETE_LIMITS: 0x06, 49 | BLINDS_SET_PERCENT_AND_ANGLE: 0x06, 50 | BLINDS_REVERSE: 0x06, 51 | BLINDS_T4_REVERSE: 0x07, 52 | BLINDS_T16_REVERSE: 0x05, 53 | BLINDS_T19_CONFIRM: 0x05, 54 | BLINDS_T19_CLOSE_CW: 0x00, 55 | BLINDS_T19_CLOSE_CCW: 0x01, 56 | 57 | CURTAIN_OPEN: 0x00, 58 | CURTAIN_CLOSE: 0x01, 59 | CURTAIN_STOP: 0x02, 60 | CURTAIN_PROGRAM: 0x03, 61 | 62 | RfyCommands: { 63 | stop: 0x00, 64 | up: 0x01, 65 | upstop: 0x02, 66 | down: 0x03, 67 | downstop: 0x04, 68 | updown: 0x05, 69 | listRemotes: 0x06, 70 | program: 0x07, 71 | program2sec: 0x08, 72 | program7sec: 0x09, 73 | stop2sec: 0x0A, 74 | stop5sec: 0x0B, 75 | updown5sec: 0x0C, 76 | erasethis: 0x0D, 77 | eraseall: 0x0E, 78 | up05sec: 0x0F, 79 | down05sec: 0x10, 80 | up2sec: 0x11, 81 | down2sec: 0x12, 82 | sunwindenable: 0x13, 83 | sundisable: 0x14 84 | }, 85 | LastRfyCommand: 0x14, 86 | 87 | THERMOSTAT1_MODE: ["Heating", "Cooling"], 88 | THERMOSTAT1_STATUS: ["N/A", "Demand", "No Demand", "Initializing"] 89 | 90 | }; 91 | 92 | -------------------------------------------------------------------------------- /lib/edisio.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Edisio devices 9 | */ 10 | class Edisio extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "edisio"; 14 | this.packetNumber = 0x1c; 15 | this.maxRepeat = 5; 16 | } 17 | 18 | /* 19 | * Splits the device id and returns the components. 20 | * Throws an Error if the format is invalid. 21 | */ 22 | _splitDeviceId(deviceId) { 23 | let id, unitCode; 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (parts.length !== 2) { 26 | throw new Error("Invalid deviceId format"); 27 | } else { 28 | id = RfxCom.stringToBytes(parts[0], 4); 29 | if ((id.value === 0) || 30 | (this.isSubtype("EDISIO_CONTROLLER") && id.value > 0xffffffff)) { 31 | Transmitter.addressError(id); 32 | } 33 | unitCode = parseInt(parts[1]); 34 | if (this.isSubtype("EDISIO_CONTROLLER")) { 35 | if (unitCode === 0) { 36 | throw new Error("Subtype doesn't support group commands"); 37 | } else if (unitCode < 1 || unitCode > 16) { 38 | throw new Error("Invalid unit code " + parts[1]); 39 | } 40 | } 41 | } 42 | return { 43 | idBytes: id.bytes, 44 | unitCode: unitCode 45 | }; 46 | } 47 | 48 | _sendCommand(deviceId, command, level, R, G, B, callback) { 49 | const device = this._splitDeviceId(deviceId); 50 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], 51 | device.unitCode, command, level, R, G, B, this.maxRepeat, 0, 0, 0]; 52 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 53 | }; 54 | 55 | switchOff(deviceId, callback) { 56 | return this._sendCommand(deviceId, 0x00, 0, 0, 0, 0, callback); 57 | }; 58 | 59 | switchOn(deviceId, callback) { 60 | return this._sendCommand(deviceId, 0x01, 0, 0, 0, 0, callback); 61 | }; 62 | 63 | toggleOnOff(deviceId, callback) { 64 | return this._sendCommand(deviceId, 0x02, 0, 0, 0, 0, callback); 65 | }; 66 | 67 | setLevel(deviceId, level, callback) { 68 | level = Math.round(level); 69 | if (level > 100 || level < 0) { 70 | throw new Error("Dim level must be in the range 0-100"); 71 | } 72 | return this._sendCommand(deviceId, 0x03, level, 0, 0, 0, callback); 73 | } 74 | 75 | increaseLevel(deviceId, roomNumber, callback) { 76 | if (typeof roomNumber === "function") { 77 | callback = roomNumber; 78 | } 79 | return this._sendCommand(deviceId, 0x04, 0, 0, 0, 0, callback); 80 | }; 81 | 82 | decreaseLevel(deviceId, roomNumber, callback) { 83 | if (typeof roomNumber === "function") { 84 | callback = roomNumber; 85 | } 86 | return this._sendCommand(deviceId, 0x05, 0, 0, 0, 0, callback); 87 | }; 88 | 89 | toggleDimming(deviceId, callback) { 90 | return this._sendCommand(deviceId, 0x06, 0, 0, 0, 0, callback); 91 | }; 92 | 93 | stopDimming(deviceId, callback) { 94 | return this._sendCommand(deviceId, 0x07, 0, 0, 0, 0, callback); 95 | }; 96 | 97 | setColour(deviceId, colour, callback) { 98 | if (colour.R === undefined || colour.G === undefined || colour.B === undefined) { 99 | throw new Error("Colour must be an object with fields R, G, B"); 100 | } 101 | const R = Math.round(colour.R), G = Math.round(colour.G), B = Math.round(colour.B); 102 | if ((R > 255 || R < 0) || (G > 255 || G < 0) ||(B > 255 || B < 0)) { 103 | throw new Error("Colour RGB values must be in the range 0-255"); 104 | } 105 | return this._sendCommand(deviceId, 0x08, 0, R, G, B, callback); 106 | }; 107 | 108 | program(deviceId, callback) { 109 | return this._sendCommand(deviceId, 0x09, 0, 0, 0, 0, callback); 110 | }; 111 | 112 | open(deviceId, callback) { 113 | return this._sendCommand(deviceId, 0x0a, 0, 0, 0, 0, callback); 114 | }; 115 | 116 | stop(deviceId, callback) { 117 | return this._sendCommand(deviceId, 0x0b, 0, 0, 0, 0, callback); 118 | }; 119 | 120 | close(deviceId, callback) { 121 | return this._sendCommand(deviceId, 0x0c, 0, 0, 0, 0, callback); 122 | }; 123 | 124 | sendContactNormal(deviceId, callback) { 125 | return this._sendCommand(deviceId, 0x0d, 0, 0, 0, 0, callback); 126 | }; 127 | 128 | sendContactAlert(deviceId, callback) { 129 | return this._sendCommand(deviceId, 0x0e, 0, 0, 0, 0, callback); 130 | }; 131 | } 132 | 133 | module.exports = Edisio; -------------------------------------------------------------------------------- /lib/funkbus.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Funkbus devices. 9 | */ 10 | class Funkbus extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "funkbus"; 14 | this.packetNumber = 0x1e; 15 | } 16 | 17 | /* 18 | * Splits the device id into idBytes, group, and optional channelCode. 19 | * 20 | */ 21 | // noinspection JSMethodCanBeStatic 22 | _splitDeviceId(deviceId) { 23 | const 24 | parts = Transmitter.splitAtSlashes(deviceId); 25 | if (parts.length < 2 || parts.length > 3) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const 29 | id = RfxCom.stringToBytes(parts[0], 2), 30 | group = parts[1].toUpperCase().charCodeAt(0); 31 | if (id.value > 0xFFFF) { 32 | Transmitter.deviceIdError(id); 33 | } 34 | if (group < 0x41 || group > 0x43) { 35 | throw new Error("Invalid group code '" + parts[1] + "'"); 36 | } 37 | return { 38 | idBytes: id.bytes, 39 | groupCode: group, 40 | channelCode: parts.length >= 3 ? parseInt(parts[2]) : -1 41 | }; 42 | }; 43 | 44 | _sendCommand(device, command, commandParameter, commandTime, callback) { 45 | if (commandParameter < 0) { 46 | commandParameter = device.channelCode; 47 | } 48 | let buffer = [device.idBytes[0], device.idBytes[1], device.groupCode, commandParameter, 49 | command, commandTime, 0, 0]; 50 | 51 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 52 | }; 53 | 54 | buttonPress(deviceId, buttonName, buttonNumber, buttonPressTime, callback) { 55 | if (buttonPressTime < 0 || buttonPressTime > 12) { 56 | throw new Error("Invalid buttonPressTime: value must be in range 0.0 - 12.0 seconds."); 57 | } 58 | const device = this._splitDeviceId(deviceId); 59 | const cmndtime = Math.max(0, Math.round(4*buttonPressTime) - 3); 60 | buttonNumber = Math.round(buttonNumber); 61 | if (this.isSubtype("GIRA")) { 62 | if (/All Off/i.test(buttonName)) { 63 | return this._sendCommand(device, 0x02, 0, cmndtime, callback); 64 | } else if (/All On/i.test(buttonName)) { 65 | return this._sendCommand(device, 0x03, 0, cmndtime, callback); 66 | } else if (/Scene/i.test(buttonName)) { 67 | if (buttonNumber >= 1 && buttonNumber <= 5) { 68 | return this._sendCommand(device, 0x04, buttonNumber, cmndtime, callback); 69 | } else { 70 | throw new Error("Invalid scene number " + buttonNumber) 71 | } 72 | } else if (/Master.*(-|Down)/i.test(buttonName)) { 73 | return this._sendCommand(device, 0x05, 0, cmndtime, callback); 74 | } else if (/Master.*([+]|Up)/i.test(buttonName)) { 75 | return this._sendCommand(device, 0x06, 0, cmndtime, callback); 76 | } 77 | } 78 | if (buttonNumber === 0) { 79 | throw new Error("Device doesn't support group commands") 80 | } else if (/^Down|^-|^Up|^[+]/i.test(buttonName)) { 81 | if (buttonNumber >= 1 && buttonNumber <= 8) { 82 | if (/^Down|^-/i.test(buttonName)) { 83 | return this._sendCommand(device, 0x00, buttonNumber, cmndtime, callback); 84 | } else if (/^Up|^[+]/i.test(buttonName)) { 85 | return this._sendCommand(device, 0x01, buttonNumber, cmndtime, callback); 86 | } 87 | } else { 88 | throw new Error("Invalid channel: value must be in range 1-8") 89 | } 90 | } else { 91 | throw new Error("Device doesn't support command \"" + buttonName + "\""); 92 | } 93 | }; 94 | 95 | /* 96 | * Switch on deviceId/groupCode/channelCode 97 | */ 98 | switchOn(deviceId, callback) { 99 | const device = this._splitDeviceId(deviceId); 100 | if (device.channelCode !== 0) { 101 | return this.buttonPress(deviceId, "Up", device.channelCode, 0.5, callback); 102 | } else { 103 | return this.buttonPress(deviceId, "All On", 0, 0.5, callback); 104 | } 105 | }; 106 | 107 | /* 108 | * Switch off deviceId/groupCode/channelCode 109 | */ 110 | switchOff(deviceId, callback) { 111 | const device = this._splitDeviceId(deviceId); 112 | if (device.channelCode !== 0) { 113 | return this.buttonPress(deviceId, "Down", device.channelCode, 0.5, callback); 114 | } else { 115 | return this.buttonPress(deviceId, "All Off", 0, 0.5, callback); 116 | } 117 | }; 118 | 119 | /* 120 | * Increase brightness deviceId/groupCode/channelCode, or the selected scene if channelCode == 0 121 | */ 122 | increaseLevel(deviceId, roomNumber, callback) { 123 | if (typeof roomNumber === "function") { // For back-compatibilty: roomNumber is no longer used 124 | callback = roomNumber; 125 | } 126 | const device = this._splitDeviceId(deviceId); 127 | if (device.channelCode !== 0) { 128 | return this.buttonPress(deviceId, "Up", device.channelCode, 1.0, callback); 129 | } else { 130 | return this.buttonPress(deviceId, "Master Up", 0, 0.5, callback); 131 | } 132 | }; 133 | 134 | /* 135 | * Decrease brightness deviceId/groupCode/channelCode, or the selected scene if channelCode == 0 136 | */ 137 | decreaseLevel(deviceId, roomNumber, callback) { 138 | if (typeof roomNumber === "function") { // For back-compatibilty: roomNumber is no longer used 139 | callback = roomNumber; 140 | } 141 | const device = this._splitDeviceId(deviceId); 142 | if (device.channelCode !== 0) { 143 | return this.buttonPress(deviceId, "Down", device.channelCode, 1.0, callback); 144 | } else { 145 | return this.buttonPress(deviceId, "Master Down", 0, 0.5, callback); 146 | } 147 | }; 148 | 149 | /* 150 | * Select a (previously programmed) scene 151 | */ 152 | setScene(deviceId, sceneNumber, roomNumber, callback) { 153 | if (typeof roomNumber === "function") { // For back-compatibilty: roomNumber is no longer used 154 | callback = roomNumber; 155 | } 156 | return this.buttonPress(deviceId, "Scene", sceneNumber, 0.5, callback) 157 | }; 158 | 159 | } 160 | 161 | module.exports = Funkbus; -------------------------------------------------------------------------------- /lib/homeConfort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 30/06/2017. 3 | */ 4 | /*jshint -W104 */ 5 | 'use strict'; 6 | const 7 | Transmitter = require('./transmitter'), 8 | RfxCom = require('./rfxcom'); 9 | 10 | /* 11 | * This is a class for controlling Home Confort devices. 12 | */ 13 | class HomeConfort extends Transmitter { 14 | constructor(rfxcom, subtype, options) { 15 | super(rfxcom, subtype, options); 16 | this.packetType = "homeConfort"; 17 | this.packetNumber = 0x1b; 18 | } 19 | 20 | /* 21 | * Splits the device id x/y and returns the components, the deviceId will be 22 | * returned as the component bytes, ready for sending. 23 | * 24 | * Throws an Error if the format is invalid or the address is out of range. 25 | */ 26 | _splitDeviceId (deviceId) { 27 | let id, houseCode, unitCode; 28 | const parts = Transmitter.splitAtSlashes(deviceId); 29 | if (parts.length !== 3) { 30 | throw new Error("Invalid deviceId format"); 31 | } 32 | id = RfxCom.stringToBytes(parts[0], 3); 33 | if ((id.value === 0) || ((this.isSubtype(["TEL_010"])) && id.value > 0x7ffff)) { 34 | Transmitter.addressError(id); 35 | } 36 | houseCode = parts[1].toUpperCase().charCodeAt(0); 37 | if (houseCode < 0x41 || (this.isSubtype("TEL_010") && houseCode > 0x44)) { 38 | throw new Error("Invalid house code '" + parts[1] + "'"); 39 | } 40 | unitCode = parseInt(parts[2], 10); 41 | if (unitCode < 0 || unitCode > 4) { 42 | throw new Error("Invalid unit code " + parts[2]); 43 | } 44 | return { 45 | idBytes: id.bytes, 46 | houseCode: houseCode, 47 | unitCode: unitCode 48 | }; 49 | }; 50 | 51 | /* 52 | * Creates a buffer containing the command message, and enqueues it for transmission. Parses and error checks the 53 | * deviceId, converting group commands appropriately. 54 | */ 55 | _sendCommand(deviceId, command, callback) { 56 | const device = this._splitDeviceId(deviceId); 57 | if (device.unitCode === 0) { 58 | command = command + 2; 59 | } 60 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], 61 | device.houseCode, device.unitCode, command, 0, 0, 0]; 62 | 63 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 64 | }; 65 | 66 | /* 67 | * Switch On deviceId. 68 | */ 69 | switchOn(deviceId, callback) { 70 | return this._sendCommand(deviceId, 0x01, callback); 71 | }; 72 | 73 | /* 74 | * Switch Off deviceId. 75 | */ 76 | switchOff(deviceId, callback) { 77 | return this._sendCommand(deviceId, 0x00, callback); 78 | }; 79 | 80 | } 81 | 82 | module.exports = HomeConfort; 83 | -------------------------------------------------------------------------------- /lib/hunterFan.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require('./defines'), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Hunter Fan devices 9 | */ 10 | class HunterFan extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "hunterFan"; 14 | this.packetNumber = 0x1f; 15 | } 16 | 17 | /* 18 | * Splits the device id and returns the components. 19 | * Throws an Error if the format is invalid. 20 | */ 21 | _splitDeviceId(deviceId) { 22 | let id; 23 | const parts = Transmitter.splitAtSlashes(deviceId); 24 | if (parts.length !== 1) { 25 | throw new Error("Invalid deviceId format"); 26 | } else { 27 | id = RfxCom.stringToBytes(parts[0], 6); 28 | if ((id.value <= 0) || 29 | (this.isSubtype("HUNTER_FAN") && id.value > 0xffffffffffff)) { 30 | Transmitter.addressError(id); 31 | } 32 | } 33 | return { 34 | idBytes: id.bytes, 35 | }; 36 | } 37 | 38 | _sendCommand(deviceId, command, callback) { 39 | const device = this._splitDeviceId(deviceId); 40 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], 41 | device.idBytes[4], device.idBytes[5], command, 0]; 42 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 43 | }; 44 | 45 | switchOff(deviceId, callback) { 46 | return this._sendCommand(deviceId, 1, callback); 47 | } 48 | 49 | setSpeed(deviceId, speed, callback) { 50 | if (typeof speed === "number") { 51 | speed = Math.round(speed); 52 | if (speed < 0 || speed > 3) { 53 | throw new Error("Invalid speed: value must be in range 0-3") 54 | } else { 55 | if (speed === 0) { 56 | return this.switchOff(deviceId, callback); 57 | } else { 58 | return this._sendCommand(deviceId, speed + 2, callback); 59 | } 60 | } 61 | } 62 | } 63 | 64 | toggleLightOnOff(deviceId, callback) { 65 | return this._sendCommand(deviceId, 2, callback); 66 | } 67 | 68 | program(deviceId, callback) { 69 | return this._sendCommand(deviceId, 6, callback); 70 | } 71 | } 72 | 73 | module.exports = HunterFan; -------------------------------------------------------------------------------- /lib/lighting1.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'); 5 | 6 | /* 7 | * This is a class for controlling Lighting1 lights. 8 | */ 9 | class Lighting1 extends Transmitter { 10 | constructor(rfxcom, subtype, options) { 11 | super(rfxcom, subtype, options); 12 | this.packetType = "lighting1"; 13 | this.packetNumber = 0x10; 14 | } 15 | 16 | /* 17 | * Splits the device id into houseCode, unitCode. 18 | * 19 | */ 20 | _splitDeviceId(deviceId) { 21 | let parts, houseCode, unitCode; 22 | if (Array.isArray(deviceId)) { 23 | parts = deviceId; 24 | if (parts.length !== 2) { 25 | throw new Error("Invalid deviceId format"); 26 | } 27 | unitCode = parseInt(parts[1]); 28 | } else { 29 | parts = deviceId.split(""); 30 | unitCode = parseInt(parts.slice(1).join(""), 10); 31 | } 32 | houseCode = parts[0].toUpperCase().charCodeAt(0); 33 | if (houseCode < 0x41 || (this.isSubtype("CHACON") && houseCode > 0x43) || 34 | (this.isSubtype(["RISING_SUN", "COCO", "HQ"]) && houseCode > 0x44) || houseCode > 0x50) { 35 | throw new Error("Invalid house code '" + parts[0] + "'"); 36 | } 37 | if ((this.isSubtype(["X10", "ARC", "WAVEMAN"]) && unitCode > 16) || 38 | (this.isSubtype(["ELRO", "IMPULS", "HQ"]) && unitCode > 64) || 39 | (this.isSubtype("PHILIPS_SBC") && unitCode > 8) || 40 | (this.isSubtype("ENERGENIE_5_GANG") && unitCode > 10) || 41 | (this.isSubtype(["CHACON", "RISING_SUN", "ENERGENIE_ENER010", "COCO"]) && unitCode > 4) || 42 | (this.isSubtype("OASE_FM") && unitCode > 3)) { 43 | throw new Error("Invalid unit code " + parts[1]); 44 | } 45 | return { 46 | houseCode: houseCode, 47 | unitCode: unitCode 48 | }; 49 | }; 50 | 51 | _sendCommand(deviceId, command, callback) { 52 | const device = this._splitDeviceId(deviceId); 53 | if (device.unitCode === 0) { 54 | if (command < 2) { 55 | command = command + 5; 56 | if ((this.isSubtype(["X10", "ARC", "PHILIPS_SBC", "ENERGENIE_ENER010"])) === false) { 57 | throw new Error("Device does not support group on/off commands"); 58 | } 59 | } else if (command !== 7) { 60 | throw new Error("Device does not support group dim/bright commands"); 61 | } 62 | } 63 | let buffer = [device.houseCode, device.unitCode, command, 0]; 64 | 65 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 66 | }; 67 | 68 | /* 69 | * Switch on deviceId/unitCode 70 | */ 71 | switchOn(deviceId, callback) { 72 | return this._sendCommand(deviceId, 0x01, callback); 73 | }; 74 | 75 | /* 76 | * Switch off deviceId/unitCode 77 | */ 78 | switchOff(deviceId, callback) { 79 | return this._sendCommand(deviceId, 0x00, callback); 80 | }; 81 | 82 | /* 83 | * Increase brightness deviceId/unitCode (X10 only) 'Bright' 84 | */ 85 | increaseLevel(deviceId, roomNumber, callback) { 86 | if (this.isSubtype("X10")) { 87 | if (typeof roomNumber === "function") { 88 | callback = roomNumber; 89 | } 90 | return this._sendCommand(deviceId, 0x03, callback); 91 | } else { 92 | throw new Error("Device does not support increaseLevel()") 93 | } 94 | }; 95 | 96 | /* 97 | * Decrease brightness deviceId/unitCode (X10 only) 'Dim' 98 | */ 99 | decreaseLevel(deviceId, roomNumber, callback) { 100 | if (this.isSubtype("X10")) { 101 | if (typeof roomNumber === "function") { 102 | callback = roomNumber; 103 | } 104 | return this._sendCommand(deviceId, 0x02, callback); 105 | } else { 106 | throw new Error("Device does not support decreaseLevel()") 107 | } 108 | }; 109 | 110 | /* 111 | * 'Chime' deviceId/unitCode (ARC only) 112 | */ 113 | chime(deviceId, callback) { 114 | if (this.isSubtype("ARC")) { 115 | return this._sendCommand(deviceId, 0x07, callback); 116 | } else { 117 | throw new Error("Device does not support chime()"); 118 | } 119 | }; 120 | 121 | /* 122 | * 'Program' device (OASE_FM only) 123 | */ 124 | program(deviceId, callback) { 125 | if (this.isSubtype("OASE_FM")) { 126 | return this._sendCommand(deviceId, 0x04, callback); 127 | } else { 128 | throw new Error("Device does not support program()"); 129 | } 130 | }; 131 | 132 | } 133 | 134 | module.exports = Lighting1; 135 | 136 | -------------------------------------------------------------------------------- /lib/lighting2.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Lighting2 lights. 9 | */ 10 | class Lighting2 extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "lighting2"; 14 | this.packetNumber = 0x11; 15 | } 16 | 17 | /* 18 | * Splits the device id x/y and returns the components, the deviceId will be 19 | * returned as the component bytes, ready for sending. 20 | * 21 | * Throws an Error if the format is invalid or if the deviceId is not the 22 | * correct length, the address is out of the valid range 23 | * 24 | * If deviceId is an array, it is assumed to be already split 25 | */ 26 | _splitDeviceId(deviceId) { 27 | let id, unitCode; 28 | const parts = Transmitter.splitAtSlashes(deviceId); 29 | if (this.isSubtype("KAMBROOK")) { 30 | if (parts.length !== 3) { 31 | throw new Error("Invalid deviceId format"); 32 | } 33 | id = RfxCom.stringToBytes(parts[1], 4); 34 | if (id.value < 1 || id.value > 0xffffff) { 35 | Transmitter.remoteIdError(id); 36 | } 37 | id.bytes[0] = parts[0].toUpperCase().charCodeAt(0) - "A".charCodeAt(0); 38 | if (id.bytes[0] < 0 || id.bytes[0] > 3) { 39 | throw new Error("Invalid house code '" + parts[0] + "'"); 40 | } 41 | unitCode = parseInt(parts[2]); 42 | if (unitCode === 0) { 43 | throw new Error("Subtype doesn't support group commands"); 44 | } else if (unitCode < 1 || unitCode > 5) { 45 | throw new Error("Invalid unit code " + parts[2]); 46 | } 47 | } else { 48 | if (parts.length !== 2) { 49 | throw new Error("Invalid deviceId format"); 50 | } 51 | id = RfxCom.stringToBytes(parts[0], 4); 52 | if (id.value < 1 || id.value > 0x03ffffff) { 53 | Transmitter.deviceIdError(id); 54 | } 55 | unitCode = parseInt(parts[1]); 56 | if (unitCode < 0 || unitCode > 16) { 57 | throw new Error("Invalid unit code " + parts[1]); 58 | } 59 | } 60 | return { 61 | idBytes: id.bytes, 62 | unitCode: unitCode 63 | }; 64 | }; 65 | 66 | _sendCommand(deviceId, command, level, callback) { 67 | const device = this._splitDeviceId(deviceId); 68 | level = (level === undefined) ? 0xf : level; // Now works when level == 0 69 | // If the device code is 0 convert to a group command by adding 3 70 | if (device.unitCode === 0) { 71 | command = command + 3; 72 | device.unitCode = 1; // Group commands are sent with a unit code of 1 73 | } 74 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], 75 | device.unitCode, command, level, 0]; 76 | 77 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 78 | }; 79 | 80 | /* 81 | * Switch on deviceId/unitCode (unitCode 0 means group) 82 | */ 83 | switchOn(deviceId, callback) { 84 | return this._sendCommand(deviceId, 1, 15, callback); 85 | }; 86 | 87 | /* 88 | * Switch off deviceId/unitCode (unitCode 0 means group) 89 | */ 90 | switchOff(deviceId, callback) { 91 | return this._sendCommand(deviceId, 0, 0, callback); 92 | }; 93 | 94 | /* 95 | * Set dim level deviceId/unitCode (unitCode 0 means group) 96 | */ 97 | setLevel(deviceId, level, callback) { 98 | if (this.isSubtype("KAMBROOK")) { 99 | throw new Error("KAMBROOK: Set level not supported") 100 | } 101 | if ((level < 0) || (level > 0xf)) { 102 | throw new Error("Invalid level: value must be in range 0-15"); 103 | } 104 | return this._sendCommand(deviceId, 2, level, callback); 105 | }; 106 | 107 | } 108 | 109 | module.exports = Lighting2; 110 | 111 | -------------------------------------------------------------------------------- /lib/lighting3.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'); 5 | 6 | /* 7 | * This is a class for controlling Lighting3 lights. 8 | */ 9 | class Lighting3 extends Transmitter { 10 | constructor(rfxcom, subtype, options) { 11 | super(rfxcom, subtype, options); 12 | this.packetType = "lighting3"; 13 | this.packetNumber = 0x12; 14 | } 15 | 16 | /* 17 | * Splits the device id into system code, channel number. 18 | * 19 | */ 20 | _splitDeviceId(deviceId) { 21 | let channelBytes = []; 22 | const parts = Transmitter.splitAtSlashes(deviceId); 23 | if (this.isSubtype('KOPPLA') && parts.length !== 2) { 24 | throw new Error("Invalid deviceId format"); 25 | } 26 | const 27 | systemCode = parseInt(parts[0], 10), 28 | channelNumber = parseInt(parts[1], 10); 29 | if (systemCode < 1 || systemCode > 16) { 30 | throw new Error("Invalid system code " + parts[0]); 31 | } 32 | if (channelNumber === 0) { 33 | channelBytes[0] = 0xff; 34 | channelBytes[1] = 0x03; 35 | } else { 36 | channelBytes = [0, 0]; 37 | if (channelNumber < 1 || channelNumber > 10) { 38 | throw new Error("Invalid channel number " + parts[1]); 39 | } else if (channelNumber > 8) { 40 | channelBytes[1] |= 1 << channelNumber - 9; 41 | } else { 42 | channelBytes[0] |= 1 << channelNumber - 1; 43 | } 44 | } 45 | return { 46 | systemCode: systemCode - 1, 47 | channelCode: channelBytes 48 | }; 49 | }; 50 | 51 | _sendCommand(deviceId, command, callback) { 52 | const device = this._splitDeviceId(deviceId); 53 | let buffer = [device.systemCode, device.channelCode[0], device.channelCode[1], command, 0]; 54 | 55 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 56 | }; 57 | 58 | /* 59 | * Switch on systemCode/Channel 60 | */ 61 | switchOn(deviceId, callback) { 62 | return this._sendCommand(deviceId, 0x10, callback); 63 | }; 64 | 65 | /* 66 | * Switch off systemCode/Channel 67 | */ 68 | switchOff(deviceId, callback) { 69 | return this._sendCommand(deviceId, 0x1A, callback); 70 | }; 71 | 72 | /* 73 | * Set brightness level systemCode/Channel 74 | */ 75 | setLevel(deviceId, level, callback) { 76 | if (level < 0 || level > 0xa) { 77 | throw new Error("Invalid level: value must be in range 0-10"); 78 | } 79 | if (level === 0) { 80 | return this._sendCommand(deviceId, 0x1A, callback); 81 | } else if (level === 0xa) { 82 | return this._sendCommand(deviceId, 0x10, callback); 83 | } else { 84 | return this._sendCommand(deviceId, 0x10 + level, callback); 85 | } 86 | }; 87 | 88 | /* 89 | * Increase brightness systemCode/Channel 'Bright' 90 | */ 91 | increaseLevel (deviceId, roomNumber, callback) { 92 | if (typeof roomNumber === "function") { 93 | callback = roomNumber; 94 | } 95 | return this._sendCommand(deviceId, 0x00, callback); 96 | }; 97 | 98 | /* 99 | * Decrease brightness systemCode/Channel 'Dim' 100 | */ 101 | decreaseLevel (deviceId, roomNumber, callback) { 102 | if (typeof roomNumber === "function") { 103 | callback = roomNumber; 104 | } 105 | return this._sendCommand(deviceId, 0x08, callback); 106 | }; 107 | 108 | /* 109 | * Send the 'Program' command systemCode/Channel 110 | */ 111 | program (deviceId, callback) { 112 | return this._sendCommand(deviceId, 0x1c, callback); 113 | }; 114 | 115 | } 116 | 117 | module.exports = Lighting3; 118 | 119 | 120 | -------------------------------------------------------------------------------- /lib/lighting4.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'); 5 | 6 | /* 7 | * This is a class for controlling Lighting4 lights. 8 | */ 9 | class Lighting4 extends Transmitter { 10 | constructor(rfxcom, subtype, options) { 11 | super(rfxcom, subtype, options); 12 | this.packetType = "lighting4"; 13 | this.packetNumber = 0x13; 14 | if (Array.isArray(this.options.defaultPulseWidth) === false) { 15 | this.options.defaultPulseWidth = [0x01, 0x5E]; 16 | } 17 | } 18 | 19 | /* 20 | * Send the specified data bytes (3-element array) and pulse width (2-element array) 21 | */ 22 | _sendCommand(data, pulseWidth, callback) { 23 | let buffer = [data[0], data[1], data[2], pulseWidth[0], pulseWidth[1], 0]; 24 | 25 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 26 | }; 27 | 28 | /* 29 | * Send the specified data - force it to be a 3-element numeric array 30 | */ 31 | sendData(data, pulseWidth, callback) { 32 | data = Lighting4.toByteArray(data, 3); 33 | if (pulseWidth === undefined || pulseWidth === null) { 34 | pulseWidth = this.options.defaultPulseWidth; 35 | } else { 36 | pulseWidth = Lighting4.toByteArray(pulseWidth, 2); 37 | } 38 | return this._sendCommand(data, pulseWidth, callback); 39 | }; 40 | 41 | static toByteArray(data, numBytes) { 42 | if (Array.isArray(data)) { 43 | data = data.reverse(); 44 | } else { 45 | if (typeof data === "string") { 46 | data = parseInt(data); 47 | } 48 | let residual = data; 49 | data = []; 50 | while (residual > 0) { 51 | data.push(residual%256); 52 | residual = Math.floor(residual/256); 53 | } 54 | } 55 | while (data.length < numBytes) { 56 | data.push(0); 57 | } 58 | data = data.reverse(); 59 | return data; 60 | }; 61 | 62 | } 63 | 64 | module.exports = Lighting4; 65 | 66 | -------------------------------------------------------------------------------- /lib/lighting6.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Lighting6 lights. 9 | */ 10 | class Lighting6 extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "lighting6"; 14 | this.packetNumber = 0x15; 15 | this.cmdseqnbr = -1; // Will be incremented by the first call to nextCmdseqbr() 16 | } 17 | 18 | nextCmdseqnbr() { 19 | this.cmdseqnbr = this.cmdseqnbr + 1; 20 | if ((this.isSubtype('BLYSS') && this.cmdseqnbr >= 5) || 21 | (this.isSubtype('CUVEO') && this.cmdseqnbr >= 255)) { 22 | this.cmdseqnbr = 0; 23 | } 24 | return this.cmdseqnbr; 25 | } 26 | /* 27 | * Splits the device id into idBytes, group, unitCode. 28 | * 29 | */ 30 | _splitDeviceId(deviceId) { 31 | const parts = Transmitter.splitAtSlashes(deviceId); 32 | if (this.isSubtype(['BLYSS', 'CUVEO']) && parts.length !== 3) { 33 | throw new Error("Invalid deviceId format"); 34 | } 35 | const 36 | id = RfxCom.stringToBytes(parts[0], 2), 37 | unit = parseInt(parts[2]); 38 | let group = parts[1]; 39 | if (this.isSubtype('BLYSS')) { 40 | group = group.toUpperCase().charCodeAt(0); 41 | if (group < 0x41 || group > 0x50) { 42 | throw new Error("Invalid group code '" + parts[1] + "'") 43 | } 44 | if (unit < 0 || unit > 5) { 45 | throw new Error("Invalid unit number " + unit) 46 | } 47 | } else if (this.isSubtype('CUVEO')) { 48 | group = parseInt(group); 49 | if (group < 0x0 || group > 0x03) { 50 | throw new Error("Invalid group code '" + parts[1] + "'") 51 | } 52 | if ((group === 0 && (unit < 1 || unit > 2)) || 53 | (group > 0 && (unit < 1 || unit > 8))) { 54 | throw new Error("Invalid unit number " + unit) 55 | } 56 | } 57 | return { 58 | idBytes: id.bytes, 59 | groupCode: group, 60 | unitCode: unit 61 | }; 62 | }; 63 | 64 | _sendCommand(deviceId, command, callback) { 65 | const device = this._splitDeviceId(deviceId); 66 | if (device.unitCode === 0) { 67 | command = command + 2; 68 | } 69 | let buffer = [device.idBytes[0], device.idBytes[1], device.groupCode, device.unitCode, 70 | command, this.nextCmdseqnbr(), 0, 0]; 71 | 72 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 73 | }; 74 | 75 | /* 76 | * Switch on deviceId/unitCode 77 | */ 78 | switchOn(deviceId, callback) { 79 | return this._sendCommand(deviceId, 0x00, callback); 80 | }; 81 | 82 | /* 83 | * Switch off deviceId/unitCode 84 | */ 85 | switchOff(deviceId, callback) { 86 | return this._sendCommand(deviceId, 0x01, callback); 87 | }; 88 | 89 | } 90 | 91 | module.exports = Lighting6; 92 | -------------------------------------------------------------------------------- /lib/radiator1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling Smartwares radiator valves 11 | */ 12 | class Radiator1 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "radiator1"; 16 | this.packetNumber = 0x48; 17 | }; 18 | 19 | /* 20 | * Extracts the device id & unit code 21 | * 22 | */ 23 | _splitDeviceId(deviceId) { 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (parts.length !== 2) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const id = RfxCom.stringToBytes(parts[0], 4); 29 | if (id.value < 1 || (this.isSubtype('SMARTWARES') && id.value > 0x3ffffff)) { 30 | Transmitter.addressError(id); 31 | } 32 | const unitCode = parseInt(parts[1]); 33 | if (unitCode < 1 || unitCode > 16) { 34 | throw new Error("Invalid unit code " + parts[1]); 35 | } 36 | return { 37 | idBytes: id.bytes, 38 | unitCode: unitCode 39 | }; 40 | }; 41 | 42 | _sendCommand(deviceId, command, setpoint, callback) { 43 | const device = this._splitDeviceId(deviceId); 44 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.idBytes[3], device.unitCode, 45 | command, setpoint[0], setpoint[1], 0]; 46 | 47 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 48 | }; 49 | 50 | setNightMode(deviceId, callback) { 51 | return this._sendCommand(deviceId, 0x00, [0x00, 0x00], callback); 52 | } 53 | 54 | setDayMode(deviceId, callback) { 55 | return this._sendCommand(deviceId, 0x01, [0x00, 0x00], callback); 56 | } 57 | 58 | setTemperature(deviceId, temperature, callback) { 59 | if (typeof temperature !== "number") { 60 | throw new Error("Invalid temperature: must be a number in range 5.0-28.0"); 61 | } 62 | const setpoint = Math.round(2*Math.max(Math.min(28.0, temperature), 5.0))/2; 63 | let setpointBytes = [Math.floor(setpoint), 10*(setpoint - Math.floor(setpoint))]; 64 | return this._sendCommand(deviceId, 0x02, setpointBytes, callback); 65 | } 66 | 67 | } 68 | 69 | module.exports = Radiator1; 70 | -------------------------------------------------------------------------------- /lib/rawtx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const defines = require('./defines'), 3 | Transmitter = require('./transmitter'); 4 | 5 | /* 6 | * This is a class for controlling Harrison curtain controllers.. 7 | */ 8 | class RawTx extends Transmitter { 9 | constructor(rfxcom, subtype, options) { 10 | // No real subtype for raw transmissions, use a dummy placeholder 11 | super(rfxcom, -1, options); 12 | this.packetType = "rawtx"; 13 | this.packetNumber = 0x7f; 14 | } 15 | 16 | isSubtype(subtypeName) { 17 | // we don't have a real subtype, so always return false 18 | return false; 19 | } 20 | 21 | static _stringToArray(str) { 22 | str = str.trim().toUpperCase(); 23 | if (str.indexOf(".") != -1 || str.indexOf("E") != -1) { 24 | throw new Error("Floating-point pulse times not allowed") 25 | } 26 | return str.split(/[ ,\t\n]+/).map(function(s) {return parseInt(s)}); 27 | } 28 | 29 | static _anyNonValid(arr) { 30 | return (arr.some(function(x) {return x <= 0 || x >= 65536})) 31 | } 32 | 33 | static _anyNonNumeric(arr) { 34 | return (arr.some(function(x) {return typeof x !== "number"})) 35 | } 36 | 37 | /* 38 | Send the message with the specified parameters, given by the fields of the params object: 39 | { 40 | repeats: 1-10 (defaults to 5)) 41 | pulseTimes: 42 | } 43 | */ 44 | sendMessage(deviceId, params, callback) { 45 | const pulsesPerPacket = 124, 46 | maxPackets = 4; 47 | if (callback === undefined && typeof deviceId == "object") { 48 | if (params === undefined) { 49 | params = deviceId; 50 | } else if (typeof params == "function") { 51 | callback = params; 52 | params = deviceId; 53 | } 54 | } 55 | if (typeof params === "function") { 56 | throw new Error("RawTx: Missing params") 57 | } 58 | if (params.repeats === undefined) { 59 | params.repeats = 5; 60 | } else if (params.repeats < 1 || params.repeats > 10) { 61 | throw new Error("RawTx: Invalid number of repeats: " + params.repeats); 62 | } 63 | if (params.pulseTimes === undefined) { 64 | throw new Error("RawTx: Missing params.pulseTimes"); 65 | } 66 | let pulseTimes; 67 | if (typeof params.pulseTimes === "string") { 68 | pulseTimes = RawTx._stringToArray(params.pulseTimes); 69 | } else if (params.pulseTimes instanceof Array) { 70 | pulseTimes = params.pulseTimes; 71 | } else { 72 | throw new Error("params.pulseTimes must be string or array") 73 | } 74 | if (RawTx._anyNonNumeric(pulseTimes)) { 75 | throw new Error("All pulse times must be numbers"); 76 | } 77 | if (RawTx._anyNonValid(pulseTimes)) { 78 | throw new Error("All pulse times must be in the range 1 to 65535"); 79 | } 80 | if (pulseTimes.length > pulsesPerPacket*maxPackets) { 81 | throw new Error("Too many pulse times, maximum " + pulsesPerPacket*maxPackets); 82 | } 83 | if (pulseTimes.length % 2 != 0) { 84 | throw new Error("Number of pulse times must be even"); 85 | } 86 | 87 | const numPackets = Math.ceil(pulseTimes.length/pulsesPerPacket) 88 | let pulseIdx = 0; 89 | for (let packetIdx = 0; packetIdx < numPackets; packetIdx++) { 90 | let buffer = [0]; 91 | while (pulseIdx < Math.min(pulseTimes.length, (packetIdx + 1)*pulsesPerPacket)) { 92 | buffer.push(Math.floor(pulseTimes[pulseIdx]/256), pulseTimes[pulseIdx]%256); 93 | pulseIdx++; 94 | } 95 | if (pulseIdx >= pulseTimes.length) { 96 | buffer[0] = params.repeats; 97 | } 98 | this.sendRaw(this.packetNumber, packetIdx, buffer, callback); 99 | }; 100 | } 101 | }; 102 | 103 | module.exports = RawTx; -------------------------------------------------------------------------------- /lib/remote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | index = require('./index'), 7 | Transmitter = require('./transmitter'), 8 | RfxCom = require('./rfxcom'); 9 | 10 | /* 11 | * This is a class for sending wireless remote control commands 12 | */ 13 | class Remote extends Transmitter { 14 | constructor(rfxcom, subtype, options) { 15 | super(rfxcom, subtype, options); 16 | this.packetType = "remote"; 17 | this.packetNumber = 0x30; 18 | }; 19 | 20 | /* 21 | * Extracts the device id. 22 | * 23 | */ 24 | _splitDeviceId(deviceId) { 25 | const parts = Transmitter.splitAtSlashes(deviceId); 26 | if (this.isSubtype(['ATI_REMOTE_WONDER', 'ATI_REMOTE_WONDER_PLUS', 'MEDION', 'X10_PC_REMOTE', 'ATI_REMOTE_WONDER_2']) && parts.length !== 1) { 27 | throw new Error("Invalid deviceId format"); 28 | } 29 | const id = RfxCom.stringToBytes(parts[0], 1); 30 | if (id.value < 0 || id.value > 0xff) { 31 | Transmitter.addressError(id); 32 | } 33 | return { 34 | idBytes: id.bytes 35 | }; 36 | }; 37 | 38 | _sendCommand(deviceId, command, callback) { 39 | const device = this._splitDeviceId(deviceId); 40 | let buffer = [device.idBytes[0], command, 0]; 41 | 42 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 43 | }; 44 | 45 | /* 46 | * General-purpose remote control button press. Pass the button string as a parameter, or a button number 47 | */ 48 | buttonPress(deviceId, button, callback) { 49 | if (this.isSubtype("ATI_REMOTE_WONDER_2")) { 50 | throw new Error("Device does not support buttonPress()"); 51 | } else if (typeof button === "number") { 52 | if (button >= 0 && button <= 255) { 53 | return this._sendCommand(deviceId, Math.round(button), callback); 54 | } else { 55 | throw new Error("Invalid button: value must be in range 0-255") 56 | } 57 | } else if (typeof button === "string") { 58 | // First look for an exact match 59 | const dict = index.remoteCommandDictionary(this.subtype); 60 | let buttonNumber = NaN; 61 | for (let key in dict) { 62 | if (dict.hasOwnProperty(key)) { 63 | if (dict[key] === button) { 64 | buttonNumber = Number(key); 65 | break; 66 | } 67 | } 68 | } 69 | if (isNaN(buttonNumber)) { 70 | // If the exact match fails, look for a case-insensitive match (risky, as a few button names differ only in case) 71 | const upperCaseButton = button.toUpperCase(); 72 | for (let key in dict) { 73 | if (dict.hasOwnProperty(key)) { 74 | if (dict[key].toUpperCase() === upperCaseButton) { 75 | buttonNumber = Number(key); 76 | break; 77 | } 78 | } 79 | } 80 | } 81 | if (isNaN(buttonNumber)) { 82 | throw new Error("Invalid button name '" + button + "'"); 83 | } else { 84 | return this._sendCommand(deviceId, buttonNumber, callback); 85 | } 86 | } 87 | }; 88 | 89 | } 90 | 91 | module.exports = Remote; 92 | -------------------------------------------------------------------------------- /lib/rfy.js: -------------------------------------------------------------------------------- 1 | /*jshint -W104 */ 2 | 'use strict'; 3 | const defines = require("./defines"), 4 | Transmitter = require('./transmitter'), 5 | RfxCom = require('./rfxcom'); 6 | 7 | /* 8 | * This is a class for controlling Somfy RTS blind motors (RFY protocol) 9 | */ 10 | class Rfy extends Transmitter { 11 | constructor(rfxcom, subtype, options) { 12 | super(rfxcom, subtype, options); 13 | this.packetType = "rfy"; 14 | this.packetNumber = 0x1a; 15 | if (this.rfxcom.listingRfyRemotes === undefined) { 16 | this.rfxcom.listingRfyRemotes = false; 17 | } 18 | if (this.options.venetianBlindsMode === undefined) { 19 | this.options.venetianBlindsMode = "EU"; 20 | } 21 | } 22 | 23 | _timeoutHandler(buffer, seqnbr) { 24 | if (this.rfxcom.listingRfyRemotes && this.seqnbr === seqnbr) { 25 | this.rfxcom.listingRfyRemotes = false; 26 | this.rfxcom.emit("rfyremoteslist", this.rfxcom.rfyRemotesList); 27 | this.rfxcom.rfyRemotesList = []; 28 | return true; 29 | } else { 30 | return false; 31 | } 32 | }; 33 | 34 | /* 35 | * Splits the device ID into the ID bytes and unit code 36 | * Throw an error for invalid ID 37 | */ 38 | _splitDeviceId(deviceId) { 39 | const parts = Transmitter.splitAtSlashes(deviceId); 40 | if (parts.length !== 2) { 41 | throw new Error("Invalid deviceId format"); 42 | } 43 | const 44 | id = RfxCom.stringToBytes(parts[0], 3), 45 | unitCode = parseInt(parts[1]); 46 | if (id.value < 1 || id.value > 0xfffff) { 47 | Transmitter.addressError(id); 48 | } 49 | if ((this.isSubtype("RFY") && (unitCode < 0x00 || unitCode > 0x04)) || 50 | (this.isSubtype("RFYEXT") && (unitCode < 0x00 || unitCode > 0x0f)) || 51 | (this.isSubtype("ASA") && (unitCode < 0x01 || unitCode > 0x05))) { 52 | throw new Error("Invalid unit code " + parts[1]); 53 | } 54 | return { 55 | idBytes: id.bytes, 56 | unitCode: unitCode 57 | }; 58 | }; 59 | 60 | /* 61 | * Called by the API commands to encode & queue the underlying byte sequence 62 | */ 63 | _sendCommand(deviceId, command, callback) { 64 | if (this.isSubtype("RFY_RESERVED")) { 65 | throw new Error("RFY subtype GEOM no longer supported"); 66 | } 67 | const device = this._splitDeviceId(deviceId); 68 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], device.unitCode, command, 0, 0, 0, 0]; 69 | let options = {}; 70 | const seqnbr = this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 71 | if (this.rfxcom.listingRfyRemotes) { 72 | this.seqnbr = seqnbr; 73 | this.rfxcom.rfyRemotesList = []; 74 | } 75 | return seqnbr; 76 | }; 77 | 78 | /* 79 | * General-purpose 'send any command' method - DOES NOT check the command is valid for the subtype! 80 | */ 81 | doCommand(deviceId, command, callback) { 82 | if (typeof command === "number") { 83 | command = Math.round(command); 84 | if (command >= 0 && command <= defines.LastRfyCommand) { 85 | return this._sendCommand(deviceId, command, callback); 86 | } else { 87 | throw new Error("Invalid command number " + command); 88 | } 89 | } else if (typeof command === "string") { 90 | if (typeof defines.RfyCommands[command] !== "undefined") { 91 | if (defines.RfyCommands[command] === defines.RfyCommands.listRemotes) { 92 | return this.listRemotes(callback); 93 | } else { 94 | return this._sendCommand(deviceId, defines.RfyCommands[command], callback); 95 | } 96 | } else { 97 | throw new Error("Unknown command '" + command + "'"); 98 | } 99 | } 100 | }; 101 | 102 | /* 103 | * 'Meta-commands' to manage the list of remotes in the RFXtrx433E 104 | */ 105 | erase(deviceId, callback) { 106 | return this._sendCommand(deviceId, defines.RfyCommands.erasethis, callback); 107 | }; 108 | 109 | eraseAll(callback) { 110 | // Use a fake deviceId which is valid for all subtypes 111 | return this._sendCommand([1, 1], defines.RfyCommands.eraseall, callback); 112 | }; 113 | 114 | listRemotes(callback) { 115 | if (this.rfxcom.listingRfyRemotes) { 116 | this.rfxcom.debugLog("Error : RFY listRemotes command received while previous list operation in progress"); 117 | return -1; 118 | } else { 119 | this.rfxcom.listingRfyRemotes = true; 120 | // Use a fake deviceId which is valid for all subtypes 121 | return this._sendCommand([1, 1], defines.RfyCommands.listRemotes, callback); 122 | } 123 | }; 124 | 125 | program(deviceId, callback) { 126 | return this._sendCommand(deviceId, defines.RfyCommands.program, callback); 127 | }; 128 | 129 | /* 130 | * Public API commands 131 | */ 132 | up(deviceId, callback) { 133 | return this._sendCommand(deviceId, defines.RfyCommands.up, callback); 134 | }; 135 | 136 | down(deviceId, callback) { 137 | return this._sendCommand(deviceId, defines.RfyCommands.down, callback); 138 | }; 139 | 140 | stop(deviceId, callback) { 141 | return this._sendCommand(deviceId, defines.RfyCommands.stop, callback); 142 | }; 143 | 144 | venetianOpen(deviceId, callback) { 145 | if (this.isSubtype("ASA")) { 146 | throw new Error("ASA: Venetian blinds commands not supported"); 147 | } 148 | if (this.options.venetianBlindsMode === "US") { 149 | return this._sendCommand(deviceId, defines.RfyCommands.up05sec, callback); 150 | } else { 151 | return this._sendCommand(deviceId, defines.RfyCommands.up2sec, callback); 152 | } 153 | }; 154 | 155 | venetianClose(deviceId, callback) { 156 | if (this.isSubtype("ASA")) { 157 | throw new Error("ASA: Venetian blinds commands not supported"); 158 | } 159 | if (this.options.venetianBlindsMode === "US") { 160 | return this._sendCommand(deviceId, defines.RfyCommands.down05sec, callback); 161 | } else { 162 | return this._sendCommand(deviceId, defines.RfyCommands.down2sec, callback); 163 | } 164 | }; 165 | 166 | venetianIncreaseAngle(deviceId, callback) { 167 | if (this.isSubtype("ASA")) { 168 | throw new Error("ASA: Venetian blinds commands not supported"); 169 | } 170 | if (this.options.venetianBlindsMode === "US") { 171 | return this._sendCommand(deviceId, defines.RfyCommands.up2sec, callback); 172 | } else { 173 | return this._sendCommand(deviceId, defines.RfyCommands.up05sec, callback); 174 | } 175 | }; 176 | 177 | venetianDecreaseAngle(deviceId, callback) { 178 | if (this.isSubtype("ASA")) { 179 | throw new Error("ASA: Venetian blinds commands not supported"); 180 | } 181 | if (this.options.venetianBlindsMode === "US") { 182 | return this._sendCommand(deviceId, defines.RfyCommands.down2sec, callback); 183 | } else { 184 | return this._sendCommand(deviceId, defines.RfyCommands.down05sec, callback); 185 | } 186 | }; 187 | 188 | enableSunSensor(deviceId, callback) { 189 | return this._sendCommand(deviceId, defines.RfyCommands.sunwindenable, callback); 190 | }; 191 | 192 | disableSunSensor(deviceId, callback) { 193 | return this._sendCommand(deviceId, defines.RfyCommands.sundisable, callback); 194 | }; 195 | 196 | } 197 | 198 | module.exports = Rfy; 199 | -------------------------------------------------------------------------------- /lib/security1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling multiple types of autmated blinds 11 | */ 12 | class Security1 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "security1"; 16 | this.packetNumber = 0x20; 17 | }; 18 | 19 | /* 20 | * Splits the device id and returns the components. 21 | * Throws an Error if the format is invalid. 22 | */ 23 | _splitDeviceId(deviceId) { 24 | let id = {}; 25 | const parts = Transmitter.splitAtSlashes(deviceId); 26 | if (parts.length === 1) { 27 | if (this.isSubtype(['KD101', 'SA30'])) { 28 | id = RfxCom.stringToBytes(parts[0], 2); 29 | if (id.value <= 0xffff) { 30 | id.bytes.push(0); 31 | } else { 32 | Transmitter.addressError(id); 33 | } 34 | } else if (this.isSubtype(['X10_DOOR', 'X10_PIR', 'X10_SECURITY'])) { 35 | id = RfxCom.stringToBytes(parts[0], 2); 36 | if (id.value <= 0xffff) { 37 | id.bytes = [id.bytes[0], 0, id.bytes[1]]; 38 | } else { 39 | Transmitter.addressError(id); 40 | } 41 | } else if (this.isSubtype(['RM174RF', 'FERPORT_TAC'])) { 42 | id = RfxCom.stringToBytes(parts[0], 3); 43 | if (id.value < 1 || id.value > 0xffffff) { 44 | Transmitter.addressError(id); 45 | } 46 | } else { 47 | id = RfxCom.stringToBytes(parts[0], 3); 48 | if (id.value < 0 || id.value > 0xffffff) { 49 | Transmitter.addressError(id); 50 | } 51 | } 52 | } else { 53 | throw new Error("Invalid deviceId format"); 54 | } 55 | return { 56 | idBytes: id.bytes, 57 | }; 58 | }; 59 | 60 | _sendCommand(deviceId, status, callback) { 61 | const device = this._splitDeviceId(deviceId); 62 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], status, 0]; 63 | 64 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 65 | }; 66 | 67 | /* 68 | * Sends a message reporting the supplied status (0 - 255), simulating a security detector. 69 | * The status value is not error-checked, refer to the SDK documentation for the allowed values for each subtype 70 | */ 71 | sendStatus(deviceId, status, callback) { 72 | return this._sendCommand(deviceId, Math.round(status), callback); 73 | }; 74 | 75 | sendPanic(deviceId, callback) { 76 | if (this.isSubtype("X10_SECURITY")) { 77 | return this._sendCommand(deviceId, 0x6, callback); 78 | } else { 79 | throw new Error("Device does not support sendPanic()"); 80 | } 81 | }; 82 | 83 | cancelPanic(deviceId, callback) { 84 | if (this.isSubtype("X10_SECURITY")) { 85 | return this._sendCommand(deviceId, 0x7, callback); 86 | } else { 87 | throw new Error("Device does not support cancelPanic()"); 88 | } 89 | }; 90 | 91 | armSystemAway(deviceId, callback) { 92 | if (this.isSubtype("X10_SECURITY")) { 93 | return this._sendCommand(deviceId, 0x9, callback); 94 | } else { 95 | throw new Error("Device does not support armSystemAway()"); 96 | } 97 | }; 98 | 99 | armSystemAwayWithDelay(deviceId, callback) { 100 | if (this.isSubtype("X10_SECURITY")) { 101 | return this._sendCommand(deviceId, 0xa, callback); 102 | } else { 103 | throw new Error("Device does not support armSystemAwayWithDelay()"); 104 | } 105 | }; 106 | 107 | armSystemHome(deviceId, callback) { 108 | if (this.isSubtype("X10_SECURITY")) { 109 | return this._sendCommand(deviceId, 0xb, callback); 110 | } else { 111 | throw new Error("Device does not support armSystemHome()"); 112 | } 113 | }; 114 | 115 | armSystemHomeWithDelay(deviceId, callback) { 116 | if (this.isSubtype("X10_SECURITY")) { 117 | return this._sendCommand(deviceId, 0xc, callback); 118 | } else { 119 | throw new Error("Device does not support armSystemHomeWithDelay()"); 120 | } 121 | }; 122 | 123 | disarmSystem(deviceId, callback) { 124 | if (this.isSubtype("X10_SECURITY")) { 125 | return this._sendCommand(deviceId, 0xd, callback); 126 | } else { 127 | throw new Error("Device does not support disarmSystem()"); 128 | } 129 | }; 130 | 131 | switchLightOn(deviceId, channel, callback) { 132 | if (this.isSubtype("X10_SECURITY")) { 133 | if (channel === 1 || channel === 2) { 134 | return this._sendCommand(deviceId, 0xf + 2*channel, callback); 135 | } else { 136 | throw new Error("Invalid channel: value must be in range 1-2"); 137 | } 138 | } else { 139 | throw new Error("Device does not support switchOnLight()"); 140 | } 141 | }; 142 | 143 | switchLightOff(deviceId, channel, callback) { 144 | if (this.isSubtype("X10_SECURITY")) { 145 | if (channel === 1 || channel === 2) { 146 | return this._sendCommand(deviceId, 0xe + 2*channel, callback); 147 | } else { 148 | throw new Error("Invalid channel: value must be in range 1-2"); 149 | } 150 | } else { 151 | throw new Error("Device does not support switchOffLight()"); 152 | } 153 | }; 154 | 155 | // Call-throughs to (temporarily) support deprecated function names 156 | switchOnLight(deviceId, channel, callback) { 157 | return this.switchLightOn(deviceId, channel, callback); 158 | }; 159 | switchOffLight(deviceId, channel, callback) { 160 | return this.switchLightOff(deviceId, channel, callback); 161 | }; 162 | } 163 | 164 | module.exports = Security1; -------------------------------------------------------------------------------- /lib/thermostat1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const defines = require("./defines"), 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling Digimax & TLX7506 thermostats 11 | */ 12 | class Thermostat1 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "thermostat1"; 16 | this.packetNumber = 0x40; 17 | }; 18 | 19 | /* 20 | * Extracts the device id. 21 | * 22 | */ 23 | _splitDeviceId(deviceId) { 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (this.isSubtype(['DIGIMAX_TLX7506', 'DIGIMAX_SHORT']) && parts.length !== 1) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const id = RfxCom.stringToBytes(parts[0], 2); 29 | if (id.value < 0 || id.value > 0xffff) { 30 | Transmitter.addressError(id); 31 | } 32 | return { 33 | idBytes: id.bytes 34 | }; 35 | }; 36 | 37 | _sendCommand(deviceId, temperature, setpoint, ctrl, callback) { 38 | const device = this._splitDeviceId(deviceId); 39 | let buffer = [device.idBytes[0], device.idBytes[1], temperature, setpoint, ctrl, 0]; 40 | 41 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 42 | }; 43 | 44 | sendMessage(deviceId, params, callback) { 45 | if (params === undefined || typeof params === "function") { 46 | throw new Error("Missing params") 47 | } 48 | let _params = Object.create(params || null); 49 | if (this.isSubtype('DIGIMAX_SHORT')) { 50 | _params.setpoint = 0; 51 | } else if (_params.setpoint === undefined || _params.setpoint < 5 || _params.setpoint > 45) { 52 | throw new Error("Invalid parameter setpoint: must be in range 5-45"); 53 | } else { 54 | _params.setpoint = Math.round(_params.setpoint); 55 | } 56 | if (_params.temperature === undefined || _params.temperature < 0 || _params.temperature > 50) { 57 | throw new Error("Invalid parameter temperature: must be in range 0-50"); 58 | } else { 59 | _params.temperature = Math.round(_params.temperature); 60 | } 61 | if (typeof _params.status === "string") { 62 | _params.status = defines.THERMOSTAT1_STATUS.map(x => x.toUpperCase()).indexOf(_params.status.toUpperCase()); 63 | } 64 | if (_params.status !== 0 && _params.status !== 1 && _params.status !== 2 && _params.status !== 3) { 65 | throw new Error("Invalid parameter status: must be in range 0-3"); 66 | } 67 | if (typeof _params.mode === "string") { 68 | _params.mode = defines.THERMOSTAT1_MODE.map(x => x.toUpperCase()).indexOf(_params.mode.toUpperCase()); 69 | } 70 | if (_params.mode !== 0 && _params.mode !== 1) { 71 | throw new Error("Invalid parameter mode: must be 0 or 1"); 72 | } 73 | const ctrl = _params.status | (_params.mode << 7); 74 | return this._sendCommand(deviceId, _params.temperature, _params.setpoint, ctrl, callback); 75 | } 76 | 77 | } 78 | 79 | module.exports = Thermostat1; 80 | -------------------------------------------------------------------------------- /lib/thermostat2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling 11 | */ 12 | class Thermostat2 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "thermostat2"; 16 | this.packetNumber = 0x41; 17 | }; 18 | 19 | /* 20 | * Extracts the device id. 21 | * 22 | */ 23 | _splitDeviceId(deviceId) { 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (this.isSubtype(['HE105', 'RTS10_RFS10_TLX1206']) && parts.length !== 1) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const id = RfxCom.stringToBytes(parts[0], 1); 29 | if (id.value < 0 || id.value > 0x1f) { 30 | Transmitter.addressError(id); 31 | } 32 | return { 33 | idBytes: id.bytes 34 | }; 35 | }; 36 | 37 | _sendCommand(deviceId, command, callback) { 38 | const device = this._splitDeviceId(deviceId); 39 | let buffer = [device.idBytes[0], command, 0]; 40 | 41 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 42 | }; 43 | 44 | switchOn(deviceId, callback) { 45 | return this._sendCommand(deviceId, 0x01, callback); 46 | } 47 | 48 | switchOff(deviceId, callback) { 49 | return this._sendCommand(deviceId, 0x00, callback); 50 | } 51 | 52 | program(deviceId, callback) { 53 | if (this.isSubtype('RTS10_RFS10_TLX1206')) { 54 | return this._sendCommand(deviceId, 0x02, callback); 55 | } else { 56 | throw new Error("Device does not support program()") 57 | } 58 | } 59 | } 60 | 61 | module.exports = Thermostat2; 62 | -------------------------------------------------------------------------------- /lib/thermostat3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling thermostat3 devices 11 | */ 12 | class Thermostat3 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "thermostat3"; 16 | this.packetNumber = 0x42; 17 | }; 18 | 19 | /* 20 | * Extracts the device id. 21 | * 22 | */ 23 | _splitDeviceId(deviceId) { 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (parts.length !== 1) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const id = RfxCom.stringToBytes(parts[0], 3); 29 | if (id.value < 0 || 30 | (this.isSubtype(["G6R_H4T1", "G6R_H3T1"]) && id.value > 0xff) || 31 | (this.isSubtype(["G6R_H4TB", "G6R_H4S"]) && id.value > 0x3ffff) || 32 | (this.isSubtype("G6R_H4TD") && id.value > 0xffff)) { 33 | Transmitter.addressError(id); 34 | } 35 | return { 36 | idBytes: id.bytes 37 | }; 38 | }; 39 | 40 | _sendCommand(deviceId, command, callback) { 41 | const device = this._splitDeviceId(deviceId); 42 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], command, 0]; 43 | 44 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 45 | }; 46 | 47 | switchOn(deviceId, callback) { 48 | return this._sendCommand(deviceId, 0x01, callback); 49 | } 50 | 51 | switchOff(deviceId, callback) { 52 | return this._sendCommand(deviceId, 0x00, callback); 53 | } 54 | 55 | switchOn2(deviceId, callback) { 56 | if (this.isSubtype("G6R_H4TB")) { 57 | return this._sendCommand(deviceId, 0x05, callback); 58 | } else { 59 | throw new Error("Device does not support switchOn2()"); 60 | } 61 | } 62 | 63 | switchOff2(deviceId, callback) { 64 | if (this.isSubtype("G6R_H4TB")) { 65 | return this._sendCommand(deviceId, 0x04, callback); 66 | } else { 67 | throw new Error("Device does not support switchOff2()"); 68 | } 69 | } 70 | 71 | up(deviceId, callback) { 72 | return this._sendCommand(deviceId, 0x02, callback); 73 | } 74 | 75 | down(deviceId, callback) { 76 | return this._sendCommand(deviceId, 0x03, callback); 77 | } 78 | 79 | runUp(deviceId, callback) { 80 | if (this.isSubtype(["G6R_H4T1", "G6R_H3T1", "G6R_H4TD"])) { 81 | return this._sendCommand(deviceId, 0x04, callback); 82 | } else { 83 | throw new Error("Device does not support runUp()"); 84 | } 85 | } 86 | 87 | runDown(deviceId, callback) { 88 | if (this.isSubtype(["G6R_H4T1", "G6R_H3T1", "G6R_H4TD"])) { 89 | return this._sendCommand(deviceId, 0x05, callback); 90 | } else { 91 | throw new Error("Device does not support runDown()"); 92 | } 93 | } 94 | 95 | stop(deviceId, callback) { 96 | if (this.isSubtype(["G6R_H4T1", "G6R_H3T1"])) { 97 | return this._sendCommand(deviceId, 0x06, callback); 98 | } else { 99 | throw new Error("Device does not support stop()"); 100 | } 101 | } 102 | 103 | } 104 | 105 | module.exports = Thermostat3; 106 | -------------------------------------------------------------------------------- /lib/thermostat4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 11/07/2017. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling MCZ pellet stoves 11 | */ 12 | class Thermostat4 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "thermostat4"; 16 | this.packetNumber = 0x43; 17 | }; 18 | 19 | _splitDeviceId(deviceId) { 20 | const parts = Transmitter.splitAtSlashes(deviceId); 21 | if (this.isSubtype(['MCZ_PELLET_STOVE_1_FAN', 'MCZ_PELLET_STOVE_2_FAN', 'MCZ_PELLET_STOVE_3_FAN']) && parts.length !== 1) { 22 | throw new Error("Invalid deviceId format") 23 | } 24 | const id = RfxCom.stringToBytes(parts[0], 3); 25 | if (id.value < 1 || id.value > 0xffffff) { 26 | Transmitter.addressError(id); 27 | } 28 | return { 29 | idBytes: id.bytes 30 | }; 31 | }; 32 | 33 | _sendCommand(deviceId, beep, fan1Speed, fan23Speed, flamePower, mode, callback) { 34 | const device = this._splitDeviceId(deviceId); 35 | let buffer = [device.idBytes[0], device.idBytes[1], device.idBytes[2], beep, fan1Speed, fan23Speed, flamePower, mode, 0]; 36 | 37 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 38 | }; 39 | 40 | /* 41 | Send the message with the specified parameters, given by the fields of the params object: 42 | { 43 | beep: Optional, defaults to true 44 | fanSpeed:[0-6, 0-6, 0-6] Optional, one speed for each fan, defaults to 6 (auto), excess ignored, "auto" --> 6 45 | flamePower:<1, 2, 3, 4, 5> Mandatory 46 | mode:<0, 1, 2, 3>, or 47 | <"Off", "Manual", "Auto", "Eco"> (case-insensitive, 3 characters needed) 48 | Mandatory 49 | } 50 | */ 51 | sendMessage(deviceId, params, callback) { 52 | const 53 | numFans = [1, 2, 3][this.subtype]; 54 | if (params === undefined || typeof params === "function") { 55 | throw new Error("Missing params") 56 | } 57 | let _params = Object.create(params || null); 58 | if (_params.beep === undefined) { 59 | _params.beep = true; 60 | } else { 61 | _params.beep = Boolean(_params.beep); 62 | } 63 | if (_params.fanSpeed === undefined) { // Set all fan speeds to 6 ("auto") 64 | _params.fanSpeed = [6, 6, 6]; 65 | } else if (!Array.isArray(_params.fanSpeed)) { 66 | _params.fanSpeed = [_params.fanSpeed]; 67 | } 68 | // delete excess fan speeds 69 | _params.fanSpeed = _params.fanSpeed.slice(0, numFans); 70 | // each speed must be a valid number, or it is forced to 6 ("auto") 71 | // _params.fanSpeed = _params.fanSpeed.map((speed) => 72 | // {return (typeof speed === "number" && speed >= 0 && speed <= 6) ? Math.round(speed) : 6}); 73 | for (let i = 0; i < numFans; i++) { 74 | _params.fanSpeed[i] = (typeof _params.fanSpeed[i] === "number" && _params.fanSpeed[i] >= 0 && _params.fanSpeed[i] <= 6) ? Math.round(_params.fanSpeed[i]) : 6; 75 | } 76 | // if too few fan speeds specified, make the unspecified speeds 6 ("auto") 77 | if (_params.fanSpeed.length < numFans) { 78 | for (let i = 0; i < numFans - _params.fanSpeed.length; i++) { 79 | _params.fanSpeed.push(6); 80 | } 81 | } 82 | if (_params.flamePower === undefined || typeof _params.flamePower !== "number" || 83 | _params.flamePower < 1 || _params.flamePower > 5) { 84 | throw new Error("Invalid parameter flamePower: must be in range 1-5"); 85 | } else { 86 | _params.flamePower = Math.round(_params.flamePower); 87 | } 88 | if (_params.mode === undefined) { 89 | throw new Error("mode parameter must be specified"); 90 | } else if (typeof _params.mode === "string") { 91 | if (/Off/i.test(_params.mode)) { 92 | _params.mode = 0x0; 93 | } else if (/Man/i.test(_params.mode)) { 94 | _params.mode = 0x1; 95 | } else if (/Aut/i.test(_params.mode)) { 96 | _params.mode = 0x2; 97 | } else if (/Eco/i.test(_params.mode)) { 98 | _params.mode = 0x3; 99 | } else { 100 | throw new Error("Invalid parameter mode: '" + _params.mode + "'"); 101 | } 102 | } else if (typeof _params.mode !== "number" || _params.mode < 0 || _params.mode > 3) { 103 | throw new Error("Invalid parameter mode: must be in range 0-3"); 104 | } else { 105 | _params.mode = Math.round(_params.mode); 106 | } 107 | const 108 | beep = _params.beep ? 1 : 0, 109 | fan1Speed = _params.fanSpeed[0], 110 | fan23Speed = (numFans > 1 ? _params.fanSpeed[1] : 0x01) | 111 | (numFans > 2 ? _params.fanSpeed[2] << 4 : 0x10), 112 | flamePower = _params.flamePower, 113 | mode = _params.mode; 114 | return this._sendCommand(deviceId, beep, fan1Speed, fan23Speed, flamePower, mode, callback); 115 | }; 116 | 117 | } 118 | 119 | module.exports = Thermostat4; 120 | -------------------------------------------------------------------------------- /lib/thermostat5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 01/01/2023. 3 | */ 4 | 'use strict'; 5 | const 6 | Transmitter = require('./transmitter'), 7 | RfxCom = require('./rfxcom'); 8 | 9 | /* 10 | * This is a class for controlling thermostat5 devices 11 | */ 12 | class Thermostat5 extends Transmitter { 13 | constructor(rfxcom, subtype, options) { 14 | super(rfxcom, subtype, options); 15 | this.packetType = "thermostat5"; 16 | this.packetNumber = 0x44; 17 | }; 18 | 19 | /* 20 | * Extracts the device id. 21 | * 22 | */ 23 | _splitDeviceId(deviceId) { 24 | const parts = Transmitter.splitAtSlashes(deviceId); 25 | if (parts.length !== 1) { 26 | throw new Error("Invalid deviceId format"); 27 | } 28 | const id = RfxCom.stringToBytes(parts[0], 2); 29 | if (id.value < 1 || id.value > 0xffff) { 30 | Transmitter.addressError(id); 31 | } 32 | return { 33 | idBytes: id.bytes 34 | }; 35 | }; 36 | 37 | _sendCommand(deviceId, mode, flameColour, flameBrightness, fuelColour, fuelBrightness, callback) { 38 | const device = this._splitDeviceId(deviceId); 39 | let buffer = [device.idBytes[0], device.idBytes[1], mode, flameColour, 40 | flameBrightness, fuelColour, fuelBrightness, 0, 0, 0]; 41 | 42 | return this.sendRaw(this.packetNumber, this.subtype, buffer, callback); 43 | }; 44 | 45 | /* 46 | Send the message with the specified parameters, given by the fields of the params object: 47 | { 48 | mode:<0, 1, 2, 3, 4>, or 49 | <"Off", "LED", "Standby", "Low", "High"> (case-insensitive, 3 characters needed) 50 | Mandatory 51 | flameBrightness: 0-5 (0 is off) 52 | flameColour: 1-3 53 | fuelBrightness: 0-5 (0 is off) 54 | fuelColour: 1-14 (14 is cycle) 55 | } 56 | */ 57 | sendMessage(deviceId, params, callback) { 58 | if (params === undefined || typeof params === "function") { 59 | throw new Error("Missing params") 60 | } 61 | let mode = 0; 62 | if (params.mode === undefined) { 63 | throw new Error("mode parameter must be specified"); 64 | } else if (typeof params.mode === "string") { 65 | if (/Off/i.test(params.mode)) { 66 | mode = 0x0; 67 | } else if (/LED/i.test(params.mode)) { 68 | mode = 0x1; 69 | } else if (/Sta/i.test(params.mode)) { 70 | mode = 0x2; 71 | } else if (/Low/i.test(params.mode)) { 72 | mode = 0x3; 73 | } else if (/Hig/i.test(params.mode)) { 74 | mode = 0x4; 75 | } else { 76 | throw new Error("Invalid parameter mode: '" + params.mode + "'"); 77 | } 78 | } else if (typeof params.mode !== "number" || params.mode < 0 || params.mode > 4) { 79 | throw new Error("Invalid parameter mode: must be in range 0-4"); 80 | } else { 81 | mode = Math.round(params.mode); 82 | } 83 | let flameBrightness = 0; // Default to off 84 | if (params.flameBrightness !== undefined) { 85 | if (typeof params.flameBrightness !== "number" || params.flameBrightness < 0 || params.flameBrightness > 5) { 86 | throw new Error("Invalid parameter flameBrightness: must be in range 0-5"); 87 | } else { 88 | flameBrightness = Math.round(params.flameBrightness); 89 | } 90 | } 91 | let flameColour = 1; // Default to 1 92 | if (params.flameColour !== undefined) { 93 | if (typeof params.flameColour !== "number" || params.flameColour < 1 || params.flameColour > 3) { 94 | throw new Error("Invalid parameter flameColour: must be in range 1-3"); 95 | } else { 96 | flameColour = Math.round(params.flameColour); 97 | } 98 | } 99 | let fuelBrightness = 0; // Default to off 100 | if (params.fuelBrightness !== undefined) { 101 | if (typeof params.fuelBrightness !== "number" || params.fuelBrightness < 0 || params.fuelBrightness > 5) { 102 | throw new Error("Invalid parameter fuelBrightness: must be in range 0-5"); 103 | } else { 104 | fuelBrightness = Math.round(params.fuelBrightness); 105 | } 106 | } 107 | let fuelColour = 14 // Default to Cycle 108 | if (params.fuelColour !== undefined) { 109 | if (typeof params.fuelColour !== "number" || params.fuelColour < 1 || params.fuelColour > 14) { 110 | throw new Error("Invalid parameter fuelColour: must be in range 1-14"); 111 | } else { 112 | fuelColour = Math.round(params.fuelColour); 113 | } 114 | } 115 | return this._sendCommand(deviceId, mode, flameColour, flameBrightness, fuelColour, fuelBrightness, callback) 116 | } 117 | 118 | } 119 | 120 | module.exports = Thermostat5; 121 | -------------------------------------------------------------------------------- /lib/transmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 01/07/2017. 3 | */ 4 | 5 | 'use strict'; 6 | const index = require('./index'); // Gives access to the subtype names 7 | 8 | /* 9 | * A base class from which packet transmitters inherit, incorporating a couple of handy methods 10 | */ 11 | class Transmitter { 12 | constructor(rfxcom, subtype, options) { 13 | if (typeof subtype === "undefined") { 14 | throw new Error("Must provide a subtype."); 15 | } 16 | this.rfxcom = rfxcom; 17 | this.packetType = ""; 18 | this.packetNumber = NaN; 19 | this.subtype = subtype; 20 | this.options = options || {}; 21 | } 22 | 23 | /* 24 | * Returns true if the subtype matches the supplied subtypeName, of if it matches any one of the names supplied 25 | * in an array of subtype names 26 | */ 27 | isSubtype(subtypeName) { 28 | if (Array.isArray(subtypeName)) { 29 | const self = this; 30 | return !subtypeName.every(name => index[self.packetType][name] !== self.subtype) 31 | } else { 32 | return index[this.packetType][subtypeName] === this.subtype; 33 | } 34 | }; 35 | 36 | /* 37 | * Called by the transmission queue handler if no response is received 38 | */ 39 | // noinspection JSMethodCanBeStatic 40 | _timeoutHandler(/*buffer, seqnbr*/) { 41 | return false; 42 | }; 43 | 44 | setOption(newOptions) { 45 | for (let key in newOptions) { 46 | if (newOptions.hasOwnProperty(key)) { 47 | this.options[key] = newOptions[key]; 48 | } 49 | } 50 | } 51 | 52 | /* 53 | * Send the given buffer as a message with the specified type & subtype - no error-checking! 54 | */ 55 | sendRaw(packetNumber, subtype, buffer, callback) { 56 | const 57 | self = this, 58 | seqnbr = self.rfxcom.nextMessageSequenceNumber(), 59 | len = buffer.unshift(packetNumber, subtype, seqnbr); 60 | buffer.unshift(len); 61 | self.rfxcom.queueMessage(self, buffer, seqnbr, callback); 62 | return seqnbr; 63 | } 64 | 65 | static splitAtSlashes(deviceId) { 66 | if (Array.isArray(deviceId)) { 67 | return deviceId; 68 | } else { 69 | return deviceId.split("/"); 70 | } 71 | } 72 | 73 | static addressError(id) { 74 | throw new Error("Address " + (Number(id.value)< 0 ? "-" : "") + "0x" + Math.abs(Number(id.value)).toString(16) + " outside valid range"); 75 | } 76 | 77 | static deviceIdError(id) { 78 | throw new Error("Device ID 0x" + Number(id.value).toString(16) + " outside valid range"); 79 | } 80 | 81 | static remoteIdError(id) { 82 | throw new Error("Remote ID 0x" + Number(id.value).toString(16) + " outside valid range"); 83 | } 84 | 85 | } 86 | 87 | module.exports = Transmitter; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfxcom", 3 | "version": "2.6.2", 4 | "contributors": [ 5 | "Max Hadley ", 6 | "Kevin McDermott " 7 | ], 8 | "engines": { 9 | "node": ">=14.21.2" 10 | }, 11 | "dependencies": { 12 | "date-format": "^4.0.14", 13 | "queue": "^6.0.2", 14 | "serialport": "^11.0.1" 15 | }, 16 | "devDependencies": { 17 | "jasmine": "^4.5.0", 18 | "jshint": "^2.11.1" 19 | }, 20 | "scripts": { 21 | "test": "jasmine", 22 | "watch": "./node_modules/.bin/jasmine-node --autotest .", 23 | "find-rfxcom": "./node_modules/.bin/find-rfxcom", 24 | "set-protocols": "./node_modules/.bin/set-protocols" 25 | }, 26 | "bin": { 27 | "find-rfxcom": "./bin/find-rfxcom.js", 28 | "set-protocols": "./bin/set-protocols.js" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "http://github.com/rfxcom/node-rfxcom.git" 33 | }, 34 | "license": "MIT", 35 | "keywords": [ 36 | "rfxcom", 37 | "domotic", 38 | "home automation", 39 | "RFXtrx433" 40 | ], 41 | "bugs": { 42 | "url": "https://github.com/rfxcom/node-rfxcom/issues" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "test", 3 | "spec_files": [ 4 | "**/*[sS]pec.?(m)js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.?(m)js" 8 | ], 9 | "env": { 10 | "stopSpecOnExpectationFailure": false, 11 | "random": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/asyncConfig.spec.js: -------------------------------------------------------------------------------- 1 | const rfxcom = require('../lib'), 2 | util = require('util'), 3 | matchers = require('./matchers'), 4 | FakeSerialPort = require('./helper'); 5 | 6 | describe('AsyncConfig class', function () { 7 | let asyncConfig, 8 | fakeSerialPort, 9 | device; 10 | beforeEach(function () { 11 | jasmine.addMatchers({ 12 | toHaveSent: matchers.toHaveSent 13 | }); 14 | fakeSerialPort = new FakeSerialPort(); 15 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 16 | port: fakeSerialPort 17 | }); 18 | device.connected = true; 19 | }); 20 | afterEach(function () { 21 | device.acknowledge.forEach(acknowledge => { 22 | if (typeof acknowledge === "function") { 23 | acknowledge() 24 | } 25 | }); 26 | }); 27 | 28 | describe('instantiation', function () { 29 | it('should throw an error if no subtype is specified', function () { 30 | expect(function () { 31 | asyncConfig = new rfxcom.AsyncConfig(device); 32 | }).toThrow(new Error("Must provide a subtype.")); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/asyncData.spec.js: -------------------------------------------------------------------------------- 1 | const rfxcom = require('../lib'), 2 | util = require('util'), 3 | matchers = require('./matchers'), 4 | FakeSerialPort = require('./helper'); 5 | 6 | describe('AsyncData class', function () { 7 | let asyncData, 8 | fakeSerialPort, 9 | device; 10 | beforeEach(function () { 11 | jasmine.addMatchers({ 12 | toHaveSent: matchers.toHaveSent 13 | }); 14 | fakeSerialPort = new FakeSerialPort(); 15 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 16 | port: fakeSerialPort 17 | }); 18 | device.connected = true; 19 | }); 20 | afterEach(function () { 21 | device.acknowledge.forEach(acknowledge => { 22 | if (typeof acknowledge === "function") { 23 | acknowledge() 24 | } 25 | }); 26 | }); 27 | 28 | describe('instantiation', function () { 29 | it('should throw an error if no subtype is specified', function () { 30 | expect(function () { 31 | asyncData = new rfxcom.AsyncData(device); 32 | }).toThrow(new Error("Must provide a subtype.")); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/curtain1.spec.js: -------------------------------------------------------------------------------- 1 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false, 2 | spyOn: false, console: false 3 | */ 4 | const rfxcom = require('../lib'), 5 | util = require('util'), 6 | matchers = require('./matchers'), 7 | FakeSerialPort = require('./helper'); 8 | 9 | describe('Curtain1 class', function () { 10 | let curtain1, 11 | fakeSerialPort, 12 | device; 13 | beforeEach(function () { 14 | jasmine.addMatchers({ 15 | toHaveSent: matchers.toHaveSent 16 | }); 17 | fakeSerialPort = new FakeSerialPort(); 18 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 19 | port: fakeSerialPort 20 | }); 21 | device.connected = true; 22 | }); 23 | afterEach(function () { 24 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 25 | }); 26 | describe('.open', function () { 27 | beforeEach(function () { 28 | curtain1 = new rfxcom.Curtain1(device, rfxcom.curtain1.HARRISON); 29 | }); 30 | it('should send the correct bytes to the serialport', function (done) { 31 | let sentCommandId = NaN; 32 | curtain1.open('A1', function (err, response, cmdId) { 33 | sentCommandId = cmdId; 34 | done(); 35 | }); 36 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x41, 0x01, 0x00, 0x00]); 37 | expect(sentCommandId).toEqual(0); 38 | }); 39 | it('should accept an array deviceId', function (done) { 40 | let sentCommandId = NaN; 41 | curtain1.open(['A', '1'], function (err, response, cmdId) { 42 | sentCommandId = cmdId; 43 | done(); 44 | }); 45 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x41, 0x01, 0x00, 0x00]); 46 | expect(sentCommandId).toEqual(0); 47 | }); 48 | it('should log the bytes being sent in debug mode', function (done) { 49 | const debugDevice = new rfxcom.RfxCom('/dev/ttyUSB0', { 50 | port: fakeSerialPort, 51 | debug: true 52 | }), 53 | curtain = new rfxcom.Curtain1(debugDevice, rfxcom.curtain1.HARRISON), 54 | debugLogSpy = spyOn(debugDevice, 'debugLog'); 55 | debugDevice.connected = true; 56 | curtain.open('a1', done); 57 | expect(debugLogSpy).toHaveBeenCalledWith('Sent : 07,18,00,00,41,01,00,00'); 58 | debugDevice.acknowledge[0](); 59 | }); 60 | it('should throw an exception with an invalid format deviceId', function () { 61 | expect(function () { 62 | curtain1.open(['A', '1', 1]); 63 | }).toThrow(new Error(("Invalid deviceId format"))); 64 | }); 65 | it('should throw an exception with houseCode < \'A\'', function () { 66 | expect(function () { 67 | curtain1.open(['@', '1']); 68 | }).toThrow(new Error(("Invalid house code '@'"))); 69 | }); 70 | it('should accept house code \'P\'', function (done) { 71 | let sentCommandId = NaN; 72 | curtain1.open('P1', function (err, response, cmdId) { 73 | sentCommandId = cmdId; 74 | done(); 75 | }); 76 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00]); 77 | expect(sentCommandId).toEqual(0); 78 | }); 79 | it('should throw an exception with houseCode > \'P\'', function () { 80 | expect(function () { 81 | curtain1.open(['q', '1']); 82 | }).toThrow(new Error(("Invalid house code 'q'"))); 83 | }); 84 | it('should throw an exception with unitCode < 1', function () { 85 | expect(function () { 86 | curtain1.open(['d', '0']); 87 | }).toThrow(new Error(("Invalid unit code 0"))); 88 | }); 89 | it('should throw an exception with a unitCode > 16', function () { 90 | expect(function () { 91 | curtain1.open(['d', '17']); 92 | }).toThrow(new Error(("Invalid unit code 17"))); 93 | }); 94 | it('should handle no callback', function () { 95 | curtain1.open('A16'); 96 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x41, 0x10, 0x00, 0x00]); 97 | }); 98 | }); 99 | describe('.close', function () { 100 | beforeEach(function () { 101 | curtain1 = new rfxcom.Curtain1(device, rfxcom.curtain1.HARRISON); 102 | }); 103 | it('should send the correct bytes to the serialport', function (done) { 104 | let sentCommandId = NaN; 105 | curtain1.close('E1', function (err, response, cmdId) { 106 | sentCommandId = cmdId; 107 | done(); 108 | }); 109 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x45, 0x01, 0x01, 0x00]); 110 | expect(sentCommandId).toEqual(0); 111 | }); 112 | }); 113 | describe('.stop', function () { 114 | beforeEach(function () { 115 | curtain1 = new rfxcom.Curtain1(device, rfxcom.curtain1.HARRISON); 116 | }); 117 | it('should send the correct bytes to the serialport', function (done) { 118 | let sentCommandId = NaN; 119 | curtain1.stop('A01', function (err, response, cmdId) { 120 | sentCommandId = cmdId; 121 | done(); 122 | }); 123 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x41, 0x01, 0x02, 0x00]); 124 | expect(sentCommandId).toEqual(0); 125 | }); 126 | }); 127 | describe('.program', function () { 128 | beforeEach(function () { 129 | curtain1 = new rfxcom.Curtain1(device, rfxcom.curtain1.HARRISON); 130 | }); 131 | it('should send the correct bytes to the serialport', function (done) { 132 | let sentCommandId = NaN; 133 | curtain1.program('A01', function (err, response, cmdId) { 134 | sentCommandId = cmdId; 135 | done(); 136 | }); 137 | expect(fakeSerialPort).toHaveSent([0x07, 0x18, 0x00, 0x00, 0x41, 0x01, 0x03, 0x00]); 138 | expect(sentCommandId).toEqual(0); 139 | }); 140 | }); 141 | 142 | }); 143 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | /* global require: false, module */ 2 | const events = require("events"), 3 | util = require("util"); 4 | 5 | class FakeSerialPort extends events.EventEmitter { 6 | constructor() { 7 | super (); 8 | this.bytesWritten = []; 9 | this.flushed = false; 10 | this.isOpen = true; 11 | } 12 | 13 | write(buffer, callback) { 14 | // Must use array concatenation to handle recording multiple packets 15 | this.bytesWritten = this.bytesWritten.concat(buffer); 16 | if (callback && typeof callback === "function") { 17 | callback(); 18 | } 19 | }; 20 | 21 | flush(callback) { 22 | this.flushed = true; 23 | if (callback && typeof callback === "function") { 24 | callback(); 25 | } 26 | }; 27 | 28 | close(callback) { 29 | this.isOpen = false; 30 | if (callback && typeof callback === "function") { 31 | callback(); 32 | } 33 | }; 34 | } 35 | module.exports = FakeSerialPort; 36 | -------------------------------------------------------------------------------- /test/homeConfort.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 30/06/2017. 3 | */ 4 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false, 5 | spyOn: false, console: false 6 | */ 7 | const rfxcom = require('../lib'), 8 | matchers = require('./matchers'), 9 | FakeSerialPort = require('./helper'); 10 | 11 | describe('HomeConfort class', function () { 12 | let homeConfort, 13 | fakeSerialPort, 14 | device; 15 | beforeEach(function () { 16 | jasmine.addMatchers({ 17 | toHaveSent: matchers.toHaveSent 18 | }); 19 | fakeSerialPort = new FakeSerialPort(); 20 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 21 | port: fakeSerialPort 22 | }); 23 | device.connected = true; 24 | }); 25 | afterEach(function () { 26 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 27 | }); 28 | describe('TEL_010', function () { 29 | beforeEach(function () { 30 | homeConfort = new rfxcom.HomeConfort(device, rfxcom.homeConfort.TEL_010); 31 | }); 32 | describe('commands', function () { 33 | describe('switchOn()', function () { 34 | it('should send the correct bytes to the serialport', function (done) { 35 | let sentCommandId = NaN; 36 | homeConfort.switchOn('0x1234/A/1', function (err, response, cmdId) { 37 | sentCommandId = cmdId; 38 | done(); 39 | }); 40 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x00, 0x12, 0x34, 0x41, 0x01, 0x01, 0x00, 0x00, 0x00]); 41 | expect(sentCommandId).toEqual(0); 42 | }); 43 | it('should handle a group command', function (done) { 44 | let sentCommandId = NaN; 45 | homeConfort.switchOn('0x1234/A/0', function (err, response, cmdId) { 46 | sentCommandId = cmdId; 47 | done(); 48 | }); 49 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x00, 0x12, 0x34, 0x41, 0x00, 0x03, 0x00, 0x00, 0x00]); 50 | expect(sentCommandId).toEqual(0); 51 | }); 52 | }); 53 | describe('switchOff()', function () { 54 | it('should send the correct bytes to the serialport', function (done) { 55 | let sentCommandId = NaN; 56 | homeConfort.switchOff('0x1234/A/1', function (err, response, cmdId) { 57 | sentCommandId = cmdId; 58 | done(); 59 | }); 60 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x00, 0x12, 0x34, 0x41, 0x01, 0x00, 0x00, 0x00, 0x00]); 61 | expect(sentCommandId).toEqual(0); 62 | }); 63 | it('should handle a group command', function (done) { 64 | let sentCommandId = NaN; 65 | homeConfort.switchOff('0x1234/A/0', function (err, response, cmdId) { 66 | sentCommandId = cmdId; 67 | done(); 68 | }); 69 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x00, 0x12, 0x34, 0x41, 0x00, 0x02, 0x00, 0x00, 0x00]); 70 | expect(sentCommandId).toEqual(0); 71 | }); 72 | }); 73 | }); 74 | describe('address checking', function () { 75 | it('should throw an exception with an invalid deviceId format', function () { 76 | expect(function () { 77 | homeConfort.switchOn('0x3FFF'); 78 | }).toThrow(new Error(("Invalid deviceId format"))); 79 | }); 80 | it('should accept the highest address, house and unit code values', function (done) { 81 | let sentCommandId = NaN; 82 | homeConfort.switchOn('0x7FFFF/D/4', function (err, response, cmdId) { 83 | sentCommandId = cmdId; 84 | done(); 85 | }); 86 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x07, 0xff, 0xff, 0x44, 0x04, 0x01, 0x00, 0x00, 0x00]); 87 | expect(sentCommandId).toEqual(0); 88 | }); 89 | it('should accept the lowest address and unit code values', function (done) { 90 | let sentCommandId = NaN; 91 | homeConfort.switchOn('0x1/A/1', function (err, response, cmdId) { 92 | sentCommandId = cmdId; 93 | done(); 94 | }); 95 | expect(fakeSerialPort).toHaveSent([0x0C, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x01, 0x01, 0x00, 0x00, 0x00]); 96 | expect(sentCommandId).toEqual(0); 97 | }); 98 | it('should throw an exception with an invalid unit number 5', function () { 99 | expect(function () { 100 | homeConfort.switchOn('0x7FFF/D/7'); 101 | }).toThrow(new Error(("Invalid unit code 7"))); 102 | }); 103 | it('should throw an exception with an invalid unit number -1', function () { 104 | expect(function () { 105 | homeConfort.switchOn('0x7FFF/D/-1'); 106 | }).toThrow(new Error(("Invalid unit code -1"))); 107 | }); 108 | it('should throw an exception with an invalid house code E', function () { 109 | expect(function () { 110 | homeConfort.switchOn('0x7FFF/E/1'); 111 | }).toThrow(new Error(("Invalid house code 'E'"))); 112 | }); 113 | it('should throw an exception with an invalid unit number @', function () { 114 | expect(function () { 115 | homeConfort.switchOn('0x7FFF/@/-1'); 116 | }).toThrow(new Error(("Invalid house code '@'"))); 117 | }); 118 | it('should throw an exception with an invalid address 0x80000', function () { 119 | expect(function () { 120 | homeConfort.switchOn('0x80000/A/4'); 121 | }).toThrow(new Error(("Address 0x80000 outside valid range"))); 122 | }); 123 | it('should throw an exception with an invalid address 0x0', function () { 124 | expect(function () { 125 | homeConfort.switchOn('0x0/A/4'); 126 | }).toThrow(new Error(("Address 0x0 outside valid range"))); 127 | }); 128 | }); 129 | }) 130 | }); 131 | -------------------------------------------------------------------------------- /test/hunterFan.spec.js: -------------------------------------------------------------------------------- 1 | const rfxcom = require('../lib'), 2 | matchers = require('./matchers'), 3 | FakeSerialPort = require('./helper'); 4 | 5 | describe('HunterFan class', function () { 6 | let hunterFan = {}, 7 | fakeSerialPort = {}, 8 | device = {}; 9 | beforeEach(function () { 10 | jasmine.addMatchers({ 11 | toHaveSent: matchers.toHaveSent 12 | }); 13 | fakeSerialPort = new FakeSerialPort(); 14 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 15 | port: fakeSerialPort 16 | }); 17 | device.connected = true; 18 | }); 19 | afterEach(function () { 20 | device.acknowledge.forEach(acknowledge => { 21 | if (typeof acknowledge === "function") { 22 | acknowledge() 23 | } 24 | }); 25 | }); 26 | describe('HUNTER_FAN', function () { 27 | beforeEach(function () { 28 | hunterFan = new rfxcom.HunterFan(device, rfxcom.hunterFan.HUNTER_FAN); 29 | }); 30 | describe('commands:', function () { 31 | describe('switchOff()', function () { 32 | it('should send the correct bytes to the serialport for switchOff', function (done) { 33 | let sentCommandId = NaN; 34 | hunterFan.switchOff('0x123456789abc', function (err, response, cmdId) { 35 | sentCommandId = cmdId; 36 | done(); 37 | }); 38 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x01, 0x00]); 39 | expect(sentCommandId).toEqual(0); 40 | }); 41 | }); 42 | describe('setSpeed()', function () { 43 | it('should send the correct bytes to the serialport for speed 0', function (done) { 44 | let sentCommandId = NaN; 45 | hunterFan.setSpeed('0x123456789abc', 0, function (err, response, cmdId) { 46 | sentCommandId = cmdId; 47 | done(); 48 | }); 49 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x01, 0x00]); 50 | expect(sentCommandId).toEqual(0); 51 | }); 52 | it('should send the correct bytes to the serialport for speed 1', function (done) { 53 | let sentCommandId = NaN; 54 | hunterFan.setSpeed('0x123456789abc', 1, function (err, response, cmdId) { 55 | sentCommandId = cmdId; 56 | done(); 57 | }); 58 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x03, 0x00]); 59 | expect(sentCommandId).toEqual(0); 60 | }); 61 | it('should send the correct bytes to the serialport for speed 2', function (done) { 62 | let sentCommandId = NaN; 63 | hunterFan.setSpeed('0x123456789abc', 2, function (err, response, cmdId) { 64 | sentCommandId = cmdId; 65 | done(); 66 | }); 67 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x04, 0x00]); 68 | expect(sentCommandId).toEqual(0); 69 | }); 70 | it('should send the correct bytes to the serialport for speed 3', function (done) { 71 | let sentCommandId = NaN; 72 | hunterFan.setSpeed('0x123456789abc', 3, function (err, response, cmdId) { 73 | sentCommandId = cmdId; 74 | done(); 75 | }); 76 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x05, 0x00]); 77 | expect(sentCommandId).toEqual(0); 78 | }); 79 | }); 80 | describe('toggleLightOnOff()', function () { 81 | it('should send the correct bytes to the serialport for toggleLightOnOff', function (done) { 82 | let sentCommandId = NaN; 83 | hunterFan.toggleLightOnOff('0x123456789abc', function (err, response, cmdId) { 84 | sentCommandId = cmdId; 85 | done(); 86 | }); 87 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x02, 0x00]); 88 | expect(sentCommandId).toEqual(0); 89 | }); 90 | }); 91 | describe('program()', function () { 92 | it('should send the correct bytes to the serialport for program', function (done) { 93 | let sentCommandId = NaN; 94 | hunterFan.program('0x123456789abc', function (err, response, cmdId) { 95 | sentCommandId = cmdId; 96 | done(); 97 | }); 98 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x06, 0x00]); 99 | expect(sentCommandId).toEqual(0); 100 | }); 101 | }); 102 | }); 103 | describe('address checking:', function () { 104 | it('should throw an exception with an invalid deviceId format', function () { 105 | expect(function () { 106 | hunterFan.toggleLightOnOff('0x1/A/2'); 107 | }).toThrow(new Error(("Invalid deviceId format"))); 108 | }); 109 | it('should accept the highest address value', function (done) { 110 | let sentCommandId = NaN; 111 | hunterFan.toggleLightOnOff('0xffffffffffff', function (err, response, cmdId) { 112 | sentCommandId = cmdId; 113 | done(); 114 | }); 115 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00]); 116 | expect(sentCommandId).toEqual(0); 117 | }); 118 | it('should accept the lowest address value', function (done) { 119 | let sentCommandId = NaN; 120 | hunterFan.toggleLightOnOff('0x1', function (err, response, cmdId) { 121 | sentCommandId = cmdId; 122 | done(); 123 | }); 124 | expect(fakeSerialPort).toHaveSent([0x0B, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00]); 125 | expect(sentCommandId).toEqual(0); 126 | }); 127 | it('should throw an exception with an invalid address 0x1000000000000', function () { 128 | expect(function () { 129 | hunterFan.toggleLightOnOff('0x1000000000000'); 130 | }).toThrow(new Error(("Address 0x1000000000000 outside valid range"))); 131 | }); 132 | it('should throw an exception with an invalid address 0', function () { 133 | expect(function () { 134 | hunterFan.toggleLightOnOff('0'); 135 | }).toThrow(new Error(("Address 0x0 outside valid range"))); 136 | }); 137 | it('should throw an exception with an invalid address -1', function () { 138 | expect(function () { 139 | hunterFan.toggleLightOnOff('-1'); 140 | }).toThrow(new Error(("Address -0x1 outside valid range"))); 141 | }); 142 | }); 143 | }); 144 | }); -------------------------------------------------------------------------------- /test/lighting2.spec.js: -------------------------------------------------------------------------------- 1 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false, 2 | spyOn: false, console: false 3 | */ 4 | const rfxcom = require('../lib'), 5 | util = require('util'), 6 | matchers = require('./matchers'), 7 | FakeSerialPort = require('./helper'); 8 | 9 | describe('Lighting2 class', function () { 10 | let lighting2, 11 | fakeSerialPort, 12 | device; 13 | beforeEach(function () { 14 | jasmine.addMatchers({ 15 | toHaveSent: matchers.toHaveSent 16 | }); 17 | fakeSerialPort = new FakeSerialPort(); 18 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 19 | port: fakeSerialPort 20 | }); 21 | device.connected = true; 22 | }); 23 | afterEach(function () { 24 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 25 | }); 26 | describe('instantiation', function () { 27 | it('should throw an error if no subtype is specified', function () { 28 | expect(function () { 29 | lighting2 = new rfxcom.Lighting2(device); 30 | }).toThrow(new Error(("Must provide a subtype."))); 31 | }); 32 | }); 33 | describe('.switchOn', function () { 34 | beforeEach(function () { 35 | lighting2 = new rfxcom.Lighting2(device, rfxcom.lighting2.ANSLUT); 36 | }); 37 | it('should send the correct bytes to the serialport', function (done) { 38 | let sentCommandId = NaN; 39 | lighting2.switchOn('0x03FFFFFF/1', function (err, response, cmdId) { 40 | sentCommandId = cmdId; 41 | done(); 42 | }); 43 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x01, 0x01, 0x0F, 0x00]); 44 | expect(sentCommandId).toEqual(0); 45 | }); 46 | it('should accept an array deviceId', function (done) { 47 | let sentCommandId = NaN; 48 | lighting2.switchOn(['0x03FFFFFF', '1'], function (err, response, cmdId) { 49 | sentCommandId = cmdId; 50 | done(); 51 | }); 52 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x01, 0x01, 0x0F, 0x00]); 53 | expect(sentCommandId).toEqual(0); 54 | }); 55 | it('should handle a group address correctly', function (done) { 56 | let sentCommandId = NaN; 57 | lighting2.switchOn(['0x03FFFFFF', '0'], function (err, response, cmdId) { 58 | sentCommandId = cmdId; 59 | done(); 60 | }); 61 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x01, 0x04, 0x0F, 0x00]); 62 | expect(sentCommandId).toEqual(0); 63 | }); 64 | it('should log the bytes being sent in debug mode', function (done) { 65 | const debugDevice = new rfxcom.RfxCom('/dev/ttyUSB0', { 66 | port: fakeSerialPort, 67 | debug: true 68 | }), 69 | debugLight = new rfxcom.Lighting2(debugDevice, rfxcom.lighting2.ANSLUT); 70 | debugDevice.connected = true; 71 | const debugLogSpy = spyOn(debugDevice, 'debugLog'); 72 | debugLight.switchOn('0x03FFFFFF/1', done); 73 | expect(debugLogSpy).toHaveBeenCalledWith('Sent : 0B,11,02,00,03,FF,FF,FF,01,01,0F,00'); 74 | debugDevice.acknowledge[0](); 75 | }); 76 | it('should throw an exception with a badly formatted deviceId', function () { 77 | expect(function () { 78 | lighting2.switchOn('0xF09AC8'); 79 | }).toThrow(new Error(("Invalid deviceId format"))); 80 | }); 81 | it('should throw an exception with an invalid deviceId', function () { 82 | expect(function () { 83 | lighting2.switchOn('0x0FF09AC8/1'); 84 | }).toThrow(new Error(("Device ID 0xff09ac8 outside valid range"))); 85 | }); 86 | it('should handle no callback', function () { 87 | lighting2.switchOn('0x03FFFFFF/1'); 88 | expect(fakeSerialPort).toHaveSent([0x0b, 0x11, 2, 0, 3, 0xff, 0xff, 0xff, 1, 1, 0xf, 0]); 89 | }); 90 | }); 91 | describe('.switchOff', function () { 92 | beforeEach(function () { 93 | lighting2 = new rfxcom.Lighting2(device, rfxcom.lighting2.ANSLUT); 94 | }); 95 | it('should send the correct bytes to the serialport', function (done) { 96 | let sentCommandId = NaN; 97 | lighting2.switchOff('0x03FFFFFF/1', function (err, response, cmdId) { 98 | sentCommandId = cmdId; 99 | done(); 100 | }); 101 | expect(fakeSerialPort).toHaveSent([0x0b, 0x11, 2, 0, 3, 0xff, 0xff, 0xff, 1, 0, 0, 0]); 102 | expect(sentCommandId).toEqual(0); 103 | }); 104 | it('should handle a group address correctly', function (done) { 105 | let sentCommandId = NaN; 106 | lighting2.switchOff(['0x03FFFFFF', '0'], function (err, response, cmdId) { 107 | sentCommandId = cmdId; 108 | done(); 109 | }); 110 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x01, 0x03, 0x00, 0x00]); 111 | expect(sentCommandId).toEqual(0); 112 | }); 113 | it('should handle no callback', function () { 114 | lighting2.switchOff('0x03FFFFFF/1'); 115 | expect(fakeSerialPort).toHaveSent([0x0b, 0x11, 2, 0, 3, 0xff, 0xff, 0xff, 1, 0, 0, 0]); 116 | }); 117 | }); 118 | describe('.setLevel', function () { 119 | beforeEach(function () { 120 | lighting2 = new rfxcom.Lighting2(device, rfxcom.lighting2.ANSLUT); 121 | }); 122 | it('should send the correct bytes to the serialport', function (done) { 123 | let sentCommandId = NaN; 124 | lighting2.setLevel('0x03FFFFFF/1', 7, function (err, response, cmdId) { 125 | sentCommandId = cmdId; 126 | done(); 127 | }); 128 | expect(fakeSerialPort).toHaveSent([0xb, 0x11, 2, 0, 3, 0xff, 0xff, 0xff, 1, 2, 7, 0]); 129 | expect(sentCommandId).toEqual(0); 130 | }); 131 | it('should handle a group address correctly', function (done) { 132 | let sentCommandId = NaN; 133 | lighting2.setLevel(['0x03FFFFFF', '0'], 7, function (err, response, cmdId) { 134 | sentCommandId = cmdId; 135 | done(); 136 | }); 137 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x01, 0x05, 0x07, 0x00]); 138 | expect(sentCommandId).toEqual(0); 139 | }); 140 | it('should throw an exception with an invalid level value', function () { 141 | expect(function () { 142 | lighting2.setLevel('0x03FFFFFF/1', 0x10); 143 | }).toThrow(new Error(("Invalid level: value must be in range 0-15"))); 144 | }); 145 | it('should handle no callback', function () { 146 | lighting2.setLevel('0x03FFFFFF/1', 5); 147 | expect(fakeSerialPort).toHaveSent([0x0b, 0x11, 2, 0, 3, 0xff, 0xff, 0xff, 1, 2, 5, 0]); 148 | }); 149 | }); 150 | describe('.kambrook', function () { 151 | beforeEach(function () { 152 | lighting2 = new rfxcom.Lighting2(device, rfxcom.lighting2.KAMBROOK); 153 | }); 154 | it('should send the correct bytes to the serialport for switchOn', function (done) { 155 | let sentCommandId = NaN; 156 | lighting2.switchOn('A/0xFFFFFF/1', function (err, response, cmdId) { 157 | sentCommandId = cmdId; 158 | done(); 159 | }); 160 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x01, 0x0F, 0x00]); 161 | expect(sentCommandId).toEqual(0); 162 | }); 163 | it('should send the correct bytes to the serialport for switchOff', function (done) { 164 | let sentCommandId = NaN; 165 | lighting2.switchOff(['A', '0xFFFFFF', '1'], function (err, response, cmdId) { 166 | sentCommandId = cmdId; 167 | done(); 168 | }); 169 | expect(fakeSerialPort).toHaveSent([0x0B, 0x11, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00]); 170 | expect(sentCommandId).toEqual(0); 171 | }); 172 | it('should throw an exception with a group address', function () { 173 | expect(function () { 174 | lighting2.switchOn('A/0xFFFFFF/0'); 175 | }).toThrow(new Error(("Subtype doesn't support group commands"))); 176 | }); 177 | it('should throw an exception with a badly formatted deviceId', function () { 178 | expect(function () { 179 | lighting2.switchOn('0xF09AC8'); 180 | }).toThrow(new Error(("Invalid deviceId format"))); 181 | }); 182 | it('should throw an exception with an invalid house code', function () { 183 | expect(function () { 184 | lighting2.switchOn('q/0xFFFFFF/1'); 185 | }).toThrow(new Error(("Invalid house code 'q'"))); 186 | }); 187 | it('should throw an exception with an invalid remote ID', function () { 188 | expect(function () { 189 | lighting2.switchOn('A/0x1FFFFFF/1'); 190 | }).toThrow(new Error(("Remote ID 0x1ffffff outside valid range"))); 191 | }); 192 | it('should throw an exception with an invalid unit code', function () { 193 | expect(function () { 194 | lighting2.switchOn('A/0xFFFFFF/42'); 195 | }).toThrow(new Error(("Invalid unit code 42"))); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /test/lighting3.spec.js: -------------------------------------------------------------------------------- 1 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false, 2 | spyOn: false, console: false 3 | */ 4 | const rfxcom = require('../lib'), 5 | util = require('util'), 6 | matchers = require('./matchers'), 7 | FakeSerialPort = require('./helper'); 8 | 9 | describe('Lighting3 class', function () { 10 | let lighting3, 11 | fakeSerialPort, 12 | device; 13 | beforeEach(function () { 14 | jasmine.addMatchers({ 15 | toHaveSent: matchers.toHaveSent 16 | }); 17 | fakeSerialPort = new FakeSerialPort(); 18 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 19 | port: fakeSerialPort 20 | }); 21 | device.connected = true; 22 | }); 23 | afterEach(function () { 24 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 25 | }); 26 | describe('instantiation', function () { 27 | it('should throw an error if no subtype is specified', function () { 28 | expect(function () { 29 | lighting3 = new rfxcom.Lighting3(device); 30 | }).toThrow(new Error(("Must provide a subtype."))); 31 | }); 32 | }); 33 | describe('.switchOn', function () { 34 | beforeEach(function () { 35 | lighting3 = new rfxcom.Lighting3(device, rfxcom.lighting3.KOPPLA); 36 | }); 37 | it('should send the correct bytes to the serialport', function (done) { 38 | let sentCommandId = NaN; 39 | lighting3.switchOn('1/1', function (err, response, cmdId) { 40 | sentCommandId = cmdId; 41 | done(); 42 | }); 43 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00]); 44 | expect(sentCommandId).toEqual(0); 45 | }); 46 | it('should accept an array deviceId', function (done) { 47 | let sentCommandId = NaN; 48 | lighting3.switchOn(['1', '1'], function (err, response, cmdId) { 49 | sentCommandId = cmdId; 50 | done(); 51 | }); 52 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00]); 53 | expect(sentCommandId).toEqual(0); 54 | }); 55 | it('should accept a switchOff command', function (done) { 56 | let sentCommandId = NaN; 57 | lighting3.switchOff(['1', '1'], function (err, response, cmdId) { 58 | sentCommandId = cmdId; 59 | done(); 60 | }); 61 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1a, 0x00]); 62 | expect(sentCommandId).toEqual(0); 63 | }); 64 | it('should accept a decreaseLevel command', function (done) { 65 | let sentCommandId = NaN; 66 | lighting3.decreaseLevel(['1', '1'], function (err, response, cmdId) { 67 | sentCommandId = cmdId; 68 | done(); 69 | }); 70 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00]); 71 | expect(sentCommandId).toEqual(0); 72 | }); 73 | it('should accept a decreaseLevel command with a room number', function (done) { 74 | let sentCommandId = NaN; 75 | lighting3.decreaseLevel(['1', '1'], 1, function (err, response, cmdId) { 76 | sentCommandId = cmdId; 77 | done(); 78 | }); 79 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00]); 80 | expect(sentCommandId).toEqual(0); 81 | }); 82 | it('should accept an increaseLevel command', function (done) { 83 | let sentCommandId = NaN; 84 | lighting3.increaseLevel(['1', '1'], function (err, response, cmdId) { 85 | sentCommandId = cmdId; 86 | done(); 87 | }); 88 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); 89 | expect(sentCommandId).toEqual(0); 90 | }); 91 | it('should accept an increaseLevel command with a room number', function (done) { 92 | let sentCommandId = NaN; 93 | lighting3.increaseLevel(['1', '1'], 1, function (err, response, cmdId) { 94 | sentCommandId = cmdId; 95 | done(); 96 | }); 97 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); 98 | expect(sentCommandId).toEqual(0); 99 | }); 100 | it('should accept a setLevel command', function (done) { 101 | let sentCommandId = NaN; 102 | lighting3.setLevel(['1', '1'], 7, function (err, response, cmdId) { 103 | sentCommandId = cmdId; 104 | done(); 105 | }); 106 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x17, 0x00]); 107 | expect(sentCommandId).toEqual(0); 108 | }); 109 | it('should throw an exception with an out of range setLevel(level)', function () { 110 | expect(function () { 111 | lighting3.setLevel(['1', '1'], 11); 112 | }).toThrow(new Error(("Invalid level: value must be in range 0-10"))); 113 | }); 114 | it('should accept a program command', function (done) { 115 | let sentCommandId = NaN; 116 | lighting3.program(['1', '1'], function (err, response, cmdId) { 117 | sentCommandId = cmdId; 118 | done(); 119 | }); 120 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00]); 121 | expect(sentCommandId).toEqual(0); 122 | }); 123 | it('should handle a group address correctly', function (done) { 124 | let sentCommandId = NaN; 125 | lighting3.switchOn(['16', '0'], function (err, response, cmdId) { 126 | sentCommandId = cmdId; 127 | done(); 128 | }); 129 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x0f, 0xff, 0x03, 0x10, 0x00]); 130 | expect(sentCommandId).toEqual(0); 131 | }); 132 | it('should log the bytes being sent in debug mode', function (done) { 133 | const debugDevice = new rfxcom.RfxCom('/dev/ttyUSB0', { 134 | port: fakeSerialPort, 135 | debug: true 136 | }), 137 | debugLight = new rfxcom.Lighting3(debugDevice, rfxcom.lighting3.KOPPLA); 138 | debugDevice.connected = true; 139 | const debugLogSpy = spyOn(debugDevice, 'debugLog'); 140 | debugLight.switchOn(['16', '0'], done); 141 | expect(debugLogSpy).toHaveBeenCalledWith('Sent : 08,12,00,00,0F,FF,03,10,00'); 142 | debugDevice.acknowledge[0](); 143 | }); 144 | it('should accept the highest system code & channel number', function (done) { 145 | let sentCommandId = NaN; 146 | lighting3.switchOn(['16', '10'], function (err, response, cmdId) { 147 | sentCommandId = cmdId; 148 | done(); 149 | }); 150 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x0f, 0x00, 0x02, 0x10, 0x00]); 151 | expect(sentCommandId).toEqual(0); 152 | }); 153 | it('should throw an exception with a badly formatted deviceId', function () { 154 | expect(function () { 155 | lighting3.switchOn('0xF09AC8'); 156 | }).toThrow(new Error(("Invalid deviceId format"))); 157 | }); 158 | it('should throw an exception with an invalid system number', function () { 159 | expect(function () { 160 | lighting3.switchOn('17/1'); 161 | }).toThrow(new Error(("Invalid system code 17"))); 162 | }); 163 | it('should throw an exception with an invalid channel number', function () { 164 | expect(function () { 165 | lighting3.switchOn('16/11'); 166 | }).toThrow(new Error(("Invalid channel number 11"))); 167 | }); 168 | it('should handle no callback', function () { 169 | lighting3.switchOn('16/0'); 170 | expect(fakeSerialPort).toHaveSent([0x08, 0x12, 0x00, 0x00, 0x0f, 0xff, 0x03, 0x10, 0x00]); 171 | }); 172 | }); 173 | }); -------------------------------------------------------------------------------- /test/lighting4.spec.js: -------------------------------------------------------------------------------- 1 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false, 2 | spyOn: false, console: false 3 | */ 4 | const rfxcom = require('../lib'), 5 | util = require('util'), 6 | matchers = require('./matchers'), 7 | FakeSerialPort = require('./helper'); 8 | 9 | describe('Lighting4 class', function () { 10 | let lighting4, 11 | fakeSerialPort, 12 | device; 13 | beforeEach(function () { 14 | jasmine.addMatchers({ 15 | toHaveSent: matchers.toHaveSent 16 | }); 17 | fakeSerialPort = new FakeSerialPort(); 18 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 19 | port: fakeSerialPort 20 | }); 21 | device.connected = true; 22 | }); 23 | afterEach(function () { 24 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 25 | }); 26 | describe('instantiation', function () { 27 | it('should throw an error if no subtype is specified', function () { 28 | expect(function () { 29 | lighting4 = new rfxcom.Lighting4(device); 30 | }).toThrow(new Error(("Must provide a subtype."))); 31 | }); 32 | }); 33 | describe('.switchOn', function () { 34 | beforeEach(function () { 35 | lighting4 = new rfxcom.Lighting4(device, rfxcom.lighting4.PT2262); 36 | }); 37 | it('should send the correct bytes to the serialport (numeric data, default pulse width)', function (done) { 38 | let sentCommandId = NaN; 39 | lighting4.sendData(0, null, function (err, response, cmdId) { 40 | sentCommandId = cmdId; 41 | done(); 42 | }); 43 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5E, 0x00]); 44 | expect(sentCommandId).toEqual(0); 45 | }); 46 | it('should log the bytes being sent in debug mode', function (done) { 47 | const debugDevice = new rfxcom.RfxCom('/dev/ttyUSB0', { 48 | port: fakeSerialPort, 49 | debug: true 50 | }), 51 | debugLight = new rfxcom.Lighting4(debugDevice, rfxcom.lighting4.PT2262); 52 | debugDevice.connected = true; 53 | const debugLogSpy = spyOn(debugDevice, 'debugLog'); 54 | debugLight.sendData(0, null, function () { 55 | done(); 56 | }); 57 | expect(debugLogSpy).toHaveBeenCalledWith('Sent : 09,13,00,00,00,00,00,01,5E,00'); 58 | debugDevice.acknowledge[0](); 59 | }); 60 | it('should send the correct bytes to the serialport (numeric data, hex string pulse width)', function (done) { 61 | let sentCommandId = NaN; 62 | lighting4.sendData(0, "0x0578", function (err, response, cmdId) { 63 | sentCommandId = cmdId; 64 | done(); 65 | }); 66 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x78, 0x00]); 67 | expect(sentCommandId).toEqual(0); 68 | }); 69 | it('should send the correct bytes to the serialport (string data, default pulse width)', function (done) { 70 | let sentCommandId = NaN; 71 | lighting4.sendData("0", null, function (err, response, cmdId) { 72 | sentCommandId = cmdId; 73 | done(); 74 | }); 75 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5E, 0x00]); 76 | expect(sentCommandId).toEqual(0); 77 | }); 78 | it('should send the correct bytes to the serialport (array data, default pulse width)', function (done) { 79 | let sentCommandId = NaN; 80 | lighting4.sendData([0, 1, 2], null, function (err, response, cmdId) { 81 | sentCommandId = cmdId; 82 | done(); 83 | }); 84 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x5E, 0x00]); 85 | expect(sentCommandId).toEqual(0); 86 | }); 87 | it('should send the correct bytes to the serialport (undersize array data, default pulse width)', function (done) { 88 | let sentCommandId = NaN; 89 | lighting4.sendData([1, 2], null, function (err, response, cmdId) { 90 | sentCommandId = cmdId; 91 | done(); 92 | }); 93 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x5E, 0x00]); 94 | expect(sentCommandId).toEqual(0); 95 | }); 96 | it('should send the correct bytes to the serialport (hex string data, default pulse width)', function (done) { 97 | let sentCommandId = NaN; 98 | lighting4.sendData("0x000102", null, function (err, response, cmdId) { 99 | sentCommandId = cmdId; 100 | done(); 101 | }); 102 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x5E, 0x00]); 103 | expect(sentCommandId).toEqual(0); 104 | }); 105 | it('should send the correct bytes to the serialport (hex string data, hex string pulse width)', function (done) { 106 | let sentCommandId = NaN; 107 | lighting4.sendData("0x000102", "0x0312", function (err, response, cmdId) { 108 | sentCommandId = cmdId; 109 | done(); 110 | }); 111 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x12, 0x00]); 112 | expect(sentCommandId).toEqual(0); 113 | }); 114 | it('should send the correct bytes to the serialport (decimal string data, decimal string pulse width)', function (done) { 115 | let sentCommandId = NaN; 116 | lighting4.sendData("258", "786", function (err, response, cmdId) { 117 | sentCommandId = cmdId; 118 | done(); 119 | }); 120 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x12, 0x00]); 121 | expect(sentCommandId).toEqual(0); 122 | }); 123 | it('should send the correct bytes to the serialport (numeric data, numeric pulse width)', function (done) { 124 | let sentCommandId = NaN; 125 | lighting4.sendData(0x000102, 0x0312, function (err, response, cmdId) { 126 | sentCommandId = cmdId; 127 | done(); 128 | }); 129 | expect(fakeSerialPort).toHaveSent([0x09, 0x13, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x12, 0x00]); 130 | expect(sentCommandId).toEqual(0); 131 | }); 132 | }); 133 | }); -------------------------------------------------------------------------------- /test/lighting6.spec.js: -------------------------------------------------------------------------------- 1 | /* global require: false, beforeEach: false, describe: false, it: false, expect: false */ 2 | const rfxcom = require('../lib'), 3 | util = require('util'), 4 | matchers = require('./matchers'), 5 | FakeSerialPort = require('./helper'); 6 | 7 | beforeEach(function () { 8 | jasmine.addMatchers({ 9 | toHaveSent: matchers.toHaveSent 10 | }); 11 | }); 12 | 13 | describe('Lighting6 class', function () { 14 | let lighting6, 15 | fakeSerialPort, 16 | device; 17 | beforeEach(function () { 18 | jasmine.addMatchers({ 19 | toHaveSent: matchers.toHaveSent 20 | }); 21 | fakeSerialPort = new FakeSerialPort(); 22 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 23 | port: fakeSerialPort 24 | }); 25 | device.connected = true; 26 | }); 27 | afterEach(function () { 28 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 29 | }); 30 | describe('instantiation', function () { 31 | it('should throw an error if no subtype is specified', function () { 32 | expect(function () { 33 | lighting6 = new rfxcom.Lighting6(device); 34 | }).toThrow(new Error(("Must provide a subtype."))); 35 | }); 36 | }); 37 | describe('.switchOn', function () { 38 | beforeEach(function () { 39 | lighting6 = new rfxcom.Lighting6(device, rfxcom.lighting6.BLYSS); 40 | }); 41 | it('should send the correct bytes to the serialport', function (done) { 42 | let sentCommandId = NaN; 43 | lighting6.switchOn('0xF09A/B/1', function (err, response, cmdId) { 44 | sentCommandId = cmdId; 45 | done(); 46 | }); 47 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xF0, 0x9A, 0x42, 0x01, 0x00, 0x00, 0x00, 0x00]); 48 | expect(sentCommandId).toEqual(0); 49 | }); 50 | it('should log the bytes being sent in debug mode', function (done) { 51 | const debugDevice = new rfxcom.RfxCom('/dev/ttyUSB0', { 52 | port: fakeSerialPort, 53 | debug: true 54 | }), 55 | debug = new rfxcom.Lighting6(debugDevice, rfxcom.lighting6.BLYSS); 56 | debugDevice.connected = true; 57 | const debugLogSpy = spyOn(debugDevice, 'debugLog'); 58 | debug.switchOn('0xF09A/B/1', function () { 59 | done(); 60 | }); 61 | expect(debugLogSpy).toHaveBeenCalledWith('Sent : 0B,15,00,00,F0,9A,42,01,00,00,00,00'); 62 | debugDevice.acknowledge[0](); 63 | }); 64 | it('should accept an array address', function (done) { 65 | let sentCommandId = NaN; 66 | lighting6.switchOff(['0xF09A', 'B', '1'], function (err, response, cmdId) { 67 | sentCommandId = cmdId; 68 | done(); 69 | }); 70 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xF0, 0x9A, 0x42, 0x01, 0x01, 0x00, 0x00, 0x00]); 71 | expect(sentCommandId).toEqual(0); 72 | }); 73 | it('should accept an group address to switch off', function (done) { 74 | let sentCommandId = NaN; 75 | lighting6.switchOff(['0xF09A', 'B', '0'], function (err, response, cmdId) { 76 | sentCommandId = cmdId; 77 | done(); 78 | }); 79 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xF0, 0x9A, 0x42, 0x00, 0x03, 0x00, 0x00, 0x00]); 80 | expect(sentCommandId).toEqual(0); 81 | }); 82 | it('should accept an group address to switch on', function (done) { 83 | let sentCommandId = NaN; 84 | lighting6.switchOn(['0xF09A', 'B', '0'], function (err, response, cmdId) { 85 | sentCommandId = cmdId; 86 | done(); 87 | }); 88 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xF0, 0x9A, 0x42, 0x00, 0x02, 0x00, 0x00, 0x00]); 89 | expect(sentCommandId).toEqual(0); 90 | }); 91 | }); 92 | describe('BLYSS address checking', function () { 93 | beforeEach(function () { 94 | lighting6 = new rfxcom.Lighting6(device, rfxcom.lighting6.BLYSS); 95 | }); 96 | 97 | it('should accept the highest ID, group code & unit code numbers', function (done) { 98 | let sentCommandId = NaN; 99 | lighting6.switchOn(['0xFFFF', 'P', '5'], function (err, response, cmdId) { 100 | sentCommandId = cmdId; 101 | done(); 102 | }); 103 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xff, 0xff, 0x50, 0x05, 0x00, 0x00, 0x00, 0x00]); 104 | expect(sentCommandId).toEqual(0); 105 | }); 106 | it('should increment cmdseqnbr before each command', function (done) { 107 | let sentCommandId = NaN; 108 | lighting6.cmdseqnbr = 0; 109 | lighting6.switchOn(['0xFFFF', 'P', '5'], function (err, response, cmdId) { 110 | sentCommandId = cmdId; 111 | done(); 112 | }); 113 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xff, 0xff, 0x50, 0x05, 0x00, 0x01, 0x00, 0x00]); 114 | expect(sentCommandId).toEqual(0); 115 | }); 116 | it('cmdseqnbr should roll back to 0 after 4', function (done) { 117 | let sentCommandId = NaN; 118 | lighting6.cmdseqnbr = 4; 119 | lighting6.switchOn(['0xFFFF', 'P', '5'], function (err, response, cmdId) { 120 | sentCommandId = cmdId; 121 | done(); 122 | }); 123 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x00, 0x00, 0xff, 0xff, 0x50, 0x05, 0x00, 0x00, 0x00, 0x00]); 124 | expect(sentCommandId).toEqual(0); 125 | }); 126 | it('should throw an exception with an invalid group code', function () { 127 | expect(function () { 128 | lighting6.switchOn(['0xFFFF', 'Q', '5']); 129 | }).toThrow(new Error(("Invalid group code 'Q'"))); 130 | }); 131 | it('should throw an exception with an invalid unit code', function () { 132 | expect(function () { 133 | lighting6.switchOn(['0xFFFF', 'P', '6']); 134 | }).toThrow(new Error(("Invalid unit number 6"))); 135 | }); 136 | it('should throw an exception with a badly formatted deviceId', function () { 137 | expect(function () { 138 | lighting6.switchOn('0xF09AC8'); 139 | }).toThrow(new Error(("Invalid deviceId format"))); 140 | }); 141 | }); 142 | describe('CUVEO address checking', function () { 143 | beforeEach(function () { 144 | lighting6 = new rfxcom.Lighting6(device, rfxcom.lighting6.CUVEO); 145 | }); 146 | it('should accept the highest ID, group code & unit code numbers', function (done) { 147 | let sentCommandId = NaN; 148 | lighting6.switchOn(['0xFFFF', '3', '8'], function (err, response, cmdId) { 149 | sentCommandId = cmdId; 150 | done(); 151 | }); 152 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x01, 0x00, 0xff, 0xff, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00]); 153 | expect(sentCommandId).toEqual(0); 154 | }); 155 | it('should accept the highest unit code number when group code is 0', function (done) { 156 | let sentCommandId = NaN; 157 | lighting6.switchOn(['0xFFFF', '0', '2'], function (err, response, cmdId) { 158 | sentCommandId = cmdId; 159 | done(); 160 | }); 161 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x01, 0x00, 0xff, 0xff, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00]); 162 | expect(sentCommandId).toEqual(0); 163 | }); 164 | it('should increment cmdseqnbr before each command', function (done) { 165 | let sentCommandId = NaN; 166 | lighting6.cmdseqnbr = 0; 167 | lighting6.switchOn(['0xFFFF', '3', '8'], function (err, response, cmdId) { 168 | sentCommandId = cmdId; 169 | done(); 170 | }); 171 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x01, 0x00, 0xff, 0xff, 0x03, 0x08, 0x00, 0x01, 0x00, 0x00]); 172 | expect(sentCommandId).toEqual(0); 173 | }); 174 | it('cmdseqnbr should roll back to 0 after 255', function (done) { 175 | let sentCommandId = NaN; 176 | lighting6.cmdseqnbr = 255; 177 | lighting6.switchOn(['0xFFFF', '3', '8'], function (err, response, cmdId) { 178 | sentCommandId = cmdId; 179 | done(); 180 | }); 181 | expect(fakeSerialPort).toHaveSent([0x0b, 0x15, 0x01, 0x00, 0xff, 0xff, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00]); 182 | expect(sentCommandId).toEqual(0); 183 | }); 184 | it('should throw an exception with an invalid group code', function () { 185 | expect(function () { 186 | lighting6.switchOn(['0xFFFF', '4', '1']); 187 | }).toThrow(new Error(("Invalid group code '4'"))); 188 | }); 189 | it('should throw an exception with an invalid unit code of 9 when the group is 1', function () { 190 | expect(function () { 191 | lighting6.switchOn(['0xFFFF', '1', '9']); 192 | }).toThrow(new Error(("Invalid unit number 9"))); 193 | }); 194 | it('should throw an exception with an invalid unit code of 3 when the group is 0', function () { 195 | expect(function () { 196 | lighting6.switchOn(['0xFFFF', '0', '3']); 197 | }).toThrow(new Error(("Invalid unit number 3"))); 198 | }); 199 | it('should throw an exception with a badly formatted deviceId', function () { 200 | expect(function () { 201 | lighting6.switchOn('0xF09AC8'); 202 | }).toThrow(new Error(("Invalid deviceId format"))); 203 | }); 204 | }); 205 | }); 206 | -------------------------------------------------------------------------------- /test/matchers.js: -------------------------------------------------------------------------------- 1 | /* global module: false */ 2 | module.exports.toHaveSent = function(matchersUtil) { 3 | 4 | return { 5 | compare: function(actual, expected) { 6 | var passed = actual.bytesWritten.toString() === expected.toString(); 7 | result = {pass: passed}; 8 | if (passed) { 9 | notText = " not"; 10 | } else { 11 | notText = ""; 12 | } 13 | result.message = "Expected " + actual.bytesWritten + notText + " to equal " + expected 14 | 15 | return result 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /test/radiator1.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 14/07/2017. 3 | */ 4 | const rfxcom = require('../lib'), 5 | matchers = require('./matchers'), 6 | FakeSerialPort = require('./helper'); 7 | 8 | describe('Radiator1 class', function () { 9 | let radiator = {}, 10 | fakeSerialPort = {}, 11 | device = {}; 12 | beforeEach(function () { 13 | jasmine.addMatchers({ 14 | toHaveSent: matchers.toHaveSent 15 | }); 16 | fakeSerialPort = new FakeSerialPort(); 17 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 18 | port: fakeSerialPort 19 | }); 20 | device.connected = true; 21 | }); 22 | afterEach(function () { 23 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 24 | }); 25 | describe('SMARTWARES', function () { 26 | beforeEach(function () { 27 | radiator = new rfxcom.Radiator1(device, rfxcom.radiator1.SMARTWARES); 28 | }); 29 | describe('commands', function () { 30 | describe('setNightMode()', function () { 31 | it('should send the correct bytes to the serialport', function (done) { 32 | let sentCommandId = NaN; 33 | radiator.setNightMode('0x1234567/8', function (err, response, cmdId) { 34 | sentCommandId = cmdId; 35 | done(); 36 | }); 37 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x00, 0x00, 0x00, 0x00]); 38 | expect(sentCommandId).toEqual(0); 39 | }); 40 | }); 41 | describe('setDayMode()', function () { 42 | it('should send the correct bytes to the serialport', function (done) { 43 | let sentCommandId = NaN; 44 | radiator.setDayMode('0x1234567/8', function (err, response, cmdId) { 45 | sentCommandId = cmdId; 46 | done(); 47 | }); 48 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x01, 0x00, 0x00, 0x00]); 49 | expect(sentCommandId).toEqual(0); 50 | }); 51 | }); 52 | describe('setTemperature()', function () { 53 | it('should send the correct bytes to the serialport with a valid setpoint', function (done) { 54 | let sentCommandId = NaN; 55 | radiator.setTemperature('0x1234567/8', 22.0, function (err, response, cmdId) { 56 | sentCommandId = cmdId; 57 | done(); 58 | }); 59 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x02, 0x16, 0x00, 0x00]); 60 | expect(sentCommandId).toEqual(0); 61 | }); 62 | it('should send the correct bytes to the serialport with a half-integral setpoint', function (done) { 63 | let sentCommandId = NaN; 64 | radiator.setTemperature('0x1234567/8', 22.5, function (err, response, cmdId) { 65 | sentCommandId = cmdId; 66 | done(); 67 | }); 68 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x02, 0x16, 0x05, 0x00]); 69 | expect(sentCommandId).toEqual(0); 70 | }); 71 | it('should limit the setpoint to a minimum of 5˚C', function (done) { 72 | let sentCommandId = NaN; 73 | radiator.setTemperature('0x1234567/8', 0.0, function (err, response, cmdId) { 74 | sentCommandId = cmdId; 75 | done(); 76 | }); 77 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x02, 0x05, 0x00, 0x00]); 78 | expect(sentCommandId).toEqual(0); 79 | }); 80 | it('should limit the setpoint to a maximum of 28˚C', function (done) { 81 | let sentCommandId = NaN; 82 | radiator.setTemperature('0x1234567/8', 50.0, function (err, response, cmdId) { 83 | sentCommandId = cmdId; 84 | done(); 85 | }); 86 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x08, 0x02, 0x1C, 0x00, 0x00]); 87 | expect(sentCommandId).toEqual(0); 88 | }); 89 | it('should throw an exception with a missing setpoint', function () { 90 | expect(function () { 91 | radiator.setTemperature('0x1234567/8'); 92 | }).toThrow(new Error(("Invalid temperature: must be a number in range 5.0-28.0"))); 93 | }); 94 | it('should throw an exception with a setpoint in the wrong format', function () { 95 | expect(function () { 96 | radiator.setTemperature('0x1234567/8', "28.2"); 97 | }).toThrow(new Error(("Invalid temperature: must be a number in range 5.0-28.0"))); 98 | }); 99 | }); 100 | }); 101 | describe('address checking', function () { 102 | it('should throw an exception with an invalid deviceId format', function () { 103 | expect(function () { 104 | radiator.setDayMode('0x1234'); 105 | }).toThrow(new Error(("Invalid deviceId format"))); 106 | }); 107 | it('should accept the highest address & unit code', function (done) { 108 | let sentCommandId = NaN; 109 | radiator.setDayMode('0x3ffffff/16', function (err, response, cmdId) { 110 | sentCommandId = cmdId; 111 | done(); 112 | }); 113 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x10, 0x01, 0x00, 0x00, 0x00]); 114 | expect(sentCommandId).toEqual(0); 115 | }); 116 | it('should accept the lowest address & unit code', function (done) { 117 | let sentCommandId = NaN; 118 | radiator.setDayMode('0x1/1', function (err, response, cmdId) { 119 | sentCommandId = cmdId; 120 | done(); 121 | }); 122 | expect(fakeSerialPort).toHaveSent([0x0c, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00]); 123 | expect(sentCommandId).toEqual(0); 124 | }); 125 | it('should throw an exception with an invalid address 0x4000000', function () { 126 | expect(function () { 127 | radiator.setDayMode('0x4000000/8'); 128 | }).toThrow(new Error(("Address 0x4000000 outside valid range"))); 129 | }); 130 | it('should throw an exception with an invalid address 0x0', function () { 131 | expect(function () { 132 | radiator.setDayMode('0x0/8'); 133 | }).toThrow(new Error(("Address 0x0 outside valid range"))); 134 | }); 135 | it('should throw an exception with an invalid unit code 0', function () { 136 | expect(function () { 137 | radiator.setDayMode('0x1234567/0'); 138 | }).toThrow(new Error(("Invalid unit code 0"))); 139 | }); 140 | it('should throw an exception with an invalid unit code 17', function () { 141 | expect(function () { 142 | radiator.setDayMode('0x1234567/17'); 143 | }).toThrow(new Error(("Invalid unit code 17"))); 144 | }); 145 | }); 146 | }); 147 | }); -------------------------------------------------------------------------------- /test/rawtx.spec.js: -------------------------------------------------------------------------------- 1 | const rfxcom = require('../lib'), 2 | util = require('util'), 3 | matchers = require('./matchers'), 4 | FakeSerialPort = require('./helper'); 5 | 6 | describe('RawTx class', function () { 7 | let rawtx, 8 | fakeSerialPort, 9 | device; 10 | beforeEach(function () { 11 | jasmine.addMatchers({ 12 | toHaveSent: matchers.toHaveSent 13 | }); 14 | fakeSerialPort = new FakeSerialPort(); 15 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 16 | port: fakeSerialPort 17 | }); 18 | device.connected = true; 19 | }); 20 | afterEach(function () { 21 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 22 | }); 23 | describe('instantiation', function () { 24 | beforeEach(function () { 25 | rawtx = new rfxcom.RawTx(device); 26 | }); 27 | it("should be initialised correctly when created", function (done) { 28 | expect(rawtx.subtype).toBe(-1); 29 | expect(rawtx.isSubtype(-1)).toBe(false); 30 | done(); 31 | }); 32 | it ("should send the correct bytes to the serialport", function (done) { 33 | let sentCommandId = NaN; 34 | const params = { 35 | repeats: 7, 36 | pulseTimes: "1 2 3 4 5 6 7 8" 37 | } 38 | const expectedPackets = 1; 39 | let packet = 1; 40 | rawtx.sendMessage('0x1234567/8', params, function (err, response, cmdId) { 41 | sentCommandId = cmdId; 42 | if (packet == expectedPackets) { 43 | done(); 44 | } 45 | packet++; 46 | }); 47 | expect(fakeSerialPort).toHaveSent([20, 0x7f, 0, 0, 7, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]); 48 | expect(sentCommandId).toEqual(expectedPackets - 1); 49 | }); 50 | it ("should use a default value of 5 repeats if none specified", function (done) { 51 | let sentCommandId = NaN; 52 | const params = { 53 | pulseTimes: "1 2 3 4 5 6 7 8" 54 | } 55 | const expectedPackets = 1; 56 | let packet = 1; 57 | rawtx.sendMessage('0x1234567/8', params, function (err, response, cmdId) { 58 | sentCommandId = cmdId; 59 | if (packet == expectedPackets) { 60 | done(); 61 | } 62 | packet++; 63 | }); 64 | expect(fakeSerialPort).toHaveSent([20, 0x7f, 0, 0, 5, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]); 65 | expect(sentCommandId).toEqual(expectedPackets - 1); 66 | }); 67 | it ("should handle a missing deviceId", function (done) { 68 | let sentCommandId = NaN; 69 | const params = { 70 | repeats: 7, 71 | pulseTimes: "1 2 3 4 5 6 7 8" 72 | } 73 | const expectedPackets = 1; 74 | let packet = 1; 75 | rawtx.sendMessage(params, function (err, response, cmdId) { 76 | sentCommandId = cmdId; 77 | if (packet == expectedPackets) { 78 | done(); 79 | } 80 | packet++; 81 | }); 82 | expect(fakeSerialPort).toHaveSent([20, 0x7f, 0, 0, 7, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]); 83 | expect(sentCommandId).toEqual(expectedPackets - 1); 84 | }); 85 | it ("should accept a numeric array of pulse times", function (done) { 86 | let sentCommandId = NaN; 87 | const params = { 88 | repeats: 7, 89 | pulseTimes: [1, 2, 3, 4, 5, 6, 7, 8] 90 | } 91 | const expectedPackets = 1; 92 | let packet = 1; 93 | rawtx.sendMessage('0x1234567/8', params, function (err, response, cmdId) { 94 | sentCommandId = cmdId; 95 | if (packet == expectedPackets) { 96 | done(); 97 | } 98 | packet++; 99 | }); 100 | expect(fakeSerialPort).toHaveSent([20, 0x7f, 0, 0, 7, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]); 101 | expect(sentCommandId).toEqual(expectedPackets - 1); 102 | }); 103 | it ("should send multiple packets if the number of pulses exceeds 124", function (done) { 104 | let sentCommandId = NaN; 105 | const params = { 106 | repeats: 7, 107 | pulseTimes: [ 108 | 1, 2, 3, 4, 5, 6, 7, 8, 109 | 1, 2, 3, 4, 5, 6, 7, 8, 110 | 1, 2, 3, 4, 5, 6, 7, 8, 111 | 1, 2, 3, 4, 5, 6, 7, 8, 112 | 1, 2, 3, 4, 5, 6, 7, 8, 113 | 1, 2, 3, 4, 5, 6, 7, 8, 114 | 1, 2, 3, 4, 5, 6, 7, 8, 115 | 1, 2, 3, 4, 5, 6, 7, 8, 116 | 1, 2, 3, 4, 5, 6, 7, 8, 117 | 1, 2, 3, 4, 5, 6, 7, 8, 118 | 1, 2, 3, 4, 5, 6, 7, 8, 119 | 1, 2, 3, 4, 5, 6, 7, 8, 120 | 1, 2, 3, 4, 5, 6, 7, 8, 121 | 1, 2, 3, 4, 5, 6, 7, 8, 122 | 1, 2, 3, 4, 5, 6, 7, 8, 123 | 1, 2, 3, 4, 5, 6, 7, 8 124 | ] 125 | } 126 | const expectedPackets = 2; 127 | let packet = 1; 128 | rawtx.sendMessage('0x1234567/8', params, function (err, response, cmdId) { 129 | sentCommandId = cmdId; 130 | if (packet == expectedPackets) { 131 | done(); 132 | } 133 | packet++; 134 | }); 135 | expect(fakeSerialPort).toHaveSent([ 136 | 252, 0x7f, 0, 0, 0, 137 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 138 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 139 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 140 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 141 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 142 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 143 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 144 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 145 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 146 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 147 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 148 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 149 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 150 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 151 | 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 152 | 0, 1, 0, 2, 0, 3, 0, 4, 153 | 12, 0x7f, 1, 1, 7, 154 | 0, 5, 0, 6, 0, 7, 0, 8 155 | ]); 156 | expect(sentCommandId).toEqual(expectedPackets - 1); 157 | }); 158 | it("should convert a valid string to an integer numeric array", function (done) { 159 | expect(rfxcom.RawTx._stringToArray(" 42,7 9\t\t4\n6\n")).toEqual([42, 7, 9, 4, 6]); 160 | done(); 161 | }); 162 | it("should convert a valid hexadecimal string to an integer numeric array", function (done) { 163 | expect(rfxcom.RawTx._stringToArray(" 0x15, 0x01")).toEqual([21, 1]); 164 | done(); 165 | }); 166 | it("should throw an exception if the string contains floating-point format numbers", function () { 167 | expect(function () { 168 | rfxcom.RawTx._stringToArray(" 42.5"); 169 | }).toThrow(new Error("Floating-point pulse times not allowed")); 170 | }); 171 | it("should reject an array with a -1 element", function (done) { 172 | expect(rfxcom.RawTx._anyNonValid([-1, 1, 2, 3])).toBe(true); 173 | done(); 174 | }) 175 | it("should reject an array with a 0 element", function (done) { 176 | expect(rfxcom.RawTx._anyNonValid([0, 1, 2, 3])).toBe(true); 177 | done(); 178 | }) 179 | it("should reject an array with a 65536 element", function (done) { 180 | expect(rfxcom.RawTx._anyNonValid([65536, 1, 2, 3])).toBe(true); 181 | done(); 182 | }) 183 | it("should accept an array with numeric elements", function (done) { 184 | expect(rfxcom.RawTx._anyNonNumeric([65536, 1, 2, 3])).toBe(false); 185 | done(); 186 | }) 187 | it("should reject an array with non-numeric elements", function (done) { 188 | expect(rfxcom.RawTx._anyNonNumeric(["65536", 1, 2, 3])).toBe(true); 189 | done(); 190 | }) 191 | }); 192 | }); -------------------------------------------------------------------------------- /test/thermostat1.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 12/07/2017. 3 | */ 4 | const rfxcom = require('../lib'), 5 | matchers = require('./matchers'), 6 | FakeSerialPort = require('./helper'); 7 | 8 | describe('Thermostat1 class', function () { 9 | let thermostat = {}, 10 | fakeSerialPort = {}, 11 | device = {}; 12 | beforeEach(function () { 13 | jasmine.addMatchers({ 14 | toHaveSent: matchers.toHaveSent 15 | }); 16 | fakeSerialPort = new FakeSerialPort(); 17 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 18 | port: fakeSerialPort 19 | }); 20 | device.connected = true; 21 | }); 22 | afterEach(function () { 23 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 24 | }); 25 | describe('DIGIMAX_TLX7506', function () { 26 | beforeEach(function () { 27 | thermostat = new rfxcom.Thermostat1(device, rfxcom.thermostat1.DIGIMAX_TLX7506); 28 | }); 29 | describe('commands', function () { 30 | describe('sendMessage()', function () { 31 | it('should send the correct bytes to the serialport', function (done) { 32 | let sentCommandId = NaN; 33 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 34 | sentCommandId = cmdId; 35 | done(); 36 | }); 37 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x00, 0x00, 0x12, 0x34, 0x19, 0x17, 0x02, 0x00]); 38 | expect(sentCommandId).toEqual(0); 39 | }); 40 | it('should handle string "mode" and "status" parameters', function (done) { 41 | let sentCommandId = NaN; 42 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:"No Demand", mode:"Heating"}, function (err, response, cmdId) { 43 | sentCommandId = cmdId; 44 | done(); 45 | }); 46 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x00, 0x00, 0x12, 0x34, 0x19, 0x17, 0x02, 0x00]); 47 | expect(sentCommandId).toEqual(0); 48 | }); 49 | it('should throw an error with an invalid temperature', function () { 50 | expect(function () { 51 | thermostat.sendMessage('0x1234', {temperature:99, setpoint:22.5, status:2, mode:0}); 52 | }).toThrow(new Error(("Invalid parameter temperature: must be in range 0-50"))); 53 | }); 54 | it('should throw an error with an invalid setpoint', function () { 55 | expect(function () { 56 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:47, status:2, mode:0}); 57 | }).toThrow(new Error(("Invalid parameter setpoint: must be in range 5-45"))); 58 | }); 59 | it('should throw an error with an invalid status', function () { 60 | expect(function () { 61 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:5, mode:0}); 62 | }).toThrow(new Error(("Invalid parameter status: must be in range 0-3"))); 63 | }); 64 | it('should throw an error with an invalid mode', function () { 65 | expect(function () { 66 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:2, mode:-1}); 67 | }).toThrow(new Error(("Invalid parameter mode: must be 0 or 1"))); 68 | }); 69 | }); 70 | }); 71 | describe('address checking', function () { 72 | it('should throw an exception with an invalid deviceId format', function () { 73 | expect(function () { 74 | thermostat.sendMessage('0x1234/A', {temperature:24.5, setpoint:22.5, status:2, mode:0}); 75 | }).toThrow(new Error(("Invalid deviceId format"))); 76 | }); 77 | it('should accept the highest address', function (done) { 78 | let sentCommandId = NaN; 79 | thermostat.sendMessage('0xffff', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 80 | sentCommandId = cmdId; 81 | done(); 82 | }); 83 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x00, 0x00, 0xff, 0xff, 0x19, 0x17, 0x02, 0x00]); 84 | expect(sentCommandId).toEqual(0); 85 | }); 86 | it('should accept the lowest address', function (done) { 87 | let sentCommandId = NaN; 88 | thermostat.sendMessage('0x0', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 89 | sentCommandId = cmdId; 90 | done(); 91 | }); 92 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x00, 0x00, 0x00, 0x00, 0x19, 0x17, 0x02, 0x00]); 93 | expect(sentCommandId).toEqual(0); 94 | }); 95 | it('should throw an exception with an invalid address 0x10000', function () { 96 | expect(function () { 97 | thermostat.sendMessage('0x10000', {temperature:24.5, setpoint:22.5, status:2, mode:0}); 98 | }).toThrow(new Error(("Address 0x10000 outside valid range"))); 99 | }); 100 | }); 101 | }); 102 | describe('DIGIMAX_SHORT', function () { 103 | beforeEach(function () { 104 | thermostat = new rfxcom.Thermostat1(device, rfxcom.thermostat1.DIGIMAX_SHORT); 105 | }); 106 | describe('commands', function () { 107 | describe('sendMessage()', function () { 108 | it('should send the correct bytes to the serialport', function (done) { 109 | let sentCommandId = NaN; 110 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 111 | sentCommandId = cmdId; 112 | done(); 113 | }); 114 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x01, 0x00, 0x12, 0x34, 0x19, 0x00, 0x02, 0x00]); 115 | expect(sentCommandId).toEqual(0); 116 | }); 117 | it('should throw an error with an invalid temperature', function () { 118 | expect(function () { 119 | thermostat.sendMessage('0x1234', {temperature:99, setpoint:22.5, status:2, mode:0}); 120 | }).toThrow(new Error(("Invalid parameter temperature: must be in range 0-50"))); 121 | }); 122 | it('should not throw an error with an invalid setpoint', function (done) { 123 | let sentCommandId = NaN; 124 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:47, status:2, mode:0}, function (err, response, cmdId) { 125 | sentCommandId = cmdId; 126 | done(); 127 | }); 128 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x01, 0x00, 0x12, 0x34, 0x19, 0x00, 0x02, 0x00]); 129 | expect(sentCommandId).toEqual(0); 130 | }); 131 | it('should throw an error with an invalid status', function () { 132 | expect(function () { 133 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:5, mode:0}); 134 | }).toThrow(new Error(("Invalid parameter status: must be in range 0-3"))); 135 | }); 136 | it('should throw an error with an invalid mode', function () { 137 | expect(function () { 138 | thermostat.sendMessage('0x1234', {temperature:24.5, setpoint:22.5, status:2, mode:-1}); 139 | }).toThrow(new Error(("Invalid parameter mode: must be 0 or 1"))); 140 | }); 141 | }); 142 | }); 143 | describe('address checking', function () { 144 | it('should throw an exception with an invalid deviceId format', function () { 145 | expect(function () { 146 | thermostat.sendMessage('0x1234/A', {temperature:24.5, setpoint:22.5, status:2, mode:0}); 147 | }).toThrow(new Error(("Invalid deviceId format"))); 148 | }); 149 | it('should accept the highest address', function (done) { 150 | let sentCommandId = NaN; 151 | thermostat.sendMessage('0xffff', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 152 | sentCommandId = cmdId; 153 | done(); 154 | }); 155 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x01, 0x00, 0xff, 0xff, 0x19, 0x00, 0x02, 0x00]); 156 | expect(sentCommandId).toEqual(0); 157 | }); 158 | it('should accept the lowest address', function (done) { 159 | let sentCommandId = NaN; 160 | thermostat.sendMessage('0x0', {temperature:24.5, setpoint:22.5, status:2, mode:0}, function (err, response, cmdId) { 161 | sentCommandId = cmdId; 162 | done(); 163 | }); 164 | expect(fakeSerialPort).toHaveSent([0x09, 0x40, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x02, 0x00]); 165 | expect(sentCommandId).toEqual(0); 166 | }); 167 | it('should throw an exception with an invalid address 0x10000', function () { 168 | expect(function () { 169 | thermostat.sendMessage('0x10000', {temperature:24.5, setpoint:22.5, status:2, mode:0}); 170 | }).toThrow(new Error(("Address 0x10000 outside valid range"))); 171 | }); 172 | }); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/thermostat2.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by max on 12/07/2017. 3 | */ 4 | const rfxcom = require('../lib'), 5 | matchers = require('./matchers'), 6 | FakeSerialPort = require('./helper'); 7 | 8 | describe('Thermostat2 class', function () { 9 | let thermostat = {}, 10 | fakeSerialPort = {}, 11 | device = {}; 12 | beforeEach(function () { 13 | jasmine.addMatchers({ 14 | toHaveSent: matchers.toHaveSent 15 | }); 16 | fakeSerialPort = new FakeSerialPort(); 17 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 18 | port: fakeSerialPort 19 | }); 20 | device.connected = true; 21 | }); 22 | afterEach(function () { 23 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 24 | }); 25 | describe('HE105', function () { 26 | beforeEach(function () { 27 | thermostat = new rfxcom.Thermostat2(device, rfxcom.thermostat2.HE105); 28 | }); 29 | describe('commands', function () { 30 | describe('switchOn()', function () { 31 | it('should send the correct bytes to the serialport', function (done) { 32 | let sentCommandId = NaN; 33 | thermostat.switchOn('0x12', function (err, response, cmdId) { 34 | sentCommandId = cmdId; 35 | done(); 36 | }); 37 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x00, 0x00, 0x12, 0x01, 0x00]); 38 | expect(sentCommandId).toEqual(0); 39 | }); 40 | }); 41 | describe('switchOff()', function () { 42 | it('should send the correct bytes to the serialport', function (done) { 43 | let sentCommandId = NaN; 44 | thermostat.switchOff('0x12', function (err, response, cmdId) { 45 | sentCommandId = cmdId; 46 | done(); 47 | }); 48 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x00, 0x00, 0x12, 0x00, 0x00]); 49 | expect(sentCommandId).toEqual(0); 50 | }); 51 | }); 52 | describe('program()', function () { 53 | it('should throw an error with an invalid temperature', function () { 54 | expect(function () { 55 | thermostat.program('0x12'); 56 | }).toThrow(new Error(("Device does not support program()"))); 57 | }); 58 | 59 | }); 60 | }); 61 | describe('address checking', function () { 62 | it('should throw an exception with an invalid deviceId format', function () { 63 | expect(function () { 64 | thermostat.switchOn('0x1234/A'); 65 | }).toThrow(new Error(("Invalid deviceId format"))); 66 | }); 67 | it('should accept the highest address', function (done) { 68 | let sentCommandId = NaN; 69 | thermostat.switchOn('0x1f', function (err, response, cmdId) { 70 | sentCommandId = cmdId; 71 | done(); 72 | }); 73 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x00, 0x00, 0x1f, 0x01, 0x00]); 74 | expect(sentCommandId).toEqual(0); 75 | }); 76 | it('should accept the lowest address', function (done) { 77 | let sentCommandId = NaN; 78 | thermostat.switchOn('0x0', function (err, response, cmdId) { 79 | sentCommandId = cmdId; 80 | done(); 81 | }); 82 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00]); 83 | expect(sentCommandId).toEqual(0); 84 | }); 85 | it('should throw an exception with an invalid address 0x20', function () { 86 | expect(function () { 87 | thermostat.switchOn('0x20'); 88 | }).toThrow(new Error(("Address 0x20 outside valid range"))); 89 | }); 90 | }); 91 | }); 92 | describe('RTS10_RFS10_TLX1206', function () { 93 | beforeEach(function () { 94 | thermostat = new rfxcom.Thermostat2(device, rfxcom.thermostat2.RTS10_RFS10_TLX1206); 95 | }); 96 | describe('commands', function () { 97 | describe('switchOn()', function () { 98 | it('should send the correct bytes to the serialport', function (done) { 99 | let sentCommandId = NaN; 100 | thermostat.switchOn('0x12', function (err, response, cmdId) { 101 | sentCommandId = cmdId; 102 | done(); 103 | }); 104 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x01, 0x00, 0x12, 0x01, 0x00]); 105 | expect(sentCommandId).toEqual(0); 106 | }); 107 | }); 108 | describe('switchOff()', function () { 109 | it('should send the correct bytes to the serialport', function (done) { 110 | let sentCommandId = NaN; 111 | thermostat.switchOff('0x12', function (err, response, cmdId) { 112 | sentCommandId = cmdId; 113 | done(); 114 | }); 115 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x01, 0x00, 0x12, 0x00, 0x00]); 116 | expect(sentCommandId).toEqual(0); 117 | }); 118 | }); 119 | describe('program()', function () { 120 | it('should send the correct bytes to the serialport', function (done) { 121 | let sentCommandId = NaN; 122 | thermostat.program('0x12', function (err, response, cmdId) { 123 | sentCommandId = cmdId; 124 | done(); 125 | }); 126 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x01, 0x00, 0x12, 0x02, 0x00]); 127 | expect(sentCommandId).toEqual(0); 128 | }); 129 | }); 130 | }); 131 | describe('address checking', function () { 132 | it('should throw an exception with an invalid deviceId format', function () { 133 | expect(function () { 134 | thermostat.switchOn('0x1234/A'); 135 | }).toThrow(new Error(("Invalid deviceId format"))); 136 | }); 137 | it('should accept the highest address', function (done) { 138 | let sentCommandId = NaN; 139 | thermostat.switchOn('0x1f', function (err, response, cmdId) { 140 | sentCommandId = cmdId; 141 | done(); 142 | }); 143 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x01, 0x00, 0x1f, 0x01, 0x00]); 144 | expect(sentCommandId).toEqual(0); 145 | }); 146 | it('should accept the lowest address', function (done) { 147 | let sentCommandId = NaN; 148 | thermostat.switchOn('0x0', function (err, response, cmdId) { 149 | sentCommandId = cmdId; 150 | done(); 151 | }); 152 | expect(fakeSerialPort).toHaveSent([0x06, 0x41, 0x01, 0x00, 0x00, 0x01, 0x00]); 153 | expect(sentCommandId).toEqual(0); 154 | }); 155 | it('should throw an exception with an invalid address 0x20', function () { 156 | expect(function () { 157 | thermostat.switchOn('0x20'); 158 | }).toThrow(new Error(("Address 0x20 outside valid range"))); 159 | }); 160 | }); 161 | }); 162 | }); -------------------------------------------------------------------------------- /test/transmitter.spec.js: -------------------------------------------------------------------------------- 1 | const rfxcom = require('../lib'), 2 | matchers = require('./matchers'), 3 | FakeSerialPort = require('./helper'); 4 | 5 | describe('Transmitter class', function () { 6 | let transmitter = {}, 7 | fakeSerialPort = {}, 8 | device = {}; 9 | beforeEach(function () { 10 | jasmine.addMatchers({ 11 | toHaveSent: matchers.toHaveSent 12 | }); 13 | fakeSerialPort = new FakeSerialPort(); 14 | device = new rfxcom.RfxCom('/dev/ttyUSB0', { 15 | port: fakeSerialPort 16 | }); 17 | device.connected = true; 18 | // transmitter = new Transmitter(device, null, {}); 19 | }); 20 | afterEach(function () { 21 | device.acknowledge.forEach(acknowledge => {if (typeof acknowledge === "function") {acknowledge()}}); 22 | }); 23 | describe('constructor', function () { 24 | it ('should construct an object if asked nicely', function () { 25 | "use strict"; 26 | transmitter = new rfxcom.Transmitter(device, 0, {key: 1}); 27 | expect(transmitter.rfxcom).toBe(device); 28 | expect(transmitter.subtype).toBe(0); 29 | expect(transmitter.options).toEqual({key: 1}); 30 | }); 31 | it ('should handle null subtype and missing options', function () { 32 | "use strict"; 33 | transmitter = new rfxcom.Transmitter(device, null); 34 | expect(transmitter.rfxcom).toBe(device); 35 | expect(transmitter.subtype).toBe(null); 36 | expect(transmitter.options).toEqual({}); 37 | }); 38 | it ('should throw an exception if no subtype is supplied', function () { 39 | expect(function () {new rfxcom.Transmitter(device)}).toThrow(new Error(("Must provide a subtype."))) 40 | }) 41 | }); 42 | describe('instance methods', function () { 43 | beforeEach(function () { 44 | transmitter = new rfxcom.Transmitter(device, null, {}); 45 | }); 46 | describe('_timeoutHandler()', function () { 47 | it('should return false', function () { 48 | "use strict"; 49 | expect(transmitter._timeoutHandler(null, null)).toBe(false); 50 | }); 51 | }); 52 | describe('setOption()', function () { 53 | it('should set an option without disturbing other options', function () { 54 | "use strict"; 55 | expect(transmitter.options).toEqual({}); 56 | transmitter.setOption({first: 1}); 57 | expect(transmitter.options).toEqual({first: 1}); 58 | transmitter.setOption({second: 1}); 59 | expect(transmitter.options).toEqual({first: 1, second: 1}); 60 | transmitter.setOption({second: 2}); 61 | expect(transmitter.options).toEqual({first: 1, second: 2}); 62 | }); 63 | }); 64 | describe('sendRaw()', function () { 65 | it('should send the correct bytes to the serialport', function (done) { 66 | let sentCommandId = NaN; 67 | transmitter.sendRaw(0x19, 0x06, [0x12, 0x34, 0x56, 0x73, 0x01, 0x00], function (err, response, cmdId) { 68 | sentCommandId = cmdId; 69 | done(); 70 | }); 71 | expect(fakeSerialPort).toHaveSent([0x09, 0x19, 0x06, 0x00, 0x12, 0x34, 0x56, 0x73, 0x01, 0x00]); 72 | expect(sentCommandId).toEqual(0); 73 | }); 74 | 75 | }); 76 | }); 77 | describe('static methods', function () { 78 | describe('splitAtSlashes', function () { 79 | it('should return an array unaltered', function () { 80 | expect(rfxcom.Transmitter.splitAtSlashes(["A", "B", "C"])).toEqual(["A", "B", "C"]); 81 | }); 82 | it('should turn a string without slashes into a single-elemnt array', function () { 83 | expect(rfxcom.Transmitter.splitAtSlashes("A")).toEqual(["A"]); 84 | }); 85 | it('should split a string at slashes', function () { 86 | expect(rfxcom.Transmitter.splitAtSlashes("A/B/C")).toEqual(["A", "B", "C"]); 87 | }); 88 | it('should not split a string array at slashes', function () { 89 | expect(rfxcom.Transmitter.splitAtSlashes(["A/B/C"])).toEqual(["A/B/C"]); 90 | }); 91 | }) 92 | }); 93 | }); 94 | --------------------------------------------------------------------------------