├── .gitignore ├── images ├── example-automation.jpg └── example-notifications.jpg ├── CHANGELOG.md ├── package.json ├── LICENSE ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /images/example-automation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-christian/homebridge-texecom/HEAD/images/example-automation.jpg -------------------------------------------------------------------------------- /images/example-notifications.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-christian/homebridge-texecom/HEAD/images/example-notifications.jpg -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | This change log documents all release versions of homebridge-texecom 4 | 5 | ### 1.0.3 (2017-01-28) 6 | 7 | - **FIX** - Zone matching did not work at all in previous release. 8 | - **FEATURE** - A dwell time is now configureable for each zone before activation is cleared. 9 | - **FIX** - Breaks added to zone searching for added performance. 10 | 11 | ### 1.0.2 (2017-01-24) 12 | 13 | - **TWEAK** - Zone matching made much more efficient for added improvement. 14 | 15 | ### 1.0.1 (2017-01-21) 16 | 17 | - **FIX** - Dependencies for serialport were incorrect which prevented NPM installation. 18 | 19 | ### 1.0.0 (2017-01-21) 20 | 21 | - **FEATURE** - Initial release. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-texecom", 3 | "description": "A plugin for homebridge (https://github.com/nfarina/homebridge) to integrate Texecom Premier Elite alarm systems into HomeKit", 4 | "version": "3.1.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/max-christian/homebridge-texecom" 8 | }, 9 | "license": "MIT", 10 | "preferGlobal": true, 11 | "keywords": [ 12 | "homebridge-plugin", 13 | "motion", 14 | "smoke", 15 | "contact", 16 | "carbon monoxide", 17 | "door", 18 | "sensor", 19 | "intruder", 20 | "Texecom" 21 | ], 22 | "engines": { 23 | "node": ">=0.12.0", 24 | "homebridge": ">=0.2.5" 25 | }, 26 | "dependencies": { 27 | "debug": "^2.2.0", 28 | "serialport": "^8.0.8", 29 | "string": "^3.3.3", 30 | "zpad": "^0.5.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kieran Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-texecom 2 | 3 | A plugin for [Homebridge](https://github.com/nfarina/homebridge) that creates HomeKit motion, contact, smoke, or carbon monoxide sensors for alarm zones from a Texecom Premier intruder alarm via a serial or IP connection. homebridge-texecom was originated by [Kieran Jones](https://github.com/kieranmjones). 4 | 5 | You can receive notifications, which can be set to work only when you're away from home: 6 | 7 | ![example of notifications](https://github.com/max-christian/homebridge-texecom/blob/master/images/example-notifications.jpg?raw=true) 8 | 9 | Another great use is to use the alarm's motion sensors to switch lights on automatically: 10 | 11 | ![example of automation](https://github.com/max-christian/homebridge-texecom/blob/master/images/example-automation.jpg?raw=true) 12 | 13 | You can also set automations to happen when you arm the alarm and when the alarm goes off. You can arm and disarm the alarm directly from HomeKit if you know your alarm's UDL code. You need the UDL, sometimes known as the engineer code, for arm/disarm to work - the number you enter on the panel to arm/disarm will not work. 14 | 15 | ## Configuration 16 | 17 | Texecom zones must be configured individually in the Homebridge config.json file with the appropriate zone number from Texecom. Configuring areas is optional, but is required if you want to see the arm/disarm/triggered state. If you want to arm/disarm using HomeKit then you also need to configure the UDL. You probably have many zones and only one area. 18 | 19 | Serial Example: 20 | 21 | ```json 22 | "platforms": [ 23 | { 24 | "platform": "Texecom", 25 | "serial_device": "/dev/ttyUSB0", 26 | "baud_rate": 19200, 27 | "zones": [ 28 | { 29 | "name": "Living Room", 30 | "zone_number": "7", 31 | "zone_type": "motion", 32 | "dwell": 1000 33 | }, 34 | { 35 | "name": "Front Door", 36 | "zone_number": "15", 37 | "zone_type": "contact", 38 | "dwell": 1000 39 | } 40 | ], 41 | "areas": [ 42 | { 43 | "name": "Texecom Alarm", 44 | "area_number": "1", 45 | "area_type": "securitysystem", 46 | "dwell": 0 47 | } 48 | ] 49 | } 50 | ] 51 | ``` 52 | 53 | IP Example with UDL: 54 | 55 | ```json 56 | "platforms": [ 57 | { 58 | "platform": "Texecom", 59 | "ip_address": "192.168.0.100", 60 | "ip_port": 10001, 61 | "udl": "123456", 62 | "zones": [ 63 | { 64 | "name": "Living Room", 65 | "zone_number": "7", 66 | "zone_type": "motion", 67 | "dwell": 1000 68 | }, 69 | { 70 | "name": "Front Door", 71 | "zone_number": "15", 72 | "zone_type": "contact", 73 | "dwell": 1000 74 | } 75 | ], 76 | "areas": [ 77 | { 78 | "name": "Texecom Alarm", 79 | "area_number": "1", 80 | "area_type": "securitysystem", 81 | "dwell": 0 82 | } 83 | ] 84 | } 85 | ] 86 | ``` 87 | 88 | ### Global Configuration 89 | 90 | For serial connections: 91 | 92 | | Key | Default | Description | 93 | | --- | --- | --- | 94 | | `serial_device` | N/A | The serial device on which to connect to Texecom | 95 | | `baud_rate` | N/A | The baud rate configured in Texecom (Usually 19200) | 96 | | `zones` | N/A | The individual configuration for each zone in Texecom | 97 | 98 | For IP connections: 99 | 100 | | Key | Default | Description | 101 | | --- | --- | --- | 102 | | `ip_address` | N/A | The IP address of the COM-IP Texecom module | 103 | | `ip_port` | N/A | The TCP port of the COM-IP Texecom module | 104 | 105 | ### Per-zone Configuration 106 | 107 | This plugin is a platform plugin so you must configure each zone from your Texecom intruder alarm into your config individually. 108 | 109 | | Key | Default | Description | 110 | | --- | --- | --- | 111 | | `name` | N/A | The name of the area as it will appear in HomeKit, e.g. 'Texecom Alarm'. | 112 | | `zone_number` | N/A | The zone number from Texecom | 113 | | `zone_type` | `"motion"` | The type of zone; motion, contact, smoke, or carbonmonoxide | 114 | | `dwell` | 0 | The amount of time in ms that a zone stays active after zone activation is cleared by Texecom | 115 | 116 | ### Per-area Configuration 117 | 118 | | Key | Default | Description | 119 | | --- | --- | --- | 120 | | `name` | N/A | The name of the sensor as it will appear in HomeKit. | 121 | | `area_number` | N/A | The area number from Texecom, usually 1. | 122 | | `area_type` | `"securitysystem"` | The type of area; only securitysystem is supported. | 123 | | `dwell` | 0 | | 124 | 125 | ## Configuring Texecom 126 | 127 | Ensure your intruder alarm is fully configured and operational, connect a USB-Com or PC-Com cable to COM1 on the panel PCB and then connect to the computer running Homebridge. 128 | 129 | To configure your COM1 port for the Crestron protocol: 130 | 131 | 1. Enter your engineer code 132 | 2. Scroll until you find "UDL/Digi Options" 133 | 3. Press 8 to jump to "Com Port Setup" 134 | 4. Scroll to "Com Port 1" 135 | 5. Press "No" to edit the port 136 | 6. Press 8 to jump to "Crestron System" 137 | 7. Press "Yes" to confirm and save. 138 | 139 | Press "Menu" repeatedly to exit the engineer menu. 140 | 141 | If connecting to a COM-IP, set up the COM-IP as usual and ensure it is working, e.g. by connecting with Wintex. Then change the configuration for the port the COM-IP is connected to to Crestron as detailed above. This allows the panel to configure the IP address into the module, then changing to Crestron will allow the panel to input/output the correct commands. 142 | 143 | ## Many thanks 144 | 145 | - [Kieran Jones](https://github.com/kieranmjones) for originating homebridge-texecom and who first freely documented the Cestron protocol. 146 | - [Chris Shucksmith](https://github.com/shuckc) provided useful documentation of the [Simple Protocol](https://github.com/shuckc/pialarm/blob/master/protocol/readme.md) 147 | - [Chris Posthumus](https://github.com/K1LL3R234) contributed arm/disarm code. 148 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Service; 2 | var Characteristic; 3 | var debug = require("debug")("TexecomAccessory"); 4 | var serialport = require("serialport"); 5 | var zpad = require("zpad"); 6 | var S = require('string'); 7 | var crypto = require("crypto"); 8 | var net = require('net'); 9 | 10 | const EventEmitter = require('events'); 11 | class ResponseEmitter extends EventEmitter { } 12 | const responseEmitter = new ResponseEmitter(); 13 | 14 | var setByAlarm = false; 15 | 16 | module.exports = function(homebridge) { 17 | Service = homebridge.hap.Service; 18 | Characteristic = homebridge.hap.Characteristic; 19 | 20 | homebridge.registerAccessory("homebridge-texecom", "Texecom", TexecomAccessory); 21 | homebridge.registerPlatform("homebridge-texecom", "Texecom", TexecomPlatform); 22 | } 23 | 24 | function TexecomPlatform(log, config){ 25 | this.log = log; 26 | this.serial_device = config["serial_device"]; 27 | this.baud_rate = config["baud_rate"]; 28 | this.udl = config["udl"]; 29 | this.zones = config["zones"] || []; 30 | this.areas = config["areas"] || []; 31 | this.ip_address = config["ip_address"]; 32 | this.ip_port = config["ip_port"]; 33 | } 34 | 35 | TexecomPlatform.prototype = { 36 | 37 | accessories: function(callback) { 38 | var zoneAccessories = []; 39 | for(var i = 0; i < this.zones.length; i++){ 40 | var zone = new TexecomAccessory(this.log, this.zones[i]); 41 | zoneAccessories.push(zone); 42 | } 43 | var zoneCount = zoneAccessories.length; 44 | 45 | var areaAccessories = []; 46 | for(var i = 0; i < this.areas.length; i++){ 47 | var area = new TexecomAccessory(this.log, this.areas[i]); 48 | areaAccessories.push(area); 49 | } 50 | var areaCount = areaAccessories.length; 51 | 52 | callback(zoneAccessories.concat(areaAccessories)); 53 | platform = this; 54 | 55 | function processData(data) { 56 | // Received data is a zone update 57 | if(S(data).startsWith('"Z')){ 58 | 59 | // Extract the data from the serial line received 60 | var zone_data = Number(S(S(data).chompLeft('"Z')).left(4).s); 61 | // Extract the zone number that is being updated 62 | var updated_zone = Number(S(S(data).chompLeft('"Z')).left(3).s); 63 | // Is the zone active? 64 | var zone_active = S(zone_data).endsWith('1'); 65 | 66 | platform.log("Zone update received for zone " + updated_zone + " active: " + zone_active); 67 | 68 | for(var i = 0; i < zoneCount; i++){ 69 | if(zoneAccessories[i].zone_number == updated_zone){ 70 | platform.log.debug("Zone match found, updating zone status in HomeKit to " + zone_active); 71 | zoneAccessories[i].changeHandler(zone_active); 72 | break; 73 | } 74 | } 75 | 76 | } else if (S(data).startsWith('"A') || S(data).startsWith('"D') || S(data).startsWith('"L')){ 77 | 78 | // Extract the area number that is being updated 79 | var updated_area = Number(S(S(data).substring(2,5))); 80 | var status = S(data).substring(1,2); 81 | var stateValue; 82 | 83 | switch (String(status)) { 84 | case "L": 85 | stateValue = Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; 86 | platform.log("Area " + updated_area + " triggered"); 87 | break; 88 | case "D": 89 | stateValue = Characteristic.SecuritySystemCurrentState.DISARMED; 90 | platform.log("Area " + updated_area + " disarmed"); 91 | break; 92 | case "A": 93 | stateValue = Characteristic.SecuritySystemCurrentState.AWAY_ARM; 94 | platform.log("Area " + updated_area + " armed"); 95 | break; 96 | default: 97 | platform.log("Unknown status letter " + status); 98 | return; 99 | } 100 | 101 | for(var i = 0; i < areaCount; i++){ 102 | if(areaAccessories[i].zone_number == updated_area){ 103 | platform.log.debug("Area match found, updating area status in HomeKit to " + stateValue); 104 | setByAlarm = true; 105 | areaAccessories[i].changeHandler(stateValue); 106 | break; 107 | } 108 | } 109 | 110 | } else { 111 | platform.log.debug("Unknown string from Texecom: " + S(data)); 112 | } 113 | } 114 | 115 | if (this.serial_device) { 116 | var SerialPort = serialport.SerialPort; 117 | 118 | var serialPort = new SerialPort(this.serial_device, { 119 | baudrate: this.baud_rate, 120 | parser: serialport.parsers.readline("\n") 121 | }); 122 | 123 | serialPort.on("open", function () { 124 | platform.log("Serial port opened"); 125 | serialPort.on('data', function(data) { 126 | platform.log.debug("Serial data received: " + data); 127 | responseEmitter.emit('data', data); 128 | processData(data); 129 | }); 130 | }); 131 | 132 | this.texecomConnection = serialPort; 133 | } else if (this.ip_address) { 134 | try { 135 | connection = net.createConnection(platform.ip_port, platform.ip_address, function() { 136 | platform.log('Connected via IP'); 137 | }); 138 | } catch (err) { 139 | platform.log(err); 140 | } 141 | connection.setNoDelay(true); 142 | connection.on('data', function(data) { 143 | platform.log.debug("IP data received: " + data); 144 | responseEmitter.emit('data', data); 145 | processData(data); 146 | }); 147 | connection.on('end', function() { 148 | platform.log('IP connection ended'); 149 | }); 150 | 151 | connection.on('close', function() { 152 | platform.log('IP connection closed'); 153 | try { 154 | connection = net.createConnection(platform.ip_port, platform.ip_address, function() { 155 | platform.log('Re-connected after loss of connection'); 156 | }); 157 | } catch (err) { 158 | platform.log(err); 159 | } 160 | }); 161 | 162 | this.texecomConnection = connection; 163 | } else { 164 | this.log("Must set either serial_device or ip_address in configuration."); 165 | } 166 | } 167 | } 168 | 169 | function TexecomAccessory(log, zoneConfig) { 170 | this.log = log; 171 | 172 | this.zone_number = zpad(zoneConfig["zone_number"] || zoneConfig["area_number"], 3); 173 | this.name = zoneConfig["name"]; 174 | this.zone_type = zoneConfig["zone_type"] || zoneConfig["area_type"] || "motion"; 175 | this.dwell_time = zoneConfig["dwell"] || 0; 176 | 177 | if(zoneConfig["sn"]){ 178 | this.sn = zoneConfig["sn"]; 179 | } else { 180 | var shasum = crypto.createHash('sha1'); 181 | shasum.update(this.zone_number); 182 | 183 | this.sn = shasum.digest('base64'); 184 | log.debug('Computed SN ' + this.sn); 185 | } 186 | } 187 | 188 | TexecomAccessory.prototype = { 189 | 190 | getServices: function() { 191 | const me = this; 192 | var informationService = new Service.AccessoryInformation(); 193 | 194 | informationService 195 | .setCharacteristic(Characteristic.Name, this.name) 196 | .setCharacteristic(Characteristic.Manufacturer, "Homebridge") 197 | .setCharacteristic(Characteristic.Model, "Texecom Zone") 198 | .setCharacteristic(Characteristic.SerialNumber, this.sn); 199 | 200 | var service, changeAction; 201 | switch(this.zone_type){ 202 | case "contact": 203 | service = new Service.ContactSensor(); 204 | changeAction = function(newState){ 205 | service.getCharacteristic(Characteristic.ContactSensorState) 206 | .setValue(newState ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED); 207 | }; 208 | break; 209 | case "motion": 210 | service = new Service.MotionSensor(); 211 | changeAction = function(newState){ 212 | service.getCharacteristic(Characteristic.MotionDetected) 213 | .setValue(newState); 214 | }; 215 | break; 216 | case "smoke": 217 | service = new Service.SmokeSensor(); 218 | changeAction = function(newState){ 219 | service.getCharacteristic(Characteristic.SmokeDetected) 220 | .setValue(newState ? Characteristic.ContactSensorState.SMOKE_DETECTED : Characteristic.ContactSensorState.SMOKE_NOT_DETECTED); 221 | }; 222 | break; 223 | case "carbonmonoxide": 224 | service = new Service.CarbonMonoxideSensor(); 225 | changeAction = function(newState){ 226 | service.getCharacteristic(Characteristic.CarbonMonoxideDetected) 227 | .setValue(newState ? Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL : Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL); 228 | }; 229 | break; 230 | case "securitysystem": 231 | service = new Service.SecuritySystem(); 232 | changeAction = function(newState){ 233 | var targetState; 234 | switch (newState) { 235 | case Characteristic.SecuritySystemCurrentState.NIGHT_ARM: 236 | targetState = Characteristic.SecuritySystemTargetState.NIGHT_ARM; 237 | break; 238 | case Characteristic.SecuritySystemCurrentState.AWAY_ARM: 239 | targetState = Characteristic.SecuritySystemTargetState.AWAY_ARM; 240 | break; 241 | case Characteristic.SecuritySystemCurrentState.STAY_ARM: 242 | targetState = Characteristic.SecuritySystemTargetState.STAY_ARM; 243 | break; 244 | case Characteristic.SecuritySystemCurrentState.DISARMED: 245 | targetState = Characteristic.SecuritySystemTargetState.DISARM; 246 | break; 247 | default: 248 | targetState = null; // alarm triggered has no corresponding target state 249 | break; 250 | } 251 | service.getCharacteristic(Characteristic.SecuritySystemCurrentState).setValue(newState); 252 | if (targetState != null) { 253 | service.getCharacteristic(Characteristic.SecuritySystemTargetState).setValue(targetState); 254 | } 255 | me.log("Set target state " + targetState + " and current state " + newState + " in response to notification from alarm"); 256 | }; 257 | 258 | // we don't know the alarm's state at startup, safer to assume disarmed: 259 | changeAction(Characteristic.SecuritySystemCurrentState.DISARMED); 260 | 261 | var area = this; 262 | 263 | service.getCharacteristic(Characteristic.SecuritySystemTargetState) 264 | .on('set', function (value, callback) { 265 | if (setByAlarm) { 266 | platform.log("Not sending command to alarm for change to state " + value + " because the state change appears to have come from the alarm itself."); 267 | setByAlarm = false; 268 | return; 269 | } 270 | if (platform.udl != null) { 271 | areaTargetSecurityStateSet(platform, area, service, value, callback); 272 | } else { 273 | platform.log("No UDL configured. Add your UDL to enable arm/disarm from HomeKit."); 274 | callback(new Error("No UDL configured")); 275 | } 276 | }); 277 | break; 278 | default: 279 | service = new Service.MotionSensor(); 280 | changeAction = function(newState){ 281 | service.getCharacteristic(Characteristic.MotionDetected) 282 | .setValue(newState); 283 | }; 284 | break; 285 | } 286 | 287 | this.changeHandler = function(status){ 288 | var newState = status; 289 | platform.log.debug("Dwell = " + this.dwell_time); 290 | 291 | if(!newState && this.dwell_time > 0){ 292 | this.dwell_timer = setTimeout(function(){ changeAction(newState); }.bind(this), this.dwell_time); 293 | } else { 294 | if(this.dwell_timer){ 295 | clearTimeout(this.dwell_timer); 296 | } 297 | changeAction(newState); 298 | } 299 | 300 | platform.log.debug("Changing state with changeHandler to " + newState); 301 | 302 | }.bind(this); 303 | 304 | return [informationService, service]; 305 | } 306 | }; 307 | 308 | function areaTargetSecurityStateSet(platform, accessory, service, value, callback) { 309 | var area_number = String.fromCharCode(accessory.zone_number); 310 | // thanks to @K1LL3R234 for contributing arm/disarm function: 311 | var command; 312 | switch (value) { 313 | case Characteristic.SecuritySystemTargetState.NIGHT_ARM: 314 | command = "Y" + area_number; 315 | break; 316 | case Characteristic.SecuritySystemTargetState.AWAY_ARM: 317 | command = "A" + area_number; 318 | break; 319 | case Characteristic.SecuritySystemTargetState.STAY_ARM: 320 | case Characteristic.SecuritySystemTargetState.DISARM: 321 | command = "D" + area_number; 322 | break; 323 | default: 324 | this.log("Unknown target state: " + value); 325 | callback(new Error("Unknown target state")); 326 | return; 327 | } 328 | 329 | platform.log("Sending arm/disarm command " + value + " to area " + accessory.zone_number); 330 | 331 | writeCommandAndWaitForOK(platform.texecomConnection, "W" + platform.udl) 332 | .then(() => writeCommandAndWaitForOK(platform.texecomConnection, command)) 333 | .then(() => { 334 | // OK response from alarm is only indication that the target state has been reached 335 | var currentState; 336 | switch (value) { 337 | case Characteristic.SecuritySystemTargetState.NIGHT_ARM: 338 | currentState = Characteristic.SecuritySystemCurrentState.NIGHT_ARM; 339 | break; 340 | case Characteristic.SecuritySystemTargetState.AWAY_ARM: 341 | currentState = Characteristic.SecuritySystemCurrentState.AWAY_ARM; 342 | break; 343 | case Characteristic.SecuritySystemTargetState.STAY_ARM: 344 | currentState = Characteristic.SecuritySystemCurrentState.STAY_ARM; 345 | break; 346 | case Characteristic.SecuritySystemTargetState.DISARM: 347 | currentState = Characteristic.SecuritySystemCurrentState.DISARMED; 348 | break; 349 | default: 350 | platform.log("Unknown target alarm state " + value); 351 | callback(new Error("Unknown target state")); 352 | return; 353 | } 354 | platform.log("Setting current status of area " + accessory.zone_number + " to " + currentState + " because alarm responded OK"); 355 | service.getCharacteristic(Characteristic.SecuritySystemCurrentState) 356 | .setValue(currentState); 357 | callback(); 358 | }) 359 | .catch((err) => { 360 | platform.log("Callback with error " + err); 361 | callback(err); // Handle errors 362 | }); 363 | } 364 | 365 | function writeCommandAndWaitForOK(connection, command) { 366 | return new Promise((resolve, reject) => { 367 | function handleData(data) { 368 | if (data.toString().trim() === 'OK') { 369 | responseEmitter.removeListener('data', handleData); 370 | resolve(); 371 | } 372 | } 373 | 374 | responseEmitter.on('data', handleData); 375 | 376 | connection.write("\\" + command + "/", function (err) { 377 | if (err) { 378 | platform.log("Error writing to connection: " + err); 379 | reject(err); 380 | } else { 381 | //platform.log("Command sent: " + command); 382 | } 383 | }); 384 | 385 | setTimeout(() => { 386 | responseEmitter.removeListener('data', handleData); 387 | reject(new Error("Timeout")); 388 | }, 2000); 389 | }); 390 | } 391 | --------------------------------------------------------------------------------