├── .eslintrc.js ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── Readme.md ├── bin ├── insteon ├── insteon-door.js ├── insteon-io ├── insteon-light ├── insteon-link ├── insteon-map ├── insteon-plm └── insteon-therm ├── gruntfile.js ├── index.js ├── lib ├── Insteon │ ├── Door.js │ ├── GarageDoor.js │ ├── IO.js │ ├── IOLinc.js │ ├── Leak.js │ ├── Light.js │ ├── Meter.js │ ├── Motion.js │ ├── Thermostat.js │ ├── X10.js │ ├── httpClient.js │ ├── index.js │ └── utils.js ├── Test │ ├── Plan.js │ ├── mockHub.js │ └── mockSerial.js ├── assign.js └── index.js ├── package-lock.json ├── package.json └── test ├── .jshintrc ├── Door.js ├── Garage.js ├── IO.js ├── IOLinc.js ├── Insteon-ip.js ├── Insteon-serial.js ├── Leak.js ├── Light.js ├── Meter.js ├── Motion.js ├── Thermostat.js └── X10.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "commonjs": true, 4 | "node": true, 5 | "es6": false 6 | }, 7 | "extends": "eslint:recommended", 8 | "installedESLint": true, 9 | "parserOptions": { 10 | "ecmaVersion": 5, 11 | "ecmaFeatures": { 12 | }, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "indent": [ 17 | "error", 18 | 2 19 | ], 20 | "linebreak-style": [ 21 | "error", 22 | "unix" 23 | ], 24 | "semi": "error", 25 | }, 26 | "globals": { 27 | "process": true, 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | thumbs.db 3 | *.log 4 | node_modules/ 5 | .settings 6 | .session.vim 7 | coverage.html 8 | lib-cov/ 9 | *.sublime-workspace 10 | *.sublime-project 11 | tmp/ 12 | /.vscode 13 | /coverage 14 | bin/insteon-test-* 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "expr": true, 7 | "immed": true, 8 | "noarg": true, 9 | "quotmark": "single", 10 | "smarttabs": true, 11 | "trailing": true, 12 | "undef": true, 13 | "unused": true, 14 | "jquery": true, 15 | "browser": true, 16 | "node": true, 17 | "-W065": true, 18 | "strict": false, 19 | "esversion": 6 20 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automategreen/home-controller/8a71bacb46a468bb04b21b34f265afd088778f32/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "11" 5 | - "12" 6 | before_script: 7 | - npm install -g grunt-cli 8 | script: 9 | - grunt coverage 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Brandon Goode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bin/insteon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var pkg = require('../package.json'); 5 | var version = pkg.version; 6 | 7 | program 8 | .version(version) 9 | .command('link ', 'link device(s) to a gateway') 10 | .command('map ', 'map the Insteon network') 11 | .command('plm ', 'PLM interface to gateway') 12 | .parse(process.argv); -------------------------------------------------------------------------------- /bin/insteon-door.js: -------------------------------------------------------------------------------- 1 | var Insteon = require('../').Insteon; 2 | var host = 'COM10'; 3 | 4 | function logAllEmitterEvents(eventEmitter) 5 | { 6 | var emitToLog = eventEmitter.emit; 7 | 8 | eventEmitter.emit = function () { 9 | var event = arguments[0]; 10 | console.log('event emitted: ' + event); 11 | emitToLog.apply(eventEmitter, arguments); 12 | }; 13 | } 14 | 15 | console.log('Connecting to ' + host); 16 | var gw = new Insteon(); 17 | 18 | gw.on('connect', function () { 19 | var d = gw.door('2d4a42'); 20 | logAllEmitterEvents(d); 21 | 22 | gw.info('2d4a42', function(err, info) { 23 | console.log('err', err); 24 | console.log('info', info); 25 | }); 26 | /* 27 | 28 | console.log('Listening for events');*/ 29 | }); 30 | 31 | gw.on('close', function() { 32 | console.log('Connection closed'); 33 | }); 34 | 35 | gw.serial(host); 36 | -------------------------------------------------------------------------------- /bin/insteon-io: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var Insteon = require('../').Insteon; 6 | var host = 'COM10'; 7 | 8 | console.log('Connecting to ' + host); 9 | var gw = new Insteon(); 10 | 11 | gw.on('connect', function () { 12 | var g = gw.garage('4204e2'); 13 | /*g.open().then(function(success) { 14 | console.log(success); 15 | });*/ 16 | g.status() 17 | .then(function(status) { 18 | console.log('status:', status); 19 | }) 20 | /*.then(function() { 21 | console.log('trying to open'); 22 | g.close().then(function (status) { 23 | console.log(status); 24 | }); 25 | })*/ 26 | .then(function() { 27 | console.log('trying to close'); 28 | g.close().then(function (status) { 29 | console.log(status); 30 | }); 31 | }); 32 | }); 33 | 34 | gw.on('close', function() { 35 | console.log('Connection closed'); 36 | }); 37 | 38 | gw.serial(host); 39 | -------------------------------------------------------------------------------- /bin/insteon-light: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var Insteon = require('../').Insteon; 6 | var host = 'COM10'; 7 | 8 | console.log('Connecting to ' + host); 9 | var gw = new Insteon(); 10 | 11 | gw.on('connect', function () { 12 | console.log('Connected!'); 13 | 14 | /*setTimeout(function() { 15 | gw.info('2d37ce', function(err, info) { 16 | console.log('info', info) 17 | }); 18 | }, 100);*/ 19 | 20 | var light = gw.light('2d37ce'); 21 | light.on('turnOn', function() { 22 | console.log('Turned on'); 23 | }); 24 | light.on('turnOnFast', function() { 25 | console.log('Turned on Fast'); 26 | }); 27 | light.on('turnOff', function() { 28 | console.log('Turned off'); 29 | }); 30 | light.on('turnOffFast', function() { 31 | console.log('Turned off Fast'); 32 | }); 33 | 34 | setTimeout(function() { 35 | console.log('Scene command on'); 36 | gw.sceneOn(1, function() { 37 | console.log('Scene command done'); 38 | }); 39 | }, 1000); 40 | setTimeout(function() { 41 | console.log('Scene command off'); 42 | gw.sceneOff(1, function() { 43 | console.log('Scene command done'); 44 | gw.close(); 45 | }); 46 | }, 3000); 47 | }); 48 | 49 | gw.on('recvCommand', function() { 50 | //console.log('command received:', cmd) 51 | }); 52 | 53 | gw.on('close', function() { 54 | console.log('Connection closed'); 55 | }); 56 | 57 | gw.serial(host); 58 | -------------------------------------------------------------------------------- /bin/insteon-link: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var Insteon = require('../').Insteon; 6 | var util = require('util'); 7 | var program = require('commander'); 8 | var pkg = require('../package.json'); 9 | var version = pkg.version; 10 | 11 | program 12 | .version(version) 13 | .usage('[options] ') 14 | .option('-c, --controller', 'devices are the controller') 15 | .option('-l, --link', 'link devices') 16 | .option('-u, --unlink', 'unlink devices') 17 | .option('--remove [;]', 'remove link from device', list) 18 | .option('--cancel', 'cancel linking') 19 | .option('-g, --group ', 'group number to use for links', parseInt) 20 | .option('-v, --verbose', 'more output') 21 | .parse(process.argv); 22 | 23 | function list(val) { 24 | if (val.indexOf(';') > -1) { 25 | return [val.substr(0, val.indexOf(';')).replace(/\./g, ''), JSON.parse(val.substr(val.indexOf(';') + 1))]; 26 | } 27 | return [JSON.parse(val)]; 28 | } 29 | 30 | if (program.args.length < 1) { 31 | return program.help(); 32 | } 33 | 34 | var host, port; 35 | 36 | var devices = []; 37 | var idRegex = /^[0-9a-f]{2}\.?[0-9a-f]{2}\.?[0-9a-f]{2}$/i; 38 | 39 | program.args.forEach(function (arg) { 40 | if (idRegex.test(arg)) { 41 | devices.push(arg.replace(/\./g, '')); 42 | } else { 43 | var uriParts = arg.split(':'); 44 | 45 | host = uriParts[0]; 46 | if (uriParts.length > 1) { 47 | port = parseInt(uriParts[1]); 48 | } 49 | } 50 | }); 51 | 52 | 53 | 54 | console.log('Connecting to ' + host); 55 | var gw = new Insteon(); 56 | 57 | gw.on('connect', function () { 58 | if (program.remove) { 59 | if (program.remove.length === 2) { 60 | console.log('Remove link from ' + program.remove[0]); 61 | 62 | if (!isNaN(program.remove[1])) { 63 | gw.removeLinkAt(program.remove[0], parseInt(program.remove[1]), function () { 64 | return gw.close(); 65 | }); 66 | } else { 67 | gw.removeLink(program.remove[0], program.remove[1], function () { 68 | return gw.close(); 69 | }); 70 | } 71 | } else if (program.remove.length === 1) { 72 | console.log('Removing link from gateway'); 73 | gw.removeLink(program.remove[0], function () { 74 | return gw.close(); 75 | }); 76 | } 77 | } else if (program.cancel) { 78 | console.log('Canceling linking mode'); 79 | gw.cancelLinking(function () { 80 | gw.close(); 81 | }); 82 | } else if (program.unlink) { 83 | if (devices.length === 0) { 84 | devices = null; 85 | } 86 | gw.unlink(devices, program, function (err, links) { 87 | if (err) { 88 | console.log('linking failed: ' + err); 89 | return gw.close(); 90 | } 91 | console.log('unlinking done: \n\n' + util.inspect(links)); 92 | return gw.close(); 93 | }); 94 | } else if (program.link) { 95 | if (devices.length === 0) { 96 | devices = null; 97 | } 98 | gw.link(devices, program, function (err, links) { 99 | if (err) { 100 | console.log('linking failed: ' + err); 101 | return gw.close(); 102 | } 103 | console.log('links created: \n\n' + util.inspect(links)); 104 | return gw.close(); 105 | }); 106 | } else { 107 | var id = !!devices[0] ? devices[0] : null; 108 | console.log('Polling device: ' + id); 109 | gw.info(id, function onInfo(err, info) { 110 | if (err) { 111 | return console.log('Failed to connect to device, ' + err); 112 | } 113 | if(!info) { 114 | console.log('No links were found.'); 115 | return gw.close(); 116 | } 117 | 118 | console.log('Device found: ' + info.id + ' - ' + info.deviceCategory.name); 119 | console.log('Getting links for device: ' + info.id); 120 | 121 | gw.links(id, function onLinksFound(err, links) { 122 | if (err) { 123 | return console.log('Failed to find any links because of an error, ' + err); 124 | } 125 | 126 | if (links && links.length > 0) { 127 | console.log('Found ' + links.length + ' link(s): \n'); 128 | for (var i = 0; i < links.length; i++) { 129 | console.log(JSON.stringify(links[i])); 130 | } 131 | } else { 132 | console.log('No links found.'); 133 | } 134 | return gw.close(); 135 | }); 136 | }); 137 | } 138 | }); 139 | 140 | gw.on('close', function () { 141 | console.log('Connection closed'); 142 | }); 143 | 144 | if (host.substring(0, 5) === '/dev/' || host.substring(0, 3) === 'COM') { 145 | gw.serial(host); 146 | } else { 147 | gw.connect(host, port); 148 | } 149 | -------------------------------------------------------------------------------- /bin/insteon-map: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var Insteon = require('../').Insteon; 6 | var program = require('commander'); 7 | var pkg = require('../package.json'); 8 | var version = pkg.version; 9 | 10 | program 11 | .version(version) 12 | .usage('[options] ') 13 | .option('-v, --verbose', 'more output') 14 | .option('-a, --all', 'include all links') 15 | .parse(process.argv); 16 | 17 | if(program.args.length !== 1) { 18 | program.help(); 19 | } 20 | 21 | var host, port; 22 | 23 | var uri = program.args.shift(); 24 | var uriParts = uri.split(':'); 25 | 26 | host = uriParts[0]; 27 | if(uriParts.length > 1) { 28 | port = parseInt(uriParts[1]); 29 | } 30 | 31 | console.log('Connecting to ' + uri); 32 | var gw = new Insteon(); 33 | 34 | var network = {}; 35 | var to_scan = []; 36 | 37 | gw.on('connect', function () { 38 | console.log('Connected'); 39 | 40 | gw.info(function onInfo(err, info) { 41 | if(err) { 42 | return console.log('Failed to connect to gateway, ' + err); 43 | } 44 | 45 | network[info.id] = info; 46 | console.log('Found gateway: ' + info.id + ' - ' + info.deviceCategory.name); 47 | 48 | console.log('Getting links for gateway ' + info.id); 49 | gw.links(function onLinksFound(err, links){ 50 | if(err) { 51 | return console.log('Failed to find any links because of an error, ' + err); 52 | } 53 | 54 | if(links && links.length > 0){ 55 | console.log('Found links: ' + links.length); 56 | network[info.id].links = links; 57 | 58 | for(var link in links) { 59 | if(!!links[link].isInUse && to_scan.indexOf(links[link].id) === -1 && network[links[link].id] === undefined) { 60 | to_scan.push(links[link].id); 61 | } 62 | } 63 | 64 | scanLinks(); 65 | } else { 66 | console.log('Gateway returned no links.'); 67 | } 68 | }); 69 | }); 70 | }); 71 | 72 | gw.on('close', function() { 73 | console.log('Connection closed'); 74 | }); 75 | 76 | gw.on('error', function(err) { 77 | console.log(err.stack); 78 | }); 79 | 80 | if(host.substring(0, 5) === '/dev/' || host.substring(0, 3) === 'COM') { 81 | gw.serial(host); 82 | } else { 83 | gw.connect(host, port); 84 | } 85 | 86 | function scanLinks() { 87 | 88 | if(to_scan.length === 0) { 89 | console.log('Done'); 90 | console.log(); 91 | 92 | printNetwork(); 93 | gw.close(); 94 | } 95 | 96 | var id = to_scan.pop(); 97 | 98 | network[id] = null; 99 | 100 | gw.info(id, function(err, link) { 101 | if(err){ 102 | console.log('Error: ', err); 103 | return scanLinks(); 104 | } 105 | 106 | if(!link){ 107 | console.log('Found new link: ' + id + ' - Unresponsive!'); 108 | return scanLinks(); 109 | } else { 110 | console.log('Found new device: ' + id + ' - ' + link.deviceCategory.name); 111 | network[id] = link; 112 | 113 | gw.links(link.id, function(err, moreLinks) { 114 | if(err) { 115 | console.log('Error getting links for devcie ' + link.id + ' - skipping'); 116 | } 117 | console.log('Found links: ' + moreLinks.length); 118 | network[link.id].links = moreLinks; 119 | 120 | return scanLinks(); 121 | }); 122 | } 123 | }); 124 | } 125 | 126 | function printNetwork() { 127 | console.log('\n\nNetwork Map:'); 128 | for(var id in network) { 129 | var controls = {}; 130 | var type = 'Unresponsive'; 131 | if(network[id]){ 132 | type = network[id].deviceCategory ? network[id].deviceCategory.name : 'Unknown Type'; 133 | for(var i = 0; i 1) { 18 | port = program.args[1]; 19 | } 20 | 21 | var client = new net.Socket(); 22 | client.setEncoding('hex'); 23 | 24 | client.on('connect', function() { 25 | log('Connected to: ' + host + ':' + port); 26 | process.stdin.resume(); 27 | process.stdin.setEncoding('utf8'); 28 | 29 | process.stdin.on('data', function (cmd) { 30 | cmd = cmd.trim(); 31 | if (/^(quit|exit|q)$/i.test(cmd)) { 32 | client.end(); 33 | } else { 34 | log('Send:', cmd); 35 | client.write(cmd, 'hex'); 36 | } 37 | }); 38 | 39 | }); 40 | 41 | client.on('data', function(data) { 42 | log('Rsvd:', data); 43 | }); 44 | 45 | client.on('close', function() { 46 | console.log('\rConnection closed'); 47 | process.exit(); 48 | }); 49 | 50 | client.connect(port, host); 51 | 52 | function log() { 53 | arguments[0] = '\r' + arguments[0]; 54 | console.log.apply(null, arguments); 55 | util.print('insteon> '); 56 | } -------------------------------------------------------------------------------- /bin/insteon-therm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | if(process.argv.length < 4) 5 | { 6 | console.log('Usage: insteon-therm '); 7 | process.exit(); 8 | } 9 | 10 | var Insteon = require('../').Insteon; 11 | var host = process.argv[2]; 12 | 13 | console.log('Connecting to ' + host); 14 | var gw = new Insteon(); 15 | 16 | gw.on('connect', function () { 17 | var id = process.argv[3]; 18 | 19 | console.log('*** STEP 1 ***'); 20 | gw.scene('gw', id, {group: 0x00}) 21 | .then(function() { 22 | console.log('*** STEP 2 ***'); 23 | return gw.scene(id, 'gw', {group: 0x01}); 24 | }) 25 | .then(function() { 26 | console.log('*** STEP 3 ***'); 27 | return gw.scene(id, 'gw', {group: 0x02}); 28 | }) 29 | .then(function() { 30 | console.log('*** STEP 4 ***'); 31 | return gw.scene(id, 'gw', {group: 0x03}); 32 | }) 33 | .then(function() { 34 | console.log('*** STEP 5 ***'); 35 | return gw.scene(id, 'gw', {group: 0x04}); 36 | }) 37 | .then(function() { 38 | console.log('*** STEP 6 ***'); 39 | return gw.scene(id, 'gw', {group: 0x05}); 40 | }) 41 | .then(function() { 42 | console.log('*** STEP 7 ***'); 43 | return gw.scene(id, 'gw', {group: 0xEF}); 44 | }) 45 | .then(function() { 46 | console.log('*** STEP 8 ***'); 47 | var cmdDataSet08 = { 48 | cmd1: '2E', 49 | cmd2: '00', 50 | extended: true, 51 | isStandardResponse: true, 52 | userData: ['00', '08'] 53 | }; 54 | return gw.directCommand(id, cmdDataSet08); 55 | }) 56 | .then(function() { 57 | gw.close(); 58 | }); 59 | }); 60 | 61 | gw.on('close', function() { 62 | console.log('Connection closed'); 63 | }); 64 | 65 | gw.serial(host); 66 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | jshint: { 8 | lib: { 9 | src: ['gruntfile.js', 'index.js', 'lib/**/*.js'], 10 | options: { 11 | jshintrc: '.jshintrc' 12 | } 13 | }, 14 | test: { 15 | src: ['test/**/*.js'], 16 | options: { 17 | jshintrc: 'test/.jshintrc' 18 | } 19 | }, 20 | bin: { 21 | src: ['bin/**/*'], 22 | options: { 23 | jshintrc: '.jshintrc' 24 | } 25 | } 26 | }, 27 | 28 | mochaTest: { 29 | test: { 30 | options: { 31 | reporter: 'spec' 32 | }, 33 | src: ['test/**/*.js'] 34 | }, 35 | }, 36 | 37 | mocha_istanbul: { 38 | coverage: { 39 | src: 'test', 40 | options: { 41 | coverage: true, 42 | mochaOptions: ['--exit'], 43 | root: './lib/Insteon/', 44 | reportFormats: ['html', 'lcovonly'], 45 | check: { 46 | lines: 80, 47 | statements: 80 48 | } 49 | } 50 | }, 51 | }, 52 | }); 53 | 54 | grunt.loadNpmTasks('grunt-contrib-jshint'); 55 | grunt.loadNpmTasks('grunt-mocha-test'); 56 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 57 | 58 | grunt.event.on('coverage', function (lcov, done) { 59 | require('coveralls').handleInput(lcov, function (err) { 60 | if (err) { 61 | return done(err); 62 | } 63 | done(); 64 | }); 65 | }); 66 | 67 | grunt.registerTask('default', ['jshint', 'mocha_istanbul']); 68 | grunt.registerTask('coverage', ['jshint', 'mocha_istanbul']); 69 | grunt.registerTask('justtest', ['jshint', 'mochaTest']); 70 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports = module.exports = require('./lib'); -------------------------------------------------------------------------------- /lib/Insteon/Door.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var debug = require('debug')('home-controller:insteon:door'); 4 | 5 | function Door(id, insteon) { 6 | this.id = id; 7 | this.insteon = insteon; 8 | 9 | this.emitOnAck = true; 10 | } 11 | 12 | util.inherits(Door, events.EventEmitter); 13 | 14 | 15 | Door.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 16 | 17 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 18 | this.emit('command', group, cmd1, cmd2); 19 | 20 | switch (group) { 21 | case 1: 22 | if(cmd1 === '11') { 23 | this.emit('opened'); 24 | } else if(cmd1 === '13') { 25 | this.emit('closed'); 26 | } else { 27 | debug('No event for command - %s, group - %s', cmd1, group); 28 | } 29 | break; 30 | case 2: 31 | this.emit('closed'); 32 | break; 33 | case 4: 34 | this.emit('heartbeat'); 35 | if(cmd1 === '11') { 36 | this.emit('opened'); 37 | } else if(cmd1 === '13') { 38 | this.emit('closed'); 39 | } else { 40 | debug('No event for command - %s, group - %s', cmd1, group); 41 | } 42 | break; 43 | default: 44 | debug('No event for command - %s, group - %s', cmd1, group); 45 | } 46 | }; 47 | 48 | 49 | module.exports = Door; -------------------------------------------------------------------------------- /lib/Insteon/GarageDoor.js: -------------------------------------------------------------------------------- 1 | //var utils = require('./utils'); 2 | var q = require('q'); 3 | var debug = require('debug')('home-controller:insteon:garage'); 4 | var util = require('util'); 5 | var events = require('events'); 6 | 7 | 8 | function GarageDoor(id, insteon) { 9 | this.id = id; 10 | this.insteon = insteon; 11 | this.isLockedOut = false; 12 | this.LOCKOUT_TIME = 15000; // 15 seconds 13 | } 14 | 15 | util.inherits(GarageDoor, events.EventEmitter); 16 | 17 | GarageDoor.prototype.open = function () { 18 | return toggle(this, 'open'); 19 | }; 20 | 21 | GarageDoor.prototype.close = function () { 22 | return toggle(this, 'closed'); 23 | }; 24 | 25 | function toggle(self, state) { 26 | if (self.isLockedOut) { 27 | return q.fcall(function () { 28 | return false; 29 | }); 30 | } 31 | 32 | self.isLockedOut = true; 33 | 34 | return self.status() 35 | .then(function (status) { 36 | if (status !== state) { 37 | self.emit(state === 'open' ? 'opening' : 'closing'); 38 | 39 | setTimeout(function () { 40 | self.isLockedOut = false; 41 | self.emit(state); 42 | }, self.LOCKOUT_TIME); 43 | 44 | return self.insteon.directCommand(self.id, '11', 'ff') 45 | .then(function () { 46 | return true; 47 | }); 48 | } else { 49 | self.isLockedOut = false; 50 | return q.fcall(function () { 51 | return false; 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | GarageDoor.prototype.status = function () { 58 | return this.insteon.directCommand(this.id, '19', '01') 59 | .then(function (status) { 60 | if (!status || !status.response || !status.response.standard) { 61 | debug('No response for garage door status for device %s', this.id); 62 | return null; 63 | } 64 | 65 | if (parseInt(status.response.standard.command2)) { 66 | return 'closed'; 67 | } else { 68 | return 'open'; 69 | } 70 | }); 71 | }; 72 | 73 | GarageDoor.prototype.cancelPending = function () { 74 | this.insteon.cancelPending(this.id); 75 | }; 76 | 77 | GarageDoor.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 78 | 79 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 80 | this.emit('command', group, cmd1, cmd2); 81 | 82 | switch (cmd1) { 83 | case '11': 84 | this.emit('open', group); 85 | break; 86 | case '13': 87 | this.emit('close', group); 88 | break; 89 | default: 90 | debug('No event for command - %s', cmd1); 91 | } 92 | }; 93 | 94 | module.exports = GarageDoor; -------------------------------------------------------------------------------- /lib/Insteon/IO.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | function IO(id, insteon) { 4 | this.id = id; 5 | this.insteon = insteon; 6 | } 7 | 8 | IO.prototype.on = function (port) { 9 | port = utils.toByte(port | 0); 10 | return this.insteon.directCommand(this.id, '45', port); 11 | }; 12 | 13 | IO.prototype.off = function (port) { 14 | port = utils.toByte(port | 0); 15 | return this.insteon.directCommand(this.id, '46', port); 16 | }; 17 | 18 | IO.prototype.set = function (data) { 19 | data = utils.toByte(data | 0); 20 | return this.insteon.directCommand(this.id, '48', data); 21 | }; 22 | 23 | 24 | IO.prototype.cancelPending = function(port) { 25 | if(port !== null && port !== undefined) { 26 | port = utils.toByte(port | 0); 27 | var portMatch = new RegExp('^....' + this.id + '..4[5-6]' + port + '$'); 28 | this.insteon.cancelPending(portMatch); 29 | } else { 30 | this.insteon.cancelPending(this.id); 31 | } 32 | }; 33 | 34 | module.exports = IO; 35 | -------------------------------------------------------------------------------- /lib/Insteon/IOLinc.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'); 2 | var events = require('events'); 3 | var util = require('util'); 4 | var debug = require('debug')('home-controller:insteon:iolinc'); 5 | 6 | function IOLinc(id, insteon) { 7 | this.id = id; 8 | this.insteon = insteon; 9 | 10 | this.emitOnAck = true; 11 | } 12 | 13 | util.inherits(IOLinc, events.EventEmitter); 14 | 15 | IOLinc.prototype.relayOn = function (next) { 16 | var id = this.id; 17 | return this.insteon.directCommand(id, '11', 'FF', next); 18 | }; 19 | 20 | IOLinc.prototype.relayOff = function (next) { 21 | var id = this.id; 22 | return this.insteon.directCommand(id, '13', next); 23 | }; 24 | 25 | 26 | IOLinc.prototype.status = function(next) { 27 | 28 | var insteon = this.insteon; 29 | var id = this.id; 30 | 31 | var deferred = Q.defer(); 32 | var stat = {}; 33 | deferred.resolve( 34 | insteon.directCommand(id, '19', '00') // Relay status 35 | .then(function (status) { 36 | if(!status || !status.response || !status.response.standard) { 37 | debug('No response for IO Linc Relay request for device %s', id); 38 | } else { 39 | var relayStatus = parseInt(status.response.standard.command2, 16); 40 | stat.relay = relayStatus === 0 ? 'off' : 'on'; 41 | } 42 | return insteon.directCommand(id, '19', '01'); // Sensor status 43 | }) 44 | .then(function (status) { 45 | if(!status || !status.response || !status.response.standard) { 46 | debug('No response for IO Linc Sensor request for device %s', id); 47 | } else { 48 | var sensorStatus = parseInt(status.response.standard.command2, 16); 49 | stat.sensor = sensorStatus === 0 ? 'off' : 'on'; 50 | } 51 | return stat; 52 | }) 53 | ); 54 | return deferred.promise.nodeify(next); 55 | }; 56 | 57 | 58 | IOLinc.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 59 | 60 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 61 | this.emit('command', group, cmd1, cmd2); 62 | 63 | switch (cmd1 + group) { 64 | case '110': 65 | this.emit('relayOn', group); 66 | break; 67 | case '111': 68 | this.emit('sensorOn', group); 69 | break; 70 | case '130': 71 | this.emit('relayOff', group); 72 | break; 73 | case '131': 74 | this.emit('sensorOff', group); 75 | break; 76 | default: 77 | debug('No event for command - %s', cmd1); 78 | } 79 | }; 80 | 81 | IOLinc.prototype.handleAck = function (cmd1, cmd2) { 82 | 83 | if(!this.emitOnAck) { 84 | return; 85 | } 86 | 87 | debug('Emitting ACK command for device (%s) - cmd1: %s, cmd2: %s', this.id, cmd1, cmd2); 88 | 89 | this.emit('command', null, cmd1, cmd2); 90 | switch (cmd1) { 91 | case '11': // turnOn 92 | this.emit('relayOn'); 93 | break; 94 | case '13': // turnOff 95 | this.emit('relayOff'); 96 | break; 97 | default: 98 | debug('No event for command - %s', cmd1); 99 | } 100 | 101 | }; 102 | 103 | 104 | 105 | IOLinc.prototype.cancelPending = function() { 106 | this.insteon.cancelPending(this.id); 107 | }; 108 | 109 | 110 | module.exports = IOLinc; -------------------------------------------------------------------------------- /lib/Insteon/Leak.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var events = require('events'); 3 | var debug = require('debug')('home-controller:insteon:leak'); 4 | 5 | function Leak(id, insteon) { 6 | this.id = id; 7 | this.insteon = insteon; 8 | 9 | this.emitOnAck = true; 10 | } 11 | 12 | util.inherits(Leak, events.EventEmitter); 13 | 14 | 15 | Leak.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 16 | 17 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 18 | this.emit('command', group, cmd1, cmd2); 19 | 20 | switch (group) { 21 | case 1: 22 | this.emit('dry'); 23 | break; 24 | case 2: 25 | this.emit('wet'); 26 | break; 27 | case 4: 28 | this.emit('heartbeat'); 29 | if(cmd1 === '11') { 30 | this.emit('dry'); 31 | } else if (cmd1 === '13') { 32 | this.emit('wet'); 33 | } else { 34 | debug('No event for command - %s, group - %s', cmd1, group); 35 | } 36 | break; 37 | default: 38 | debug('No event for command - %s, group - %s', cmd1, group); 39 | } 40 | }; 41 | 42 | 43 | module.exports = Leak; -------------------------------------------------------------------------------- /lib/Insteon/Light.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var Q = require('q'); 3 | var events = require('events'); 4 | var util = require('util'); 5 | var toByte = utils.toByte; 6 | var debug = require('debug')('home-controller:insteon:light'); 7 | 8 | function Light(id, insteon) { 9 | this.id = id; 10 | this.insteon = insteon; 11 | 12 | this.emitOnAck = true; 13 | } 14 | 15 | util.inherits(Light, events.EventEmitter); 16 | 17 | Light.prototype.turnOn = function (level, rate, next) { 18 | var id = this.id; 19 | if (typeof rate === 'function') { 20 | next = rate; 21 | rate = null; 22 | } else if (typeof level === 'function') { 23 | next = level; 24 | level = 100; 25 | rate = null; 26 | } else if (level === undefined){ 27 | level = 100; 28 | rate = null; 29 | } 30 | if (rate !== null && rate !== undefined) { 31 | switch(rate) { 32 | case 'slow': 33 | rate = 47000; 34 | break; 35 | case 'fast': 36 | rate = 100; 37 | break; 38 | } 39 | 40 | var rampAndLevel = utils.levelToHexHalfByte(level) + utils.rampRateToHexHalfByte(rate); 41 | return this.insteon.directCommand(id, '2E', rampAndLevel, next); 42 | } else { 43 | return this.insteon.directCommand(id, '11', utils.levelToHexByte(level), next); 44 | } 45 | }; 46 | 47 | 48 | Light.prototype.fan = function (speed, next) { 49 | var id = this.id; 50 | if (typeof speed === 'function') { 51 | next = speed; 52 | speed = null; 53 | } 54 | 55 | if(speed) { 56 | 57 | switch(speed) { 58 | case 'off': 59 | speed = 0x00; 60 | break; 61 | case 'low': 62 | speed = 0x3F; 63 | break; 64 | case 'high': 65 | speed = 0xFF; 66 | break; 67 | default: // med 68 | speed = 0xBF; 69 | } 70 | 71 | var cmd = { 72 | cmd1: '11', 73 | cmd2: toByte(speed), 74 | userData: ['02'], 75 | extended: true, 76 | isStandardResponse: true 77 | }; 78 | return this.insteon.directCommand(id, cmd, next); 79 | 80 | } else { 81 | 82 | var deferred = Q.defer(); 83 | deferred.resolve( 84 | this.insteon.directCommand(id, '19', '03') 85 | .then(function (status) { 86 | if(!status || !status.response || !status.response.standard) { 87 | debug('No response for fan request for device %s', id); 88 | return null; 89 | } 90 | 91 | var speed = parseInt(status.response.standard.command2, 16); 92 | if (speed === 0) { 93 | return 'off'; 94 | } else if (speed === 0xFF) { 95 | return 'high'; 96 | } else if (speed <= 0x7F) { 97 | return 'low'; 98 | } else { 99 | return 'medium'; 100 | } 101 | }) 102 | ); 103 | return deferred.promise.nodeify(next); 104 | 105 | } 106 | }; 107 | 108 | Light.prototype.fanOn = function (next) { 109 | return this.fan('medium', next); 110 | }; 111 | 112 | Light.prototype.fanOff = function (next) { 113 | return this.fan('off', next); 114 | }; 115 | 116 | Light.prototype.fanLow = function (next) { 117 | return this.fan('low', next); 118 | }; 119 | 120 | Light.prototype.fanMedium = function (next) { 121 | return this.fan('medium', next); 122 | }; 123 | 124 | Light.prototype.fanHigh = function (next) { 125 | return this.fan('high', next); 126 | }; 127 | 128 | 129 | /** 130 | * Turn Light On fast (no ramp) to 100% 131 | * 132 | * 12 -- ON FAST command 133 | * 134 | * @param {String} id 135 | * @param {Function} next 136 | */ 137 | Light.prototype.turnOnFast = function (next) { 138 | return this.insteon.directCommand(this.id, '12', next); 139 | }; 140 | 141 | /** 142 | * Turn Light Off 143 | * 144 | * 13 -- OFF command 145 | * 146 | * @param {String} id 147 | * @param {Function} next 148 | */ 149 | Light.prototype.turnOff = function (rate, next) { 150 | var id = this.id; 151 | if (typeof rate === 'function') { 152 | next = rate; 153 | rate = null; 154 | } 155 | if (rate !== null && rate !== undefined) { 156 | switch(rate) { 157 | case 'slow': 158 | rate = 47000; // 47 sec in msec 159 | break; 160 | case 'fast': 161 | rate = 100; // .1 sec in msec 162 | break; 163 | } 164 | 165 | var rampAndLevel = '0' + utils.rampRateToHexHalfByte(rate); 166 | return this.insteon.directCommand(id, '2F', rampAndLevel, next); 167 | } else { 168 | return this.insteon.directCommand(id, '13', next); 169 | } 170 | }; 171 | 172 | 173 | /** 174 | * Turn Light Off fast (no ramp) 175 | * 176 | * 13 -- OFF command 177 | * 178 | * @param {String} id 179 | * @param {Function} next 180 | */ 181 | Light.prototype.turnOffFast = function (next) { 182 | return this.insteon.directCommand(this.id, '14', next); 183 | }; 184 | 185 | 186 | /** 187 | * Brighten Light 188 | * 189 | * 15 -- Brighten command 190 | * 191 | * @param {String} id 192 | * @param {Function} next 193 | */ 194 | Light.prototype.brighten = function (next) { 195 | return this.insteon.directCommand(this.id, '15', next); 196 | }; 197 | 198 | /** 199 | * Dim Light 200 | * 201 | * 16 -- Dim command 202 | * 203 | * @param {String} id 204 | * @param {Function} next 205 | */ 206 | Light.prototype.dim = function (next) { 207 | return this.insteon.directCommand(this.id, '16', next); 208 | }; 209 | 210 | 211 | /** 212 | * Light Instant Change 213 | * Check for light level. 214 | * 215 | * 19 -- STATUS command 216 | * 217 | * 21 -- Instant Change command 218 | * 219 | * @param {String} id 220 | * @param {Number} level 221 | * @param {Function} next 222 | */ 223 | Light.prototype.level = function (level, next) { 224 | if (typeof level === 'function') { 225 | next = level; 226 | level = null; 227 | } 228 | 229 | var id = this.id; 230 | 231 | if (level !== null && level !== undefined){ 232 | return this.insteon.directCommand(id, '21', utils.levelToHexByte(level), next); 233 | } else { 234 | var deferred = Q.defer(); 235 | deferred.resolve( 236 | this.insteon.directCommand(id, '19') 237 | .then(function (status) { 238 | 239 | if(!status || !status.response || !status.response.standard) { 240 | debug('No response for level request for device %s', id); 241 | return null; 242 | } 243 | 244 | var level = Math.ceil(parseInt(status.response.standard.command2, 16) * 100 / 255); 245 | return level; 246 | }) 247 | ); 248 | return deferred.promise.nodeify(next); 249 | } 250 | }; 251 | 252 | Light.prototype.info = function (btn, next) { 253 | if (typeof btn === 'function') { 254 | next = btn; 255 | btn = 1; 256 | } 257 | if(btn === null || btn === undefined) { 258 | btn = 1; 259 | } 260 | var id = this.id; 261 | 262 | var cmd = {cmd1: '2E', cmd2: '00', extended: true}; 263 | 264 | btn = toByte(btn); 265 | 266 | cmd.userData = [btn]; 267 | 268 | var deferred = Q.defer(); 269 | deferred.resolve( 270 | this.insteon.directCommand(id, cmd) 271 | .then(function (status) { 272 | if(!status || !status.response || !status.response.extended) { 273 | debug('No response for info request for device %s', id); 274 | return null; 275 | } 276 | var rampRate = utils.byteToRampRate(status.response.extended.userData[6]); 277 | var onLevel = utils.byteToLevel(status.response.extended.userData[7]); 278 | var ledBrightness = parseInt(status.response.extended.userData[8], 16); 279 | 280 | return { 281 | rampRate: rampRate, 282 | onLevel: onLevel, 283 | ledBrightness: ledBrightness 284 | }; 285 | }) 286 | ); 287 | return deferred.promise.nodeify(next); 288 | }; 289 | 290 | Light.prototype.rampRate = function (btn, rate, next) { 291 | if (typeof btn === 'function') { 292 | next = btn; 293 | btn = 1; 294 | rate = null; 295 | } else if (typeof rate === 'function') { 296 | next = rate; 297 | rate = btn; 298 | btn = 1; 299 | } 300 | if(!btn) { 301 | btn = 1; 302 | } 303 | 304 | if (rate) { 305 | var id = this.id; 306 | 307 | var cmd = {cmd1: '2E', cmd2: '00', extended: true}; 308 | 309 | btn = toByte(btn); 310 | cmd.userData = [btn, '05', utils.rampRateToHexByte(rate)]; 311 | 312 | return this.insteon.directCommand(id, cmd).then(function(status) { 313 | if(!status || !status.response || !status.response.extended) { 314 | debug('No response after ramp rate set request for device %s', id); 315 | return null; 316 | } 317 | return utils.byteToRampRate(status.response.extended.userData[6]); 318 | }).nodeify(next); 319 | } 320 | 321 | var deferred = Q.defer(); 322 | deferred.resolve( 323 | this.info(btn) 324 | .then(function (info) { 325 | return (info || {}).rampRate; 326 | }) 327 | ); 328 | return deferred.promise.nodeify(next); 329 | }; 330 | 331 | Light.prototype.onLevel = function (btn, level, next) { 332 | if (typeof btn === 'function') { 333 | next = btn; 334 | btn = 1; 335 | level = null; 336 | } else if (typeof level === 'function') { 337 | next = level; 338 | level = btn; 339 | btn = 1; 340 | } 341 | if(!btn) { 342 | btn = 1; 343 | } 344 | 345 | if (level) { 346 | var id = this.id; 347 | 348 | var cmd = {cmd1: '2E', cmd2: '00', extended: true}; 349 | 350 | btn =toByte(btn); 351 | 352 | cmd.userData = [btn, '06', utils.levelToHexByte(level)]; 353 | 354 | return this.insteon.directCommand(id, cmd).then(function (status) { 355 | if (!status || !status.response || !status.response.extended) { 356 | debug('No response for onLevel request for device %s', id); 357 | return null; 358 | } 359 | return utils.byteToLevel(status.response.extended.userData[7]); 360 | }).nodeify(next); 361 | } 362 | 363 | var deferred = Q.defer(); 364 | deferred.resolve( 365 | this.info(btn) 366 | .then(function (info) { 367 | return (info || {}).onLevel; 368 | }) 369 | ); 370 | return deferred.promise.nodeify(next); 371 | 372 | }; 373 | 374 | 375 | Light.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 376 | 377 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 378 | this.emit('command', group, cmd1, cmd2); 379 | 380 | switch (cmd1) { 381 | case '04': 382 | var level = Math.round((parseInt(cmd2, 16) / 0xff) * 100); 383 | this.emit('heartbeat', level); 384 | break; 385 | case '11': 386 | this.emit('turnOn', group); 387 | break; 388 | case '12': 389 | this.emit('turnOnFast', group); 390 | break; 391 | case '13': 392 | this.emit('turnOff', group); 393 | break; 394 | case '14': 395 | this.emit('turnOffFast', group); 396 | break; 397 | case '17': 398 | if(cmd2 === '00') { 399 | this.dimming = true; 400 | this.emit('dimming', group); 401 | } else { 402 | this.dimming = false; 403 | this.emit('brightening', group); 404 | } 405 | break; 406 | case '18': 407 | if(this.dimming) { 408 | this.emit('dimmed', group); 409 | } else { 410 | this.emit('brightened', group); 411 | } 412 | break; 413 | default: 414 | debug('No event for command - %s', cmd1); 415 | } 416 | }; 417 | 418 | Light.prototype.handleAck = function (cmd1, cmd2) { 419 | 420 | if(!!this.insteon && !!this.insteon.status && !!this.insteon.status.command) { 421 | var command = this.insteon.status.command; 422 | if (command.cmd1 === '19') { 423 | // ack for light status/level 424 | return; 425 | } 426 | if(command.extended && 427 | command.cmd1 === '2E' && 428 | command.id.toUpperCase() === this.id) { 429 | // ack for light.info 430 | return; 431 | } 432 | if(command.extended && 433 | command.cmd1 === '2F' && 434 | command.id.toUpperCase() === this.id) { 435 | // ack for insteon.linkAt 436 | return; 437 | } 438 | } 439 | 440 | 441 | if(!this.emitOnAck) { 442 | return; 443 | } 444 | 445 | debug('Emitting ACK command for device (%s) - cmd1: %s, cmd2: %s', this.id, cmd1, cmd2); 446 | var group = null; // act doesn't have group 447 | 448 | this.emit('command', group, cmd1, cmd2); 449 | switch (cmd1) { 450 | case '11': // turnOn 451 | case '21': // level 452 | this.emit('turnOn', group, Math.ceil(parseInt(cmd2, 16) * 100 / 255)); 453 | break; 454 | case '12': // turnOnFast 455 | this.emit('turnOnFast', group); 456 | break; 457 | case '2e': // turnOn at rampandlevel 458 | this.emit('turnOn', group, Math.ceil(parseInt(cmd2.substring(0,1), 16) * 100 / 15)); 459 | break; 460 | case '13': // turnOff 461 | case '14': // turnOffFast 462 | case '2f': // turnOff at ramp 463 | this.emit('turnOff', group); 464 | break; 465 | case '15': // brighten 466 | this.emit('brightened', group); 467 | break; 468 | case '16': // dim 469 | this.emit('dimmed', group); 470 | break; 471 | default: 472 | debug('No event for command - %s', cmd1); 473 | } 474 | 475 | }; 476 | 477 | 478 | Light.prototype.cancelPending = function() { 479 | this.insteon.cancelPending(this.id); 480 | }; 481 | 482 | 483 | module.exports = Light; 484 | -------------------------------------------------------------------------------- /lib/Insteon/Meter.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Q = require('q'); 3 | var events = require('events'); 4 | // var debug = require('debug')('home-controller:insteon:meter'); 5 | 6 | function Meter(id, insteon) { 7 | this.id = id; 8 | this.insteon = insteon; 9 | } 10 | 11 | util.inherits(Meter, events.EventEmitter); 12 | 13 | Meter.prototype.statusAndReset = function (next) { 14 | 15 | var meter = this; 16 | 17 | var deferred = Q.defer(); 18 | 19 | deferred.resolve( 20 | meter.status() 21 | .then(function (details) { 22 | return meter.reset() 23 | .then(function () { 24 | return details; 25 | }); 26 | }) 27 | ); 28 | 29 | return deferred.promise.nodeify(next); 30 | 31 | }; 32 | 33 | Meter.prototype.reset = function (next) { 34 | var cmd = { 35 | cmd1: '80', 36 | cmd2: '00' 37 | }; 38 | 39 | return this.insteon.directCommand(this.id, cmd, next); 40 | 41 | }; 42 | 43 | Meter.prototype.status = function (next) { 44 | 45 | var cmd = { 46 | cmd1: '82', 47 | cmd2: '00', 48 | waitForExtended: true 49 | }; 50 | 51 | var insteon = this.insteon; 52 | var id = this.id; 53 | 54 | 55 | var details = {}; 56 | var data; 57 | 58 | var deferred = Q.defer(); 59 | 60 | deferred.resolve( 61 | insteon.directCommand(id, cmd) 62 | .then(function (status) { 63 | data = status.response.extended.userData; 64 | 65 | if(parseInt(data[8], 16) >= 254){ 66 | details.energy = 0; 67 | } else { 68 | details.energy = parseInt(data[8] + data[9] + data[10] + data[11], 16); 69 | } 70 | 71 | // Convert Accumulated Energy value into kW-h 72 | details.energy = details.energy* 65535 / (1000 * 60 * 60 * 60); 73 | 74 | // Watts 75 | details.power = parseInt(data[6] + data[7], 16); 76 | 77 | if(details.power > 32767){ 78 | details.power = details.power - 65535; 79 | } 80 | return details; 81 | }) 82 | 83 | ); 84 | 85 | return deferred.promise.nodeify(next); 86 | 87 | }; 88 | 89 | Meter.prototype.cancelPending = function() { 90 | this.insteon.cancelPending(this.id); 91 | }; 92 | 93 | module.exports = Meter; 94 | -------------------------------------------------------------------------------- /lib/Insteon/Motion.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var events = require('events'); 3 | var Q = require('q'); 4 | var debug = require('debug')('home-controller:insteon:motion'); 5 | 6 | var utils = require('./utils'); 7 | var toByte = utils.toByte; 8 | var assignDefaults = utils.assignDefaults; 9 | 10 | function Motion(id, insteon) { 11 | this.id = id; 12 | this.insteon = insteon; 13 | 14 | this.emitOnAck = true; 15 | } 16 | 17 | util.inherits(Motion, events.EventEmitter); 18 | 19 | Motion.prototype.status = function(wait, next) { 20 | if(typeof wait === 'function') { 21 | next = wait; 22 | wait = true; 23 | } 24 | 25 | if(wait === null || wait === undefined) { 26 | wait = true; 27 | } 28 | 29 | var cmd = { 30 | cmd1: '2E', 31 | cmd2: '00', 32 | extended: true 33 | }; 34 | 35 | var insteon = this.insteon; 36 | var id = this.id; 37 | 38 | 39 | var details = {}; 40 | var data; 41 | 42 | var deferred = Q.defer(); 43 | 44 | function status() { 45 | deferred.resolve( 46 | insteon.directCommand(id, cmd) 47 | .then(function (status) { 48 | data = status.response.extended.userData; 49 | 50 | details.ledLevel = parseInt(data[2], 16); 51 | details.clearTimer = (parseInt(data[3], 16) + 1) * 30; // secondes 52 | details.duskThreshold = Math.floor(parseInt(data[4], 16) * 100 / 255); 53 | 54 | var optionFlags = parseInt(data[5], 16); 55 | details.options = { 56 | occupancyMode: !!(optionFlags & 0x10), 57 | ledOn: !!(optionFlags & 0x08), 58 | nightMode: !(optionFlags & 0x04), 59 | onOnlyMode: !(optionFlags & 0x02) 60 | }; 61 | 62 | var jumperFlags = parseInt(data[8], 16); 63 | details.jumpers = { 64 | j2: !(jumperFlags & 0x08), 65 | j3: !(jumperFlags & 0x04), 66 | j4: !(jumperFlags & 0x02), 67 | j5: !(jumperFlags & 0x01) 68 | }; 69 | details.lightLevel = Math.floor(parseInt(data[10], 16) * 100 / 255); 70 | details.batteryLevel = parseInt(data[11], 16) / 10; 71 | 72 | return details; 73 | }) 74 | ); 75 | } 76 | 77 | if(wait) { 78 | this.once('command', status); 79 | } else { 80 | status(); 81 | } 82 | 83 | return deferred.promise.nodeify(next); 84 | }; 85 | 86 | Motion.prototype.setWaitCmd = function (wait, userData, next) { 87 | 88 | var insteon = this.insteon; 89 | 90 | var deferred = Q.defer(); 91 | 92 | var id = this.id; 93 | 94 | 95 | var cmd = { 96 | cmd1: '2E', 97 | cmd2: '00', 98 | extended: true, 99 | isStandardResponse: true, 100 | userData: userData 101 | }; 102 | 103 | 104 | function setOptions() { 105 | deferred.resolve(insteon.directCommand(id, cmd)); 106 | } 107 | 108 | if(wait) { 109 | this.once('command', setOptions); 110 | } else { 111 | setOptions(); 112 | } 113 | 114 | return deferred.promise.nodeify(next); 115 | }; 116 | 117 | Motion.prototype.options = function (options, wait, next) { 118 | if(typeof options !== 'object') { 119 | next = wait; 120 | wait = options; 121 | options = null; 122 | } 123 | 124 | if(typeof wait === 'function') { 125 | next = wait; 126 | wait = true; 127 | } 128 | 129 | if(wait === null || wait === undefined) { 130 | wait = true; 131 | } 132 | 133 | var defaults = { 134 | occupancyMode: false, 135 | ledOn: true, 136 | nightMode: false, 137 | onOnlyMode: false 138 | }; 139 | 140 | options = assignDefaults(defaults, options); 141 | 142 | var optionBytes = toByte( 143 | (options.occupancyMode ? 0x10 : 0) | 144 | (options.ledOn ? 0x08 : 0) | 145 | (options.nightMode ? 0 : 0x04) | 146 | (options.onOnlyMode ? 0 : 0x02) 147 | ); 148 | 149 | 150 | 151 | var userData = ['00', '05', optionBytes]; 152 | 153 | return this.setWaitCmd(wait, userData, next); 154 | 155 | }; 156 | 157 | 158 | 159 | Motion.prototype.clearTimer = function(timeout, wait, next) { 160 | if(typeof timeout !== 'number') { 161 | next = wait; 162 | wait = timeout; 163 | timeout = 30; // default timeout 164 | } 165 | 166 | if(typeof wait === 'function') { 167 | next = wait; 168 | wait = true; 169 | } 170 | 171 | if(wait === null || wait === undefined) { 172 | wait = true; 173 | } 174 | 175 | timeout = Math.ceil((timeout / 30)) - 1; 176 | timeout = timeout < 0 ? 0 : timeout; 177 | 178 | timeout = toByte(timeout); 179 | 180 | var userData = ['00', '03', timeout]; 181 | 182 | return this.setWaitCmd(wait, userData, next); 183 | }; 184 | 185 | Motion.prototype.duskThreshold = function(threshold, wait, next) { 186 | if(typeof threshold !== 'number' && typeof threshold !== 'string') { 187 | next = wait; 188 | wait = threshold; 189 | threshold = 50; // default threshold 190 | } 191 | 192 | if(typeof wait === 'function') { 193 | next = wait; 194 | wait = true; 195 | } 196 | 197 | if(wait === null || wait === undefined) { 198 | wait = true; 199 | } 200 | 201 | if(threshold === 'alwaysDay') { 202 | threshold = 0; 203 | } 204 | 205 | if(threshold === 'alwaysNight') { 206 | threshold = 100; 207 | } 208 | 209 | if(typeof threshold === 'string') { 210 | threshold = 50; // default threshold 211 | } 212 | 213 | threshold = Math.ceil((threshold / 100 * 255)); 214 | threshold = threshold < 0 ? 0 : threshold; 215 | threshold = threshold > 255 ? 255 : threshold; 216 | 217 | threshold = toByte(threshold); 218 | 219 | var userData = ['00', '04', threshold]; 220 | 221 | return this.setWaitCmd(wait, userData, next); 222 | }; 223 | 224 | 225 | Motion.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { 226 | 227 | debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); 228 | this.emit('command', group, cmd1, cmd2); 229 | 230 | switch (group) { 231 | case 1: 232 | if(cmd1 === '11') { 233 | this.emit('motion'); 234 | } else if(cmd1 === '13') { 235 | this.emit('clear'); 236 | } 237 | break; 238 | case 2: 239 | if(cmd1 === '11') { 240 | this.emit('dusk'); 241 | } else if(cmd1 === '13') { 242 | this.emit('dawn'); 243 | } 244 | break; 245 | case 3: 246 | this.emit('battery'); 247 | break; 248 | default: 249 | debug('No event for command - %s, %s', cmd1, group); 250 | } 251 | }; 252 | 253 | 254 | Motion.prototype.cancelPending = function() { 255 | this.insteon.cancelPending(this.id); 256 | }; 257 | 258 | 259 | module.exports = Motion; -------------------------------------------------------------------------------- /lib/Insteon/Thermostat.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var toByte = utils.toByte; 3 | var convertTemp = utils.convertTemp; 4 | var util = require('util'); 5 | var Q = require('q'); 6 | var events = require('events'); 7 | var debug = require('debug')('home-controller:insteon:thermostat'); 8 | 9 | function Thermostat(id, insteon) { 10 | this.id = id; 11 | this.insteon = insteon; 12 | } 13 | 14 | util.inherits(Thermostat, events.EventEmitter); 15 | 16 | Thermostat.prototype.tempUp = function (change, next) { 17 | if(typeof change === 'function') { 18 | next = change; 19 | change = 1; 20 | } 21 | 22 | if(change === null || change === undefined) { 23 | change = 1; 24 | } 25 | 26 | change = toByte(change * 2); 27 | 28 | var cmd = { 29 | cmd1: '68', 30 | cmd2: change, 31 | extended: true, 32 | isStandardResponse: true 33 | }; 34 | 35 | return this.insteon.directCommand(this.id, cmd, next); 36 | }; 37 | 38 | Thermostat.prototype.tempDown = function (change, next) { 39 | if(typeof change === 'function') { 40 | next = change; 41 | change = 1; 42 | } 43 | 44 | if(change === null || change === undefined) { 45 | change = 1; 46 | } 47 | 48 | change = toByte(change * 2); 49 | 50 | var cmd = { 51 | cmd1: '69', 52 | cmd2: change, 53 | extended: true, 54 | isStandardResponse: true 55 | }; 56 | 57 | return this.insteon.directCommand(this.id, cmd, next); 58 | 59 | }; 60 | 61 | 62 | Thermostat.prototype.info = function (type, divisor, zone, next) { 63 | if(typeof zone === 'function') { 64 | next = zone; 65 | zone = 0; 66 | } 67 | zone = zone || 0; 68 | 69 | var cmd2 = toByte(type << 5 | zone); 70 | 71 | var deferred = Q.defer(); 72 | 73 | deferred.resolve( 74 | this.insteon.directCommand(this.id, '6A', cmd2) 75 | .then(function (status) { 76 | return parseInt(status.response.standard.command2, 16)/divisor; 77 | }) 78 | ); 79 | 80 | return deferred.promise.nodeify(next); 81 | 82 | }; 83 | 84 | Thermostat.prototype.temp = function (zone, next) { 85 | return this.info(0, 2, zone, next); 86 | }; 87 | 88 | Thermostat.prototype.setpoints = function (zone, next) { 89 | if(typeof zone === 'function') { 90 | next = zone; 91 | zone = 0; 92 | } 93 | if(!zone) { 94 | zone = 0; 95 | } 96 | 97 | var cmd2 = toByte(1 << 5 | zone); 98 | 99 | var deferred = Q.defer(); 100 | 101 | deferred.resolve( 102 | this.insteon.directCommand(this.id, {cmd1: '6A', cmd2: cmd2, responseCount: 2}) 103 | .then(function (status) { 104 | return status.response.standard.map(function (resp) { 105 | return parseInt(resp.command2, 16) / 2; 106 | }); 107 | }) 108 | ); 109 | return deferred.promise.nodeify(next); 110 | }; 111 | 112 | Thermostat.prototype.humidity = function (zone, next) { 113 | return this.info(3, 1, zone, next); 114 | }; 115 | 116 | 117 | var MODES = [ 118 | 'off', 119 | 'heat', 120 | 'cool', 121 | 'auto', 122 | 'fan', 123 | 'program', 124 | 'program heat', 125 | 'program cool', 126 | 'fan auto' 127 | ]; 128 | 129 | var MODE_CMDS = { 130 | off: '09', 131 | heat: '04', 132 | cool: '05', 133 | auto: '06', 134 | fan: '07', 135 | 'fan auto': '08', 136 | 'program heat': '0A', 137 | 'program cool': '0B', 138 | 'program auto': '0C' 139 | }; 140 | 141 | Thermostat.prototype.mode = function (mode, next) { 142 | if(typeof mode === 'function') { 143 | next = mode; 144 | mode = null; 145 | } 146 | 147 | if(mode) { 148 | var modeCmd = MODE_CMDS[mode.toLowerCase()]; 149 | if(!modeCmd) { 150 | return Q.fcall(function() { 151 | throw new Error('Invalid mode. Must be one of the following: ' + MODES.join(', ')); 152 | }).nodeify(next); 153 | } 154 | var cmd = { 155 | cmd1: '6B', 156 | cmd2: modeCmd, 157 | extended: true, 158 | exitOnAck: true 159 | }; 160 | 161 | return this.insteon.directCommand(this.id, cmd) 162 | .then(function(status) { 163 | if(!!status && status.success) { 164 | return mode; 165 | } else { 166 | return null; 167 | } 168 | }) 169 | .nodeify(next); 170 | } 171 | 172 | var deferred = Q.defer(); 173 | 174 | deferred.resolve( 175 | this.insteon.directCommand(this.id, '6B', '02') 176 | .then( function (status) { 177 | var mode = parseInt(status.response.standard.command2, 16); 178 | return MODES[mode]; 179 | }) 180 | ); 181 | 182 | return deferred.promise.nodeify(next); 183 | }; 184 | 185 | 186 | 187 | Thermostat.prototype.coolTemp = function (temp, next) { 188 | 189 | temp = toByte(temp * 2); 190 | 191 | var cmd = { 192 | cmd1: '6C', 193 | cmd2: temp, 194 | extended: true, 195 | isStandardResponse: true 196 | }; 197 | 198 | 199 | return this.insteon.directCommand(this.id, cmd, next); 200 | 201 | }; 202 | 203 | Thermostat.prototype.heatTemp = function (temp, next) { 204 | 205 | temp = toByte(temp * 2); 206 | 207 | var cmd = { 208 | cmd1: '6D', 209 | cmd2: temp, 210 | extended: true, 211 | isStandardResponse: true 212 | }; 213 | 214 | return this.insteon.directCommand(this.id, cmd, next); 215 | 216 | }; 217 | 218 | Thermostat.prototype.highHumidity = function (humidity, next) { 219 | var cmd = { 220 | cmd1: '2E', 221 | cmd2: '00', 222 | extended: true, 223 | exitOnAck: true, 224 | userData: ['01','0B', toByte(humidity)] 225 | }; 226 | 227 | 228 | return this.insteon.directCommand(this.id, cmd, next); 229 | }; 230 | 231 | Thermostat.prototype.lowHumidity = function (humidity, next) { 232 | var cmd = { 233 | cmd1: '2E', 234 | cmd2: '00', 235 | extended: true, 236 | exitOnAck: true, 237 | userData: ['01','0C', toByte(humidity)] 238 | }; 239 | 240 | 241 | return this.insteon.directCommand(this.id, cmd, next); 242 | 243 | }; 244 | 245 | Thermostat.prototype.backlight = function (delay, next) { 246 | var cmd = { 247 | cmd1: '2E', 248 | cmd2: '00', 249 | extended: true, 250 | exitOnAck: true, 251 | userData: ['01','05', toByte(delay)] 252 | }; 253 | 254 | 255 | return this.insteon.directCommand(this.id, cmd, next); 256 | 257 | }; 258 | 259 | Thermostat.prototype.cycleDelay = function (delay, next) { 260 | var cmd = { 261 | cmd1: '2E', 262 | cmd2: '00', 263 | extended: true, 264 | exitOnAck: true, 265 | userData: ['01','06', toByte(delay)] 266 | }; 267 | 268 | 269 | return this.insteon.directCommand(this.id, cmd, next); 270 | 271 | }; 272 | 273 | Thermostat.prototype.energyChange = function (change, next) { 274 | var cmd = { 275 | cmd1: '2E', 276 | cmd2: '00', 277 | extended: true, 278 | exitOnAck: true, 279 | userData: ['01','07', toByte(change)] 280 | }; 281 | 282 | 283 | return this.insteon.directCommand(this.id, cmd, next); 284 | 285 | }; 286 | 287 | 288 | Thermostat.prototype.date = function (date, next) { 289 | 290 | if(typeof date === 'function') { 291 | next = date; 292 | date = new Date(); 293 | } 294 | 295 | if(!util.isDate(date)) { 296 | date = new Date(date); 297 | } 298 | 299 | var cmd = { 300 | cmd1: '2E', 301 | cmd2: '02', 302 | extended: true, 303 | exitOnAck: true, 304 | crc: true, 305 | userData: [ 306 | '02', 307 | toByte(date.getDay()), 308 | toByte(date.getHours()), 309 | toByte(date.getMinutes()), 310 | toByte(date.getSeconds()) 311 | ] 312 | }; 313 | 314 | 315 | return this.insteon.directCommand(this.id, cmd, next); 316 | 317 | }; 318 | 319 | 320 | Thermostat.prototype.details = function (next) { 321 | 322 | var cmdDataSet00 = { 323 | cmd1: '2E', 324 | cmd2: '00', 325 | extended: true, 326 | crc: true 327 | }; 328 | 329 | var cmdDataSet01 = { 330 | cmd1: '2E', 331 | cmd2: '00', 332 | extended: true, 333 | crc: true, 334 | userData: ['00', '00', '01'] 335 | }; 336 | 337 | var insteon = this.insteon; 338 | var id = this.id; 339 | 340 | 341 | var details = {}; 342 | var data; 343 | 344 | var deferred = Q.defer(); 345 | 346 | deferred.resolve( 347 | this.status() 348 | .then(function (status) { 349 | if(!!status) { 350 | details = status; 351 | } else { 352 | debug('No response for status command.'); 353 | return; 354 | } 355 | }). 356 | then(function() { 357 | return insteon.directCommand(id, cmdDataSet00); 358 | }) 359 | .then(function (status) { 360 | if(!status || !status.response || !status.response.extended) { 361 | debug('No response for status command set #1'); 362 | return; 363 | } 364 | 365 | data = status.response.extended.userData; 366 | details.backlight = parseInt(data[9], 16); 367 | details.delay = parseInt(data[10], 16); 368 | details.energyOffset = parseInt(data[11], 16); 369 | }) 370 | .then( function () { 371 | return insteon.directCommand(id, cmdDataSet01); 372 | }) 373 | .then(function (status) { 374 | if(!status || !status.response || !status.response.extended) { 375 | debug('No response for status command set #2'); 376 | return details; 377 | } 378 | 379 | data = status.response.extended.userData; 380 | details.setpoints = details.setpoints || {}; 381 | details.setpoints.highHumidity = parseInt(data[3], 16); 382 | details.setpoints.lowHumidity = parseInt(data[4], 16); 383 | 384 | return details; 385 | }) 386 | 387 | ); 388 | 389 | return deferred.promise.nodeify(next); 390 | 391 | }; 392 | 393 | 394 | Thermostat.prototype.status = function (next) { 395 | 396 | var cmd02 = { 397 | cmd1: '2E', 398 | cmd2: '02', 399 | extended: true, 400 | crc: true 401 | }; 402 | 403 | var insteon = this.insteon; 404 | var id = this.id; 405 | 406 | 407 | var details = {}; 408 | var data; 409 | 410 | var deferred = Q.defer(); 411 | 412 | deferred.resolve( 413 | insteon.directCommand(id, cmd02) 414 | .then(function (status) { 415 | if(!status || !status.response || !status.response.extended) { 416 | debug('No response for status command.'); 417 | return null; 418 | } 419 | 420 | if(!status.response.extended.userData || !status.response.extended.crc) { 421 | return null; 422 | } 423 | 424 | data = status.response.extended.userData; 425 | 426 | details.date = { 427 | day: parseInt(data[1], 16), 428 | hour: parseInt(data[2], 16), 429 | minute: parseInt(data[3], 16), 430 | seconds: parseInt(data[4], 16) 431 | }; 432 | 433 | var rawMode = parseInt(data[5], 16); 434 | var modes = ['off', 'auto', 'heat', 'cool', 'program']; 435 | 436 | details.mode = modes[rawMode >> 4]; 437 | details.fan = !!(rawMode & 1); 438 | 439 | details.setpoints = { 440 | cool: convertTemp('F', insteon.defaultTempUnits, parseInt(data[6], 16)), 441 | heat: convertTemp('F', insteon.defaultTempUnits, parseInt(data[11], 16)) 442 | }; 443 | 444 | details.humidity = parseInt(data[7], 16); 445 | 446 | var tempC = parseInt(data[8] + data[9], 16) / 10; 447 | 448 | details.temperature = convertTemp('C', insteon.defaultTempUnits, tempC); 449 | 450 | var rawState = parseInt(data[10], 16); 451 | 452 | details.cooling = !!(rawState & 1); 453 | details.heating = !!((rawState >> 1) & 1); 454 | details.energySaving = !!((rawState >> 2) & 1); 455 | details.unit = ((rawState >> 3) & 1) ? 'C' : 'F'; 456 | details.hold = !!((rawState >> 4) & 1); 457 | 458 | return details; 459 | }) 460 | 461 | ); 462 | 463 | return deferred.promise.nodeify(next); 464 | 465 | }; 466 | 467 | Thermostat.prototype.monitor = function (enable, next) { 468 | if(typeof enable === 'function') 469 | { 470 | next = enable; 471 | enable = true; 472 | } 473 | 474 | if(enable === null || enable === undefined) { 475 | enable = true; 476 | } 477 | 478 | var id = this.id; 479 | var insteon = this.insteon; 480 | 481 | if(enable) { 482 | 483 | return insteon.scene(id, 'gw', {group: 0xEF}) 484 | .then(function() { 485 | var cmdDataSet08 = { 486 | cmd1: '2E', 487 | cmd2: '00', 488 | extended: true, 489 | exitOnAck: true, 490 | userData: ['08'] 491 | }; 492 | return insteon.directCommand(id, cmdDataSet08, next); 493 | }); 494 | } 495 | 496 | return insteon.scene(id, null, {group: 0xEF, remove: true}, next); 497 | }; 498 | 499 | Thermostat.prototype.handleDirect = function (cmd1, cmd2) { 500 | 501 | this.emit('command', cmd1, cmd2); 502 | 503 | switch(cmd1) { 504 | case '6e': 505 | this.emit('status', { 'temperature': convertTemp('F', this.insteon.defaultTempUnits, 0.5 * cmd2) }); 506 | break; 507 | case '6f': 508 | this.emit('status', { 'humidity': cmd2 }); 509 | break; 510 | case '70': 511 | var fan = !!(cmd2 & 0x10); 512 | var modes = ['off', 'heat', 'cool', 'auto', 'program']; 513 | var mode = modes[cmd2 & 0x0F]; 514 | this.emit('status', { 'mode': mode, 'fan': fan }); 515 | break; 516 | case '71': 517 | this.emit('status', { 'coolSetpoint': cmd2 }); 518 | break; 519 | case '72': 520 | this.emit('status', { 'heatSetpoint': cmd2 }); 521 | break; 522 | default: 523 | debug('No event for command - %s', cmd1); 524 | } 525 | 526 | }; 527 | 528 | Thermostat.prototype.handleAllLinkBroadcast = function (group, cmd1) { 529 | 530 | this.emit('command', group, cmd1); 531 | 532 | switch(cmd1) { 533 | case '11': 534 | this.emit(['cooling', 'heating', 'highHumidity', 'lowHumidity'][group-1]); 535 | break; 536 | case '13': 537 | if(group === 3 || group === 4) { 538 | this.emit('normalHumidity'); 539 | } else { 540 | this.emit('off'); 541 | } 542 | break; 543 | default: 544 | debug('No event for command - %s', cmd1); 545 | } 546 | 547 | }; 548 | 549 | 550 | Thermostat.prototype.cancelPending = function() { 551 | this.insteon.cancelPending(this.id); 552 | }; 553 | 554 | module.exports = Thermostat; 555 | -------------------------------------------------------------------------------- /lib/Insteon/X10.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'); 2 | var utils = require('./utils'); 3 | var debug = require('debug')('home-controller:insteon:x10'); 4 | var toByte = utils.toByte; 5 | 6 | 7 | var CODES = [0x6, 0xE, 0x2, 0xA, 0x1, 0x9, 0x5, 0xD, 0x7, 0xF, 0x3, 0xB, 0x0, 0x8, 0x4, 0xC]; 8 | var ON_CODE = 0x2; 9 | var OFF_CODE = 0x3; 10 | 11 | 12 | function X10(id, insteon) { 13 | this.id = id; 14 | this.house = id.substring(0,1); 15 | this.unit = id.substring(1); 16 | 17 | var houseCodeIndex = this.house.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0); 18 | var unitCodeIndex = parseInt(this.unit) - 1; 19 | 20 | this.houseCode = CODES[houseCodeIndex] << 4; 21 | this.unitCode = CODES[unitCodeIndex]; 22 | 23 | this.unitCmd = { 24 | exitOnAck: true, 25 | raw: '0263' + toByte(this.houseCode + this.unitCode) + '00', 26 | id: this.id 27 | }; 28 | 29 | this.insteon = insteon; 30 | } 31 | X10.prototype.turnOn = function (next) { 32 | return this.send(ON_CODE, next); 33 | }; 34 | 35 | X10.prototype.turnOff = function (next) { 36 | return this.send(OFF_CODE, next); 37 | }; 38 | 39 | X10.prototype.send = function(code, next) { 40 | var insteon = this.insteon; 41 | 42 | var cmd = { 43 | exitOnAck: true, 44 | raw: '0263' + toByte(this.houseCode + code) + '80', 45 | id: this.id 46 | }; 47 | 48 | debug('sending x10 unit', this.unitCmd); 49 | 50 | var deferred = Q.defer(); 51 | deferred.resolve( 52 | insteon.sendCommand(this.unitCmd) 53 | .delay(500) 54 | .then(function () { 55 | debug('sending x10 cmd', cmd); 56 | return insteon.sendCommand(cmd); 57 | }) 58 | ); 59 | 60 | return deferred.promise.nodeify(next); 61 | }; 62 | 63 | 64 | 65 | 66 | X10.prototype.cancelPending = function() { 67 | this.insteon.cancelPending(this.id); 68 | }; 69 | 70 | 71 | module.exports = X10; -------------------------------------------------------------------------------- /lib/Insteon/httpClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP client for the Insteon hub 2245 3 | * 4 | * This client is somewhat unique in that it is trying to access a simple GET-based 5 | * API which is a bridge to the insteon Serial PLM inside the hub. 6 | * 7 | * This implementation is mainly made complex because the hub's HTTP API is 8 | * single-threaded, and will not accept more than a single TCP connection, 9 | * and Node's callback-based implementation of an HTTP client means we need 10 | * to do our own tracking and queueing to implement our own "single threaded" 11 | * implementation which never tries to make concurrent requests to the hub. 12 | */ 13 | var Q = require('q'); 14 | var http = require('http'); 15 | var debug = require('debug')('home-controller:http'); 16 | var utils = require('./utils'); 17 | 18 | function httpClient(insteon, config, connectListener) { 19 | var maxDelay = config.maxDelay || 5000; 20 | var knownBufferPage = ''; 21 | var inRequest = false; 22 | var running = true; 23 | var queue = []; 24 | var currentDelay = 100; 25 | var agent = new http.Agent({ 26 | maxSockets: 1, // Most important config: insteon hub does not like having tons of sockets 27 | keepAlive: true, 28 | keepAliveMsecs: 5000 // Be nice and give the socket back in 5sec. 29 | }); 30 | 31 | var defaultOptions = { 32 | agent: agent, 33 | hostname: config.host, 34 | port: config.port 35 | }; 36 | if (config.user) { 37 | defaultOptions.auth = config.user + ':' + config.password; 38 | } 39 | 40 | function httpOptions(x) { return utils.assignDefaults(defaultOptions, x); } 41 | 42 | function finish() { 43 | inRequest = false; 44 | if (queue.length) { 45 | getData(queue.shift()); 46 | } 47 | } 48 | 49 | function getData(input) { 50 | var deferred = input._promise || Q.defer(); 51 | if (inRequest) { 52 | input._promise = deferred; 53 | queue.push(input); 54 | return deferred.promise; 55 | } 56 | debug('making request', input.options.path); 57 | inRequest = true; 58 | http.request(input.options, function(response) { 59 | if (response.statusCode !== 200) { 60 | var err = new Error('Status code expected 200, got ' + response.statusCode); 61 | err.response = response; 62 | deferred.reject(err); 63 | return finish(); 64 | } 65 | 66 | var rawData = ''; 67 | 68 | response.on('data', function(chunk) { 69 | rawData += chunk; 70 | }); 71 | 72 | response.on('end', function() { 73 | deferred.resolve(rawData, response); 74 | finish(); 75 | }); 76 | }).on('error', function(err) { 77 | finish(); 78 | deferred.reject(err); 79 | }).end(); 80 | 81 | return deferred.promise; 82 | } 83 | 84 | function clearBuf() { 85 | debug('buffer clearing'); 86 | return getData({ 87 | options: httpOptions({path: '/1?XB=M=1'}), 88 | }); 89 | } 90 | 91 | 92 | function fetchBuf() { 93 | return getData({ 94 | options: httpOptions({path: '/buffstatus.xml'}) 95 | }).then(function(data) { 96 | // data looks like (202 characters of hex) 97 | // in lieu of using an XML parser adding another library dependency, we will 98 | // just use a bit of regex to parse it. 99 | var raw = /BS>([^<]+)<\/BS/g.exec(data)[1]; 100 | if (raw.length === 202) { 101 | // The last 2 bytes are the length of 'good' data 102 | var length = parseInt(raw.substr(200), 16); 103 | raw = raw.substring(0, length); 104 | } 105 | var result = raw; 106 | if (knownBufferPage.length && raw.substring(0, knownBufferPage.length) === knownBufferPage) { 107 | result = raw.substr(knownBufferPage.length); 108 | } 109 | knownBufferPage = raw; 110 | if (result.length) { 111 | debug('good buffer', result); 112 | insteon.buffer += result; 113 | insteon.checkStatus(); 114 | currentDelay = 100; 115 | } 116 | 117 | if (raw.length > 30) { 118 | return clearBuf(); 119 | } 120 | }); 121 | } 122 | 123 | 124 | function delayFetch() { 125 | if (!running) { 126 | return; 127 | } 128 | if (!inRequest) { 129 | fetchBuf(); 130 | } 131 | if (currentDelay < maxDelay) { 132 | currentDelay += 100; 133 | } 134 | setTimeout(delayFetch, currentDelay); 135 | } 136 | 137 | insteon.write = function(hex) { 138 | return getData({ options: httpOptions({path: '/3?' + hex + '=I=3'}) }) 139 | .then(function() { 140 | debug('command sent'); 141 | currentDelay = 0; 142 | setTimeout(fetchBuf, 50); // It's typical to need at least 50ms to see a result 143 | }); 144 | }; 145 | 146 | insteon.close = function(had_error) { 147 | running = false; 148 | currentDelay = 0; 149 | // Running an extra fetchBuf ensures queue is cleared 150 | return fetchBuf().then(function() { 151 | // Shutdown after all delays are done 152 | agent.destroy(); 153 | insteon.emit('close', had_error); 154 | }); 155 | }; 156 | 157 | // Start up the event loop by first clearing the insteon buffer and then fetching events. 158 | // It would be neat to not clear the buffer, except that some commands that arrive before 159 | // there is a 'status' object would bork our system. 160 | return clearBuf() 161 | .then(function() { 162 | delayFetch(); 163 | if (connectListener) { 164 | connectListener(); 165 | } 166 | }); 167 | } 168 | 169 | module.exports = httpClient; 170 | -------------------------------------------------------------------------------- /lib/Insteon/utils.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var DEV_CAT_NAMES = ['Generalized Controllers', 'Dimmable Lighting Control', 4 | 'Switched Lighting Control', 'Network Bridges', 'Irrigation Control', 5 | 'Climate Control', 'Pool and Spa Control', 'Sensors and Actuators', 6 | 'Home Entertainment', 'Energy Management', 'Built-In Appliance Control', 7 | 'Plumbing', 'Communication', 'Computer Control', 'Window Coverings', 8 | 'Access Control', 'Security', 'Surveillance', 'Automotive', 'Pet Care', 9 | 'Toys', 'Timekeeping', 'Holiday']; 10 | 11 | var VERSIONS = { 12 | '00': 'i1', 13 | '01': 'i2', 14 | '02': 'i2cs' 15 | }; 16 | 17 | var RAMP_RATES = [ 18 | 2000, // shouldn't be used 19 | 480000, 20 | 420000, 21 | 360000, 22 | 300000, 23 | 270000, 24 | 240000, 25 | 210000, 26 | 180000, 27 | 150000, 28 | 120000, 29 | 90000, 30 | 60000, 31 | 47000, 32 | 43000, 33 | 38500, 34 | 34000, 35 | 32000, 36 | 30000, 37 | 28000, 38 | 26000, 39 | 23500, 40 | 21500, 41 | 19000, 42 | 8500, 43 | 6500, 44 | 4500, 45 | 2000, 46 | 500, 47 | 300, 48 | 200, 49 | 100 50 | ]; 51 | 52 | 53 | function levelToHexByte(level) { 54 | if (level < 0 || level > 100) { 55 | throw new Error('level must be between 0 and 100'); 56 | } 57 | // scale level to a max of 0xFF (255) 58 | level = ~~ (255 * level / 100); 59 | 60 | return toByte(level); 61 | 62 | } 63 | 64 | function levelToHexHalfByte(level) { 65 | if (level < 0 || level > 100) { 66 | throw new Error('level must be between 0 and 100'); 67 | } 68 | // scale level to a max of 0xF (15) 69 | level = ~~ (15 * level / 100); 70 | 71 | return level.toString(16).toUpperCase(); 72 | 73 | } 74 | 75 | function byteToLevel(byte) { 76 | return Math.ceil(parseInt(byte, 16) * 100 / 255); 77 | } 78 | 79 | function byteToRampRate(byte) { 80 | return RAMP_RATES[parseInt(byte, 16)]; 81 | } 82 | 83 | 84 | function lookupRampRateIndex(rate) { 85 | 86 | for(var i = 1; i < RAMP_RATES.length; i++) { 87 | if (rate >= RAMP_RATES[i]) { 88 | return i; 89 | } 90 | } 91 | 92 | return RAMP_RATES.length - 1; 93 | } 94 | 95 | function rampRateToHexByte(rate) { 96 | return toByte(lookupRampRateIndex(rate)); 97 | } 98 | 99 | function rampRateToHexHalfByte(rate) { 100 | return (~~((lookupRampRateIndex(rate))/2)).toString(16).toUpperCase(); 101 | } 102 | 103 | function toByte(value, length) { 104 | length = length || 1; 105 | value = value.toString(16).toUpperCase(); 106 | var pad = new Array((length * 2) + 1).join('0'); 107 | return pad.substring(0, pad.length - value.length) + value; 108 | } 109 | 110 | function assignDefaults(defaults, options) { 111 | if (!options) { 112 | options = defaults; 113 | return options; 114 | } 115 | for(var key in defaults) { 116 | options[key] = (typeof options[key] === 'undefined') ? defaults[key] : options[key]; 117 | } 118 | 119 | return options; 120 | } 121 | 122 | function convertTemp(from, to, val) { 123 | if(from === to) { 124 | return val; 125 | } 126 | 127 | if(from === 'F') { 128 | var c = (val - 32) * 5/9; 129 | if(to === 'K') { 130 | return c + 273.15; 131 | } 132 | return c; 133 | } 134 | 135 | if(from === 'K') { 136 | val = val - 273.15; 137 | } 138 | 139 | if(to === 'C') { 140 | return val; 141 | } 142 | 143 | if(to === 'K') { 144 | return val + 273.15; 145 | } 146 | 147 | return (val * 9/5) + 32; 148 | } 149 | 150 | 151 | 152 | module.exports = { 153 | levelToHexByte: levelToHexByte, 154 | levelToHexHalfByte: levelToHexHalfByte, 155 | rampRateToHexByte: rampRateToHexByte, 156 | rampRateToHexHalfByte: rampRateToHexHalfByte, 157 | byteToLevel: byteToLevel, 158 | byteToRampRate: byteToRampRate, 159 | toByte: toByte, 160 | assignDefaults: assignDefaults, 161 | convertTemp: convertTemp, 162 | DEV_CAT_NAMES: DEV_CAT_NAMES, 163 | VERSIONS: VERSIONS, 164 | RAMP_RATES: RAMP_RATES 165 | }; 166 | -------------------------------------------------------------------------------- /lib/Test/Plan.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | function Plan(count, done) { 4 | this.done = done; 5 | this.count = count; 6 | this.ok = this.ok.bind(this); 7 | } 8 | 9 | Plan.prototype.ok = function () { 10 | 11 | if (this.count === 0) { 12 | assert(false, 'Too many assertions called'); 13 | } else { 14 | this.count--; 15 | } 16 | 17 | if (this.count === 0) { 18 | this.done(); 19 | } 20 | }; 21 | 22 | module.exports = Plan; 23 | -------------------------------------------------------------------------------- /lib/Test/mockHub.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | var util = require('util'); 3 | 4 | var mockHub = net.createServer(function (socket) { 5 | mockHub.socket = socket; 6 | socket.setEncoding('hex'); 7 | socket.on('data', function (cmd) { 8 | if (!util.isArray(mockHub.mockData)) { 9 | mockHub.mockData = [mockHub.mockData]; 10 | } 11 | var responses; 12 | for (var i in mockHub.mockData) { 13 | var data = mockHub.mockData[i]; 14 | if (data[cmd]) { 15 | responses = data[cmd]; 16 | mockHub.mockData.splice(i, 1); 17 | break; 18 | } 19 | } 20 | 21 | mockHub.send(responses); 22 | }); 23 | }); 24 | 25 | mockHub.mockData = {}; 26 | 27 | mockHub.send = function (responses, next) { 28 | if (!responses) { 29 | if (next) { 30 | next(); 31 | } 32 | return; 33 | } 34 | 35 | if (typeof responses === 'string') { 36 | responses = [responses]; 37 | } 38 | 39 | function write(response) { 40 | if (!response) { 41 | if (next) { 42 | next(); 43 | } 44 | return; 45 | } 46 | mockHub.socket.write(response, 'hex'); 47 | setTimeout(function () { 48 | write(responses.shift()); 49 | }, 10); 50 | } 51 | write(responses.shift()); 52 | }; 53 | 54 | module.exports = mockHub; 55 | -------------------------------------------------------------------------------- /lib/Test/mockSerial.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var mockSerial = { 4 | port: null, 5 | mockData: [] 6 | }; 7 | 8 | mockSerial.attach = function (port) { 9 | mockSerial.port = port; 10 | 11 | var write = mockSerial.port.write.bind(mockSerial.port); 12 | mockSerial.port.write = function (cmd) { 13 | write(cmd, function () { 14 | cmd = cmd.toString('hex'); 15 | 16 | if (!util.isArray(mockSerial.mockData)) { 17 | mockSerial.mockData = [mockSerial.mockData]; 18 | } 19 | 20 | var responses; 21 | for (var i in mockSerial.mockData) { 22 | var data = mockSerial.mockData[i]; 23 | if (data[cmd]) { 24 | responses = data[cmd]; 25 | mockSerial.mockData.splice(i, 1); 26 | break; 27 | } 28 | } 29 | 30 | mockSerial.send(responses); 31 | }); 32 | }; 33 | }; 34 | 35 | mockSerial.send = function (responses, next) { 36 | if (!responses) { 37 | if (next) { 38 | next(); 39 | } 40 | return; 41 | } 42 | 43 | if (typeof responses === 'string') { 44 | responses = [responses]; 45 | } 46 | 47 | function write(response) { 48 | if (!response) { 49 | if (next) { 50 | next(); 51 | } 52 | return; 53 | } 54 | mockSerial.port.binding.emitData(Buffer.from(response, 'hex')); 55 | setTimeout(function () { 56 | write(responses.shift()); 57 | }, 10); 58 | } 59 | write(responses.shift()); 60 | }; 61 | 62 | module.exports = mockSerial; 63 | -------------------------------------------------------------------------------- /lib/assign.js: -------------------------------------------------------------------------------- 1 | if (typeof Object.assign !== 'function') { 2 | Object.assign = function (target) { // .length of function is 2 3 | 'use strict'; 4 | if (target == null) { // TypeError if undefined or null 5 | throw new TypeError('Cannot convert undefined or null to object'); 6 | } 7 | 8 | var to = Object(target); 9 | 10 | for (var index = 1; index < arguments.length; index++) { 11 | var nextSource = arguments[index]; 12 | 13 | if (nextSource != null) { // Skip over if undefined or null 14 | for (var nextKey in nextSource) { 15 | // Avoid bugs when hasOwnProperty is shadowed 16 | if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { 17 | to[nextKey] = nextSource[nextKey]; 18 | } 19 | } 20 | } 21 | } 22 | return to; 23 | }; 24 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Insteon = require('./Insteon'); 2 | exports.Insteon = Insteon; 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.9.2", 3 | "author": "Brandon Goode ", 4 | "name": "home-controller", 5 | "description": "A Node Home Automation Controller for Insteon Devices", 6 | "keywords": [ 7 | "automation", 8 | "insteon" 9 | ], 10 | "homepage": "https://github.com/automategreen/home-controller", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/automategreen/home-controller" 14 | }, 15 | "bugs": { 16 | "email": "support@automategreen.com", 17 | "url": "https://github.com/automategreen/home-controller/issues" 18 | }, 19 | "main": "./index.js", 20 | "engines": { 21 | "node": ">= 4.0" 22 | }, 23 | "dependencies": { 24 | "commander": "^7.2.0", 25 | "debug": "^4.3.1", 26 | "eventsource": "^2.0.2", 27 | "q": "^1.5.1" 28 | }, 29 | "optionalDependencies": { 30 | "serialport": "^8.0.7", 31 | "spark": "^1.0.0" 32 | }, 33 | "devDependencies": { 34 | "coveralls": "^3.1.0", 35 | "grunt": "^1.4.0", 36 | "grunt-contrib-jshint": "^3.0.0", 37 | "grunt-mocha-istanbul": "*", 38 | "grunt-mocha-test": "^0.13.3", 39 | "istanbul": "*", 40 | "mocha": "^8.4.0", 41 | "should": "^13.2.3" 42 | }, 43 | "scripts": { 44 | "test": "grunt" 45 | }, 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "globals": { 4 | "describe": false, 5 | "it": false, 6 | "suite": false, 7 | "test": false, 8 | "before": false, 9 | "after": false, 10 | "afterEach": false 11 | } 12 | } -------------------------------------------------------------------------------- /test/Door.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var Plan = require('../lib/Test/Plan'); 5 | var mockHub = require('../lib/Test/mockHub'); 6 | var should = require('should'); 7 | 8 | var host = '127.0.0.1'; 9 | var port = 9761; 10 | 11 | describe('Door Events', function () { 12 | this.timeout(5000); 13 | 14 | before(function (done) { 15 | mockHub.listen(port, host, function () { 16 | done(); 17 | }); 18 | }); 19 | 20 | after(function (done) { 21 | this.timeout(10000); 22 | mockHub.close(function() { 23 | done(); 24 | }); 25 | }); 26 | 27 | it('emits opened event', function (done) { 28 | var plan = new Plan(3, function() { 29 | gw.close(); 30 | done(); 31 | }); 32 | var gw = new Insteon(); 33 | var door = gw.door('284283'); 34 | 35 | door.on('command', function (group, cmd1) { 36 | should.exists(group); 37 | should.exists(cmd1); 38 | group.should.equal(1); 39 | cmd1.should.equal('11'); 40 | plan.ok(); 41 | }); 42 | 43 | door.on('opened', function () { 44 | plan.ok(); 45 | }); 46 | 47 | gw.connect(host, function () { 48 | setTimeout(function () { 49 | mockHub.send([ 50 | '0250284283000001cf1101', 51 | '0250284283000001cf1101', 52 | '02502842831eb552451101', 53 | '0250284283110101cf0600', 54 | '0250284283110101cf0600' 55 | ], function () { 56 | plan.ok(); 57 | }); 58 | }, 10); 59 | }); 60 | }); 61 | 62 | it('emits closed event', function (done) { 63 | var plan = new Plan(3, function() { 64 | gw.close(); 65 | done(); 66 | }); 67 | var gw = new Insteon(); 68 | var door = gw.door('284283'); 69 | 70 | door.on('command', function (group, cmd1) { 71 | group.should.equal(1); 72 | cmd1.should.equal('13'); 73 | plan.ok(); 74 | }); 75 | 76 | door.on('closed', function () { 77 | plan.ok(); 78 | }); 79 | 80 | gw.connect(host, function () { 81 | setTimeout(function () { // make sure server connection event fires first 82 | mockHub.send([ 83 | '0250284283000001cf1301', 84 | '0250284283000001cf1301', 85 | '02502842831eb552451301', 86 | '0250284283130101cf0600', 87 | '0250284283130101cf0600' 88 | ], function () { 89 | plan.ok(); 90 | }); 91 | }, 10); 92 | }); 93 | }); 94 | 95 | it('emits closed event - ignoring direct message from Insteon App', function (done) { 96 | var plan = new Plan(11, function() { 97 | gw.close(); 98 | done(); 99 | }); 100 | var gw = new Insteon(); 101 | gw.emitDuplicates = true; 102 | var door = gw.door('284283'); 103 | 104 | door.on('command', function (group, cmd1) { 105 | group.should.equal(1); 106 | cmd1.should.equal('13'); 107 | plan.ok(); 108 | }); 109 | 110 | door.on('closed', function () { 111 | plan.ok(); 112 | }); 113 | 114 | gw.connect(host, function () { 115 | setTimeout(function () { // make sure server connection event fires first 116 | mockHub.send([ 117 | '0262aabbcc05190206', 118 | '0250284283000001cf1301', 119 | '0250284283000001cf1301', 120 | '02502842831eb552451301', 121 | '0250284283130101cf0600', 122 | '0250284283130101cf0600' 123 | ], function () { 124 | plan.ok(); 125 | }); 126 | }, 10); 127 | }); 128 | }); 129 | 130 | it('emits closed event - with emitDuplicates', function (done) { 131 | var plan = new Plan(11, function() { 132 | gw.close(); 133 | done(); 134 | }); 135 | var gw = new Insteon(); 136 | gw.emitDuplicates = true; 137 | var door = gw.door('284283'); 138 | 139 | door.on('command', function (group, cmd1) { 140 | group.should.equal(1); 141 | cmd1.should.equal('13'); 142 | plan.ok(); 143 | }); 144 | 145 | door.on('closed', function () { 146 | plan.ok(); 147 | }); 148 | 149 | gw.connect(host, function () { 150 | setTimeout(function () { // make sure server connection event fires first 151 | mockHub.send([ 152 | '0250284283000001cf1301', 153 | '0250284283000001cf1301', 154 | '02502842831eb552451301', 155 | '0250284283130101cf0600', 156 | '0250284283130101cf0600' 157 | ], function () { 158 | plan.ok(); 159 | }); 160 | }, 10); 161 | }); 162 | }); 163 | 164 | it('emits closed event - with duplicates 6 seconds apart', function (done) { 165 | 166 | this.timeout(10000); 167 | 168 | var plan = new Plan(5, function() { 169 | gw.close(); 170 | done(); 171 | }); 172 | var gw = new Insteon(); 173 | var door = gw.door('284283'); 174 | 175 | door.on('command', function (group, cmd1) { 176 | group.should.equal(1); 177 | cmd1.should.equal('13'); 178 | plan.ok(); 179 | }); 180 | 181 | door.on('closed', function () { 182 | plan.ok(); 183 | }); 184 | 185 | gw.connect(host, function () { 186 | setTimeout(function () { 187 | mockHub.send([ 188 | '0250284283000001cf1301', 189 | '0250284283000001cf1301', 190 | '02502842831eb552451301', 191 | '0250284283130101cf0600', 192 | '0250284283130101cf0600' 193 | ], function () { 194 | 195 | setTimeout(function () { 196 | mockHub.send([ 197 | '0250284283000001cf1301', 198 | '0250284283000001cf1301', 199 | '02502842831eb552451301', 200 | '0250284283130101cf0600', 201 | '0250284283130101cf0600' 202 | ], function () { 203 | plan.ok(); 204 | }); 205 | }, 6000); 206 | }); 207 | }, 10); 208 | }); 209 | }); 210 | 211 | it('emits open/closed event - within 3 seconds', function (done) { 212 | var plan = new Plan(5, function() { 213 | gw.close(); 214 | done(); 215 | }); 216 | var gw = new Insteon(); 217 | var door = gw.door('284283'); 218 | 219 | door.on('command', function (group) { 220 | group.should.equal(1); 221 | plan.ok(); 222 | }); 223 | door.on('opened', function () { 224 | plan.ok(); 225 | }); 226 | door.on('closed', function () { 227 | plan.ok(); 228 | }); 229 | 230 | gw.connect(host, function () { 231 | setTimeout(function () { // make sure server connection event fires first 232 | mockHub.send([ 233 | '0250284283000001cf1101', 234 | '0250284283000001cf1101', 235 | '02502842831eb552451101', 236 | '0250284283110101cf0600', 237 | '0250284283110101cf0600', 238 | '0250284283000001cf1301', 239 | '0250284283000001cf1301', 240 | '02502842831eb552451301', 241 | '0250284283130101cf0600', 242 | '0250284283130101cf0600' 243 | ], function () { 244 | plan.ok(); 245 | }); 246 | }, 10); 247 | }); 248 | }); 249 | 250 | it('emits closed event - group 2', function (done) { 251 | var plan = new Plan(3, function() { 252 | gw.close(); 253 | done(); 254 | }); 255 | var gw = new Insteon(); 256 | var door = gw.door('284283'); 257 | 258 | door.on('command', function (group, cmd1) { 259 | group.should.equal(2); 260 | cmd1.should.equal('11'); 261 | plan.ok(); 262 | }); 263 | 264 | door.on('closed', function () { 265 | plan.ok(); 266 | }); 267 | 268 | gw.connect(host, function () { 269 | setTimeout(function () { // make sure server connection event fires first 270 | mockHub.send([ 271 | '0250284283000002cb1102', 272 | '0250', 273 | '284283000002cb1102', 274 | '0250284283110002cf0600', 275 | '0250284283110002cf0600' 276 | ], function () { 277 | plan.ok(); 278 | }); 279 | }, 10); 280 | }); 281 | }); 282 | 283 | it('emits heartbeat event - opened', function (done) { 284 | var plan = new Plan(4, function() { 285 | gw.close(); 286 | done(); 287 | }); 288 | var gw = new Insteon(); 289 | var door = gw.door('284283'); 290 | 291 | door.on('command', function (group, cmd1) { 292 | group.should.equal(4); 293 | cmd1.should.equal('11'); 294 | plan.ok(); 295 | }); 296 | 297 | door.on('heartbeat', function () { 298 | plan.ok(); 299 | }); 300 | 301 | door.on('opened', function () { 302 | plan.ok(); 303 | }); 304 | 305 | gw.connect(host, function () { 306 | setTimeout(function () { // make sure server connection event fires first 307 | mockHub.send([ 308 | '0250284283000004cf1104', 309 | '0250284283000004cf1104', 310 | '0250284283110004cf0600', 311 | '0250284283110004cf0600' 312 | ], function () { 313 | plan.ok(); 314 | }); 315 | }, 10); 316 | }); 317 | }); 318 | 319 | it('emits heartbeat event - closed', function (done) { 320 | var plan = new Plan(4, function() { 321 | gw.close(); 322 | done(); 323 | }); 324 | var gw = new Insteon(); 325 | var door = gw.door('284283'); 326 | 327 | door.on('command', function (group, cmd1) { 328 | group.should.equal(4); 329 | cmd1.should.equal('13'); 330 | plan.ok(); 331 | }); 332 | 333 | door.on('heartbeat', function () { 334 | plan.ok(); 335 | }); 336 | 337 | door.on('closed', function () { 338 | plan.ok(); 339 | }); 340 | 341 | gw.connect(host, function () { 342 | setTimeout(function () { // make sure server connection event fires first 343 | mockHub.send([ 344 | '0250284283000004cf1304', 345 | '0250284283000004cf1304', 346 | '0250284283130004cf0600', 347 | '0250284283130004cf0600' 348 | ], function () { 349 | plan.ok(); 350 | }); 351 | }, 10); 352 | }); 353 | }); 354 | 355 | it('recieves unknown command', function (done) { 356 | var gw = new Insteon(); 357 | var door = gw.door('284283'); 358 | var plan = new Plan(4, function() { 359 | gw.close(); 360 | done(); 361 | }); 362 | 363 | door.on('command', function (group, cmd1) { 364 | group.should.equalOneOf([1, 3, 4]); 365 | cmd1.should.equal('12'); 366 | plan.ok(); 367 | }); 368 | 369 | gw.connect(host, function () { 370 | setTimeout(function () { 371 | mockHub.send([ 372 | '0250284283000004cf1204', 373 | '0250284283000001cf1204', 374 | '0250284283000003cf1204' 375 | ], function () { 376 | plan.ok(); 377 | }); 378 | }, 10); 379 | }); 380 | }); 381 | }); // Door Events 382 | 383 | -------------------------------------------------------------------------------- /test/Garage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | var Plan = require('../lib/Test/Plan'); 6 | var mockHub = require('../lib/Test/mockHub'); 7 | 8 | var host = '127.0.0.1'; 9 | var port = 9761; 10 | 11 | describe('Garage Door', function () { 12 | this.timeout(5000); 13 | 14 | before(function (done) { 15 | mockHub.listen(port, host, function () { 16 | done(); 17 | }); 18 | }); 19 | 20 | after(function (done) { 21 | mockHub.close(function () { 22 | done(); 23 | }); 24 | }); 25 | 26 | it('gets status', function (done) { 27 | var gw = new Insteon(); 28 | 29 | mockHub.mockData = { 30 | '0262aabbcc0f1901': [ 31 | '0262aabbcc0f190106', 32 | '0250aabbcc1122332b0201' 33 | ] 34 | }; 35 | 36 | gw.connect(host, function () { 37 | gw.garage('aabbcc') 38 | .status() 39 | .then(function (status) { 40 | should.exist(status); 41 | status.should.equal('closed'); 42 | gw.close(); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | 48 | it('fails to get status', function (done) { 49 | var gw = new Insteon(); 50 | 51 | mockHub.mockData = { 52 | '0262aabbcc0f1901': '0262aabbcc0f190115' 53 | }; 54 | 55 | gw.connect(host, function () { 56 | gw.garage('aabbcc') 57 | .status() 58 | .then(function (status) { 59 | should.not.exist(status); 60 | gw.close(); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | 66 | it('tests lockout period', function (done) { 67 | this.slow(5000); 68 | 69 | var gw = new Insteon(); 70 | var plan = new Plan(7, function () { 71 | gw.close(); 72 | done(); 73 | }); 74 | 75 | mockHub.mockData = [{ 76 | '0262aabbcc0f1901': ['0262aabbcc0f190106', '0250aabbcc1122332b0201'] 77 | }, 78 | { 79 | '0262aabbcc0f11ff': ['0262aabbcc0f11ff06', '0250aabbcc1122332b11ff'] 80 | }, 81 | { 82 | '0262aabbcc0f1901': ['0262aabbcc0f190106', '0250aabbcc1122332b0200'] 83 | }, 84 | { 85 | '0262aabbcc0f11ff': ['0262aabbcc0f11ff06', '0250aabbcc1122332b11ff'] 86 | } 87 | ]; 88 | 89 | gw.connect(host, function () { 90 | var g = gw.garage('aabbcc'); 91 | 92 | // for this test's purposes reduce lockout time 93 | g.LOCKOUT_TIME = 2000; 94 | 95 | g.on('open', function () { 96 | plan.ok(); 97 | }); 98 | g.on('closed', function () { 99 | plan.ok(); 100 | }); 101 | g.on('opening', function () { 102 | plan.ok(); 103 | }); 104 | g.on('closing', function () { 105 | plan.ok(); 106 | }); 107 | 108 | g.open() 109 | .then(function (status) { 110 | should.exist(status); 111 | status.should.equal(true); 112 | plan.ok(); 113 | 114 | setTimeout(function () { 115 | g.close() 116 | .then(function (status) { 117 | should.exist(status); 118 | status.should.equal(true); 119 | plan.ok(); 120 | }); 121 | }, 2500); 122 | }); 123 | 124 | g.open() 125 | .then(function (status) { 126 | should.exist(status); 127 | status.should.equal(false); 128 | plan.ok(); 129 | }); 130 | }); 131 | }); 132 | 133 | it('tests wrong state/action combination', function (done) { 134 | var gw = new Insteon(); 135 | 136 | mockHub.mockData = [{ 137 | '0262aabbcc0f1901': ['0262aabbcc0f190106', '0250aabbcc1122332b0201'] 138 | }, 139 | { 140 | '0262aabbcc0f11ff': ['0262aabbcc0f11ff06', '0250aabbcc1122332b11ff'] 141 | } 142 | ]; 143 | 144 | gw.connect(host, function () { 145 | var g = gw.garage('aabbcc'); 146 | 147 | g.close() 148 | .then(function (status) { 149 | should.exist(status); 150 | status.should.equal(false); 151 | gw.close(); 152 | done(); 153 | }); 154 | }); 155 | }); 156 | 157 | it('cancels pending', function (done) { 158 | var gw = new Insteon(); 159 | 160 | gw.connect(host, function () { 161 | var g = gw.garage('aabbcc'); 162 | var plan = new Plan(2, function () { 163 | gw.close(); 164 | done(); 165 | }); 166 | 167 | mockHub.mockData = { 168 | '0262aabbcc0f1901': '' 169 | }; 170 | 171 | g.status() 172 | .then(function (status) { 173 | should.exist(status); 174 | status.should.equal('closed'); 175 | plan.ok(); 176 | }); 177 | 178 | g.close() 179 | .then(function () { 180 | throw new Error('This command should have been canceled.'); 181 | }); 182 | 183 | g.cancelPending(); 184 | 185 | setTimeout(function () { 186 | mockHub.send(['0262aabbcc0f190106', '0250aabbcc1122332b0201'], function () { 187 | plan.ok(); 188 | }); 189 | }, 10); 190 | }); 191 | }); 192 | 193 | describe('Garage Door Events', function () { 194 | 195 | it('emits events', function (done) { 196 | var gw = new Insteon(); 197 | var plan = new Plan(4, function () { 198 | gw.close(); 199 | done(); 200 | }); 201 | 202 | gw.connect(host, function () { 203 | var g = gw.garage('aabbcc'); 204 | 205 | g.on('open', function () { 206 | plan.ok(); 207 | }); 208 | g.on('close', function () { 209 | plan.ok(); 210 | }); 211 | }); 212 | 213 | setTimeout(function () { 214 | mockHub.send([ 215 | '0250aabbcc000001cf1300', 216 | '0250aabbcc347828451301', 217 | ], function () { 218 | plan.ok(); 219 | }); 220 | }, 10); 221 | 222 | setTimeout(function () { 223 | mockHub.send([ 224 | '0250aabbcc000001cf1100', 225 | '0250aabbcc347828451101' 226 | ], function () { 227 | plan.ok(); 228 | }); 229 | }, 100); 230 | }); 231 | 232 | it('unknown command', function (done) { 233 | var gw = new Insteon(); 234 | var plan = new Plan(2, function () { 235 | gw.close(); 236 | done(); 237 | }); 238 | 239 | gw.connect(host, function () { 240 | var g = gw.garage('aabbcc'); 241 | 242 | g.on('close', function () { 243 | plan.ok(); 244 | }); 245 | }); 246 | 247 | setTimeout(function () { 248 | mockHub.send([ 249 | '0250aabbcc000001cf1300', 250 | '0250aabbcc347828451301', 251 | '0250aabbcc000001cf2500', 252 | ], function () { 253 | plan.ok(); 254 | }); 255 | }, 10); 256 | }); 257 | 258 | it('tests lockout period', function (done) { 259 | this.slow(5000); 260 | 261 | var gw = new Insteon(); 262 | var plan = new Plan(6, function () { 263 | gw.close(); 264 | done(); 265 | }); 266 | 267 | mockHub.mockData = [{ 268 | '0262aabbcc0f1901': ['0262aabbcc0f190106', '0250aabbcc1122332b0201'] 269 | }, 270 | { 271 | '0262aabbcc0f11ff': ['0262aabbcc0f11ff06', '0250aabbcc1122332b11ff'] 272 | }, 273 | { 274 | '0262aabbcc0f1901': ['0262aabbcc0f190106', '0250aabbcc1122332b0200'] 275 | }, 276 | { 277 | '0262aabbcc0f11ff': ['0262aabbcc0f11ff06', '0250aabbcc1122332b11ff'] 278 | } 279 | ]; 280 | 281 | gw.connect(host, function () { 282 | var g = gw.garage('aabbcc'); 283 | 284 | // for this test's purposes reduce lockout time 285 | g.LOCKOUT_TIME = 2000; 286 | 287 | g.on('open', function () { 288 | plan.ok(); 289 | }); 290 | g.on('closed', function () { 291 | plan.ok(); 292 | }); 293 | g.on('opening', function () { 294 | plan.ok(); 295 | }); 296 | g.on('closing', function () { 297 | plan.ok(); 298 | }); 299 | 300 | g.open() 301 | .then(function (status) { 302 | should.exist(status); 303 | status.should.equal(true); 304 | plan.ok(); 305 | 306 | setTimeout(function () { 307 | g.close() 308 | .then(function (status) { 309 | should.exist(status); 310 | status.should.equal(true); 311 | plan.ok(); 312 | }); 313 | }, 2500); 314 | }); 315 | }); 316 | }); 317 | }); 318 | }); -------------------------------------------------------------------------------- /test/IO.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | var Plan = require('../lib/Test/Plan'); 6 | var mockHub = require('../lib/Test/mockHub'); 7 | 8 | var host = '127.0.0.1'; 9 | var port = 9761; 10 | 11 | describe('IO Commands', function () { 12 | this.timeout(5000); 13 | 14 | before(function (done) { 15 | mockHub.listen(port, host, function () { 16 | done(); 17 | }); 18 | }); 19 | 20 | after(function (done) { 21 | mockHub.close(function() { 22 | done(); 23 | }); 24 | }); 25 | 26 | it('turns on', function (done) { 27 | var gw = new Insteon(); 28 | 29 | mockHub.mockData = { 30 | '0262aabbcc0f4500': 31 | [ 32 | '0262aabbcc0f450006', 33 | '0250aabbcc1122332b4500' 34 | ], 35 | }; 36 | 37 | gw.connect(host, function () { 38 | gw.io('aabbcc').on().then(function () { 39 | gw.close(); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('turns off', function (done) { 46 | var gw = new Insteon(); 47 | 48 | mockHub.mockData = { 49 | '0262aabbcc0f4600': 50 | [ 51 | '0262aabbcc0f460006', 52 | '0250aabbcc1122332b4600' 53 | ], 54 | }; 55 | 56 | gw.connect(host, function () { 57 | gw.io('aabbcc').off().then(function () { 58 | gw.close(); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | 64 | it('sets the data', function (done) { 65 | var gw = new Insteon(); 66 | 67 | mockHub.mockData = { 68 | '0262aabbcc0f480a': 69 | [ 70 | '0262aabbcc0f480a06', 71 | '0250aabbcc1122332b480a' 72 | ], 73 | }; 74 | 75 | gw.connect(host, function () { 76 | gw.io('aabbcc').set(10).then(function () { 77 | gw.close(); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | 83 | it('cancels pending', function (done) { 84 | var gw = new Insteon(); 85 | 86 | gw.connect(host, function () { 87 | var io = gw.io('aabbcc'); 88 | var plan = new Plan(4, function() { 89 | gw.close(); 90 | done(); 91 | }); 92 | 93 | io.on().then(function () { 94 | plan.ok(); 95 | }); 96 | 97 | io.off().then(function () { 98 | throw new Error('This command should have been canceled.'); 99 | }).fail(function (err) { 100 | should.exist(err); 101 | err.message.should.equal('Canceled'); 102 | plan.ok(); 103 | 104 | setTimeout(function () { 105 | mockHub.send([ 106 | '0262aabbcc0f460006', 107 | '0250aabbcc1122332b4600' 108 | ], function () { 109 | plan.ok(); 110 | }); 111 | }, 10); 112 | }); 113 | 114 | io.off(5).then(function () { 115 | throw new Error('This command should have been canceled.'); 116 | }).fail(function (err) { 117 | should.exist(err); 118 | err.message.should.equal('Canceled'); 119 | plan.ok(); 120 | }); 121 | 122 | setTimeout(function () { 123 | io.cancelPending(5); 124 | io.cancelPending(); 125 | }, 100); 126 | }); 127 | }); 128 | }); // IO commands 129 | 130 | -------------------------------------------------------------------------------- /test/IOLinc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var Plan = require('../lib/Test/Plan'); 5 | var mockHub = require('../lib/Test/mockHub'); 6 | 7 | var host = '127.0.0.1'; 8 | var port = 9761; 9 | 10 | describe('IO Linc Functions', function () { 11 | this.timeout(5000); 12 | 13 | before(function (done) { 14 | mockHub.listen(port, host, function () { 15 | done(); 16 | }); 17 | }); 18 | 19 | after(function (done) { 20 | mockHub.close(function () { 21 | done(); 22 | }); 23 | }); 24 | 25 | it('relay on', function (done) { 26 | var gw = new Insteon(); 27 | gw.emitSelfAck = true; 28 | 29 | mockHub.mockData = [{ 30 | '02629999990f11ff': '02629999990f11ff060250999999ffffff2f11ff' 31 | }]; 32 | 33 | gw.connect(host, function () { 34 | var ioLinc = gw.ioLinc('999999'); 35 | 36 | ioLinc.on('relayOn', function () { 37 | gw.close(); 38 | done(); 39 | }); 40 | ioLinc.relayOn(); 41 | 42 | }); 43 | }); 44 | 45 | it('relay off', function (done) { 46 | var gw = new Insteon(); 47 | gw.emitSelfAck = true; 48 | 49 | mockHub.mockData = [{ 50 | '02629999990f1300': '02629999990f1300060250999999ffffff2f1300' 51 | }]; 52 | 53 | gw.connect(host, function () { 54 | var ioLinc = gw.ioLinc('999999'); 55 | 56 | ioLinc.on('relayOff', function () { 57 | gw.close(); 58 | done(); 59 | }); 60 | ioLinc.relayOff(); 61 | 62 | }); 63 | }); 64 | 65 | it('relay status - relay and sensor "on"', function (done) { 66 | var gw = new Insteon(); 67 | 68 | mockHub.mockData = [{ 69 | '0262348bbf0f1900': '0262348bbf0f1900060250348bbf39008f2f0601' 70 | }, { 71 | '0262348bbf0f1901': '0262348bbf0f1901060250348bbf39008f2f0601' 72 | }]; 73 | 74 | gw.connect(host, function () { 75 | var ioLinc = gw.ioLinc('348bbf'); 76 | 77 | ioLinc.status().then(function (status) { 78 | status.relay.should.equal('on'); 79 | status.sensor.should.equal('on'); 80 | gw.close(); 81 | done(); 82 | }); 83 | 84 | }); 85 | }); 86 | 87 | it('relay status - relay and sensor "off"', function (done) { 88 | var gw = new Insteon(); 89 | 90 | mockHub.mockData = [{ 91 | '0262348bbf0f1900': '0262348bbf0f1900060250348bbf39008f2f0600' 92 | }, { 93 | '0262348bbf0f1901': '0262348bbf0f1901060250348bbf39008f2f0600' 94 | }]; 95 | 96 | gw.connect(host, function () { 97 | var ioLinc = gw.ioLinc('348bbf'); 98 | 99 | ioLinc.status().then(function (status) { 100 | status.relay.should.equal('off'); 101 | status.sensor.should.equal('off'); 102 | gw.close(); 103 | done(); 104 | }); 105 | 106 | }); 107 | }); 108 | 109 | describe('IO Linc Events', function () { 110 | it('emits sensorOn event', function (done) { 111 | var plan = new Plan(3, function () { 112 | gw.close(); 113 | done(); 114 | }); 115 | var gw = new Insteon(); 116 | var light = gw.ioLinc('19d41c'); 117 | 118 | light.on('command', function (group, cmd1) { 119 | this.id.should.equal('19D41C'); 120 | group.should.equal(1); 121 | cmd1.should.equal('11'); 122 | plan.ok(); 123 | }); 124 | 125 | light.on('sensorOn', function () { 126 | this.id.should.equal('19D41C'); 127 | plan.ok(); 128 | }); 129 | 130 | gw.connect(host, function () { 131 | setTimeout(function () { 132 | mockHub.send([ 133 | '025019d41c000001cb1100', 134 | '025019d41c1eb552451101', 135 | '025019d41c110101cf0600' 136 | ], function () { 137 | plan.ok(); 138 | }); 139 | }, 10); 140 | }); 141 | }); 142 | it('emits sensorOff event', function (done) { 143 | var plan = new Plan(3, function () { 144 | gw.close(); 145 | done(); 146 | }); 147 | var gw = new Insteon(); 148 | var light = gw.ioLinc('19d41c'); 149 | 150 | light.on('command', function (group, cmd1) { 151 | this.id.should.equal('19D41C'); 152 | group.should.equal(1); 153 | cmd1.should.equal('13'); 154 | plan.ok(); 155 | }); 156 | 157 | light.on('sensorOff', function () { 158 | this.id.should.equal('19D41C'); 159 | plan.ok(); 160 | }); 161 | 162 | gw.connect(host, function () { 163 | setTimeout(function () { 164 | mockHub.send([ 165 | '025019d41c000001cb1300', 166 | '025019d41c1eb552451301', 167 | '025019d41c130101cf0600' 168 | ], function () { 169 | plan.ok(); 170 | }); 171 | }, 10); 172 | }); 173 | }); 174 | it('emits relayOff event', function (done) { 175 | var plan = new Plan(3, function () { 176 | gw.close(); 177 | done(); 178 | }); 179 | var gw = new Insteon(); 180 | var light = gw.ioLinc('19d41c'); 181 | 182 | light.on('command', function (group, cmd1) { 183 | this.id.should.equal('19D41C'); 184 | group.should.equal(0); 185 | cmd1.should.equal('13'); 186 | plan.ok(); 187 | }); 188 | 189 | light.on('relayOff', function () { 190 | this.id.should.equal('19D41C'); 191 | plan.ok(); 192 | }); 193 | 194 | gw.connect(host, function () { 195 | setTimeout(function () { 196 | mockHub.send([ 197 | '025019d41c000000cb1300', 198 | '025019d41c1eb552451300', 199 | '025019d41c130100cf0600' 200 | ], function () { 201 | plan.ok(); 202 | }); 203 | }, 10); 204 | }); 205 | }); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /test/Insteon-serial.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | 6 | var SerialPort = require('serialport/test'); 7 | var MockBinding = SerialPort.Binding; 8 | MockBinding.createPort('/dev/home-controller-mock', { echo: false, record: true, readyData: '' }); 9 | 10 | var mockSerial = require('../lib/Test/mockSerial'); 11 | var Plan = require('../lib/Test/Plan'); 12 | 13 | describe('Insteon Gateway (Serial Interface)', function () { 14 | this.timeout(5000); 15 | 16 | it('tests serial connection #1', function (done) { 17 | var gw = new Insteon(); 18 | gw.SerialPort = SerialPort; 19 | 20 | gw.serial('/dev/home-controller-mock', {}, function () { 21 | gw.close(); 22 | gw.serial('/dev/home-controller-mock', { baudRate: 19200, dataBits: 8, parity: 'none', stopBits: 1 }, function () { 23 | gw.close(); 24 | done(); 25 | }); 26 | }); 27 | }); 28 | 29 | it('tests serial connection #2', function (done) { 30 | var gw = new Insteon(); 31 | gw.SerialPort = SerialPort; 32 | gw.on('connect', function () { 33 | gw.close(); 34 | done(); 35 | }); 36 | gw.serial('/dev/home-controller-mock'); 37 | }); 38 | 39 | it('tests serial socket events', function (done) { 40 | var plan = new Plan(2, function() { 41 | done(); 42 | }); 43 | 44 | var gw2 = new Insteon(); 45 | gw2.SerialPort = SerialPort; 46 | 47 | /* TODO Add error event test 48 | gw2.on('error', function(err) { 49 | should.exist(err); 50 | err.message.should.equal('test'); 51 | plan.ok(); 52 | });*/ 53 | gw2.on('close', function () { 54 | plan.ok(); 55 | }); 56 | 57 | gw2.serial('/dev/home-controller-mock', {}, function () { 58 | plan.ok(); 59 | gw2.close(); 60 | }); 61 | }); 62 | 63 | it('gets the gateway info', function (done) { 64 | var gw = new Insteon(); 65 | gw.SerialPort = SerialPort; 66 | 67 | mockSerial.mockData = { 68 | '0260': '0260ffffff03379c06' 69 | }; 70 | 71 | gw.serial('/dev/home-controller-mock', function () { 72 | mockSerial.attach(gw.socket); 73 | 74 | gw.info().then(function (info) { 75 | should.exist(info); 76 | info.firmwareVersion.should.equal('9c'); 77 | info.id.should.equal('ffffff'); 78 | info.deviceCategory.id.should.equal(3); 79 | info.deviceSubcategory.id.should.equal(55); 80 | 81 | gw.close(); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | 87 | it('beeps', function (done) { 88 | var gw = new Insteon(); 89 | gw.SerialPort = SerialPort; 90 | 91 | mockSerial.mockData = { 92 | '0262407fb40f3000': [ 93 | '0262407fb40f300006', 94 | '0250407fb43118bd2f3000' 95 | ] 96 | }; 97 | 98 | gw.serial('/dev/home-controller-mock', function () { 99 | mockSerial.attach(gw.socket); 100 | 101 | gw.beep('407fb4').then(function () { 102 | gw.close(); 103 | done(); 104 | }); 105 | }); 106 | }); 107 | 108 | }); 109 | -------------------------------------------------------------------------------- /test/Leak.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var mockHub = require('../lib/Test/mockHub'); 5 | var Plan = require('../lib/Test/Plan'); 6 | 7 | var host = '127.0.0.1'; 8 | var port = 9761; 9 | 10 | describe('Leak Events', function () { 11 | this.timeout(5000); 12 | 13 | before(function (done) { 14 | mockHub.listen(port, host, function () { 15 | done(); 16 | }); 17 | }); 18 | 19 | after(function (done) { 20 | mockHub.close(function() { 21 | done(); 22 | }); 23 | }); 24 | 25 | it('emits dry event', function (done) { 26 | var plan = new Plan(3, function() { 27 | gw.close(); 28 | done(); 29 | }); 30 | var gw = new Insteon(); 31 | var leak = gw.leak('2d2dd9'); 32 | 33 | leak.on('command', function (group, cmd1) { 34 | group.should.equal(1); 35 | cmd1.should.equal('11'); 36 | plan.ok(); 37 | }); 38 | 39 | leak.on('dry', function () { 40 | plan.ok(); 41 | }); 42 | 43 | gw.connect(host, function () { 44 | setTimeout(function () { // make sure server connection event fires first 45 | mockHub.send([ 46 | '02502d2dd9000001cb1101', 47 | '02502d2dd9000001cf1101', 48 | '02502d2dd91eb552451101', 49 | '02502d2dd9110101cf0600', 50 | '02502d2dd9110101cf0600' 51 | ], function () { 52 | plan.ok(); 53 | }); 54 | }, 10); 55 | }); 56 | }); 57 | it('emits wet event', function (done) { 58 | var plan = new Plan(3, function() { 59 | gw.close(); 60 | done(); 61 | }); 62 | var gw = new Insteon(); 63 | var leak = gw.leak('2d2dd9'); 64 | 65 | leak.on('command', function (group, cmd1) { 66 | group.should.equal(2); 67 | cmd1.should.equal('11'); 68 | plan.ok(); 69 | }); 70 | 71 | leak.on('wet', function () { 72 | plan.ok(); 73 | }); 74 | 75 | gw.connect(host, function () { 76 | setTimeout(function () { // make sure server connection event fires first 77 | mockHub.send([ 78 | '02502d2dd9000002cf1102', 79 | '02502d2dd9000002cf1102', 80 | '02502d2dd91eb552451102', 81 | '02502d2dd9110102cf0600', 82 | '02502d2dd9110102cf0600' 83 | ], function () { 84 | plan.ok(); 85 | }); 86 | }, 10); 87 | }); 88 | }); 89 | it('emits heartbeat event - dry', function (done) { 90 | var plan = new Plan(4, function() { 91 | gw.close(); 92 | done(); 93 | }); 94 | var gw = new Insteon(); 95 | var leak = gw.leak('2d2dd9'); 96 | 97 | leak.on('command', function (group, cmd1) { 98 | group.should.equal(4); 99 | cmd1.should.equal('11'); 100 | plan.ok(); 101 | }); 102 | 103 | leak.on('heartbeat', function () { 104 | plan.ok(); 105 | }); 106 | 107 | leak.on('dry', function () { 108 | plan.ok(); 109 | }); 110 | 111 | gw.connect(host, function () { 112 | setTimeout(function () { // make sure server connection event fires first 113 | mockHub.send([ 114 | '02502d2dd9000004cf1104', 115 | '02502d2dd9000004cf1104', 116 | '02502d2dd9110104cf0600', 117 | '02502d2dd9110104cf0600' 118 | ], function () { 119 | plan.ok(); 120 | }); 121 | }, 10); 122 | }); 123 | }); 124 | 125 | it('emits heartbeat event - wet', function (done) { 126 | var plan = new Plan(4, function() { 127 | gw.close(); 128 | done(); 129 | }); 130 | var gw = new Insteon(); 131 | var leak = gw.leak('2d2dd9'); 132 | 133 | leak.on('command', function (group, cmd1) { 134 | group.should.equal(4); 135 | cmd1.should.equal('13'); 136 | plan.ok(); 137 | }); 138 | 139 | leak.on('heartbeat', function () { 140 | plan.ok(); 141 | }); 142 | 143 | leak.on('wet', function () { 144 | plan.ok(); 145 | }); 146 | 147 | gw.connect(host, function () { 148 | setTimeout(function () { // make sure server connection event fires first 149 | mockHub.send([ 150 | '02502d2dd9000004cf1304', 151 | '02502d2dd9000004cf1304', 152 | '02502d2dd91eb552451304', 153 | '02502d2dd9130104cf0600', 154 | '02502d2dd9130104cf0600' 155 | ], function () { 156 | plan.ok(); 157 | }); 158 | }, 10); 159 | }); 160 | }); 161 | 162 | [ 163 | { 'raw': '02502d2dd9000004cf1204', 'group': 4 }, 164 | { 'raw': '02502d2dd9000008cf1204', 'group': 8 } 165 | ].forEach(function (data) { 166 | it('handles invalid command for group ' + data.group, function (done) { 167 | var plan = new Plan(2, function() { 168 | gw.close(); 169 | done(); 170 | }); 171 | var gw = new Insteon(); 172 | var leak = gw.leak('2d2dd9'); 173 | 174 | leak.on('command', function (group, cmd1) { 175 | group.should.equal(data.group); 176 | cmd1.should.equal('12'); 177 | plan.ok(); 178 | }); 179 | 180 | gw.connect(host, function () { 181 | setTimeout(function () { 182 | mockHub.send(data.raw, function () { 183 | plan.ok(); 184 | }); 185 | }, 10); 186 | }); 187 | }); 188 | }); 189 | }); // Leak Events 190 | 191 | -------------------------------------------------------------------------------- /test/Light.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | 6 | var mockHub = require('../lib/Test/mockHub'); 7 | var Plan = require('../lib/Test/Plan'); 8 | 9 | var host = '127.0.0.1'; 10 | var port = 9761; 11 | 12 | describe('Light commands', function () { 13 | this.timeout(5000); 14 | 15 | before(function (done) { 16 | mockHub.listen(port, host, function () { 17 | done(); 18 | }); 19 | }); 20 | 21 | after(function (done) { 22 | mockHub.close(function () { 23 | done(); 24 | }); 25 | }); 26 | 27 | it('gets light\'s informaion', function (done) { 28 | var gw = new Insteon(); 29 | var light = gw.light('112233'); 30 | var plan = new Plan(3, function () { 31 | gw.close(); 32 | done(); 33 | }); 34 | 35 | mockHub.mockData = [ 36 | { 37 | '02621122331f2e0001000000000000000000000000d1': 38 | [ 39 | '02621122331f2e0001000000000000000000000000d106', 40 | '0251112233ffffff112e0001010000202018fc7f0000000000' 41 | ] 42 | }, 43 | { 44 | '02621122331f2e0001000000000000000000000000d1': 45 | [ 46 | '02621122331f2e0001000000000000000000000000d106', 47 | '0251112233ffffff112e0001010000202018fc7f0000000000' 48 | ] 49 | }, 50 | { 51 | '02621122331f2e0001000000000000000000000000d1': 52 | [ 53 | '02621122331f2e0001000000000000000000000000d115' 54 | ] 55 | } 56 | ]; 57 | 58 | gw.connect(host, function () { 59 | light.info().then(function (info) { 60 | should.exist(info); 61 | 62 | plan.ok(); 63 | }); 64 | 65 | light.info(function (err, info) { 66 | should.not.exist(err); 67 | should.exist(info); 68 | 69 | info.rampRate.should.equal(8500); 70 | info.onLevel.should.equal(99); 71 | info.ledBrightness.should.equal(127); 72 | 73 | plan.ok(); 74 | }); 75 | 76 | light.info(function (err, info) { 77 | should.not.exist(err); 78 | should.not.exist(info); 79 | plan.ok(); 80 | }); 81 | }); 82 | }); 83 | 84 | it('turns on a light to level 50', function (done) { 85 | var gw = new Insteon(); 86 | 87 | mockHub.mockData = { 88 | '02629999990f117f': '02629999990f117f060250999999ffffff2f117f' 89 | }; 90 | 91 | gw.connect(host, function () { 92 | gw.light('999999').turnOn(50, function () { 93 | gw.close(); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | 99 | it('turns on a light to level 100', function (done) { 100 | var gw = new Insteon(); 101 | 102 | mockHub.mockData = [{ 103 | '02629999990f11ff': '02629999990f11ff060250999999ffffff2f11ff' 104 | }, 105 | { 106 | '02629999990f11ff': '02629999990f11ff060250999999ffffff2f11ff' 107 | }]; 108 | 109 | gw.connect(host, function () { 110 | gw.light('999999').turnOn(); 111 | gw.light('999999').turnOn(function () { 112 | gw.close(); 113 | done(); 114 | }); 115 | }); 116 | }); 117 | 118 | it('turns on a light to level at ramp', function (done) { 119 | var gw = new Insteon(); 120 | 121 | mockHub.mockData = { 122 | '02629999990f2e7d': '02629999990f2e7d060250999999ffffff2f2e7d' 123 | }; 124 | 125 | gw.connect(host, function () { 126 | gw.light('999999').turnOn(50, 2000, function () { 127 | gw.close(); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | 133 | it('turns on a light to level at ramp (min)', function (done) { 134 | var gw = new Insteon(); 135 | 136 | mockHub.mockData = { 137 | '02629999990f2e7f': '02629999990f2e7f060250999999ffffff2f2e7f' 138 | }; 139 | 140 | gw.connect(host, function () { 141 | gw.light('999999').turnOn(50, 0, function () { 142 | gw.close(); 143 | done(); 144 | }); 145 | }); 146 | }); 147 | 148 | it('turns on a light to level at ramp (max)', function (done) { 149 | var gw = new Insteon(); 150 | 151 | mockHub.mockData = { 152 | '02629999990f2e70': '02629999990f2e70060250999999ffffff2f2e70' 153 | }; 154 | 155 | gw.connect(host, function () { 156 | gw.light('999999').turnOn(50, 10000000, function () { 157 | gw.close(); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | 163 | it('turns on a light to level at ramp (slow)', function (done) { 164 | var gw = new Insteon(); 165 | 166 | mockHub.mockData = { 167 | '02629999990f2e76': '02629999990f2e76060250999999ffffff2f2e76' 168 | }; 169 | 170 | gw.connect(host, function () { 171 | gw.light('999999').turnOn(50, 'slow', function () { 172 | gw.close(); 173 | done(); 174 | }); 175 | }); 176 | }); 177 | 178 | it('turns on a light to level at ramp (fast)', function (done) { 179 | var gw = new Insteon(); 180 | 181 | mockHub.mockData = { 182 | '02629999990f2e7f': '02629999990f2e7f060250999999ffffff2f2e7f' 183 | }; 184 | 185 | gw.connect(host, function () { 186 | gw.light('999999').turnOn(50, 'fast', function () { 187 | gw.close(); 188 | done(); 189 | }); 190 | }); 191 | }); 192 | 193 | it('turns off a light', function (done) { 194 | var gw = new Insteon(); 195 | 196 | mockHub.mockData = { 197 | '02629999990f1300': '02629999990f1300060250999999ffffff2f1300' 198 | }; 199 | 200 | gw.connect(host, function () { 201 | gw.light('999999').turnOff(function () { 202 | gw.close(); 203 | done(); 204 | }); 205 | }); 206 | }); 207 | 208 | it('turns off a light at ramp (slow)', function (done) { 209 | var gw = new Insteon(); 210 | 211 | mockHub.mockData = { 212 | '02629999990f2f06': '02629999990f2f06060250999999ffffff2f2f06' 213 | }; 214 | 215 | gw.connect(host, function () { 216 | gw.light('999999').turnOff('slow', function () { 217 | gw.close(); 218 | done(); 219 | }); 220 | }); 221 | }); 222 | 223 | it('turns off a light at ramp (fast)', function (done) { 224 | var gw = new Insteon(); 225 | 226 | mockHub.mockData = { 227 | '02629999990f2f0f': '02629999990f2f0f060250999999ffffff2f2f0f' 228 | }; 229 | 230 | gw.connect(host, function () { 231 | gw.light('999999').turnOff('fast', function () { 232 | gw.close(); 233 | done(); 234 | }); 235 | }); 236 | }); 237 | 238 | it('turns off a light fast', function (done) { 239 | var gw = new Insteon(); 240 | 241 | mockHub.mockData = { 242 | '02629999990f1400': '02629999990f1400060250999999ffffff2f1400' 243 | }; 244 | 245 | gw.connect(host, function () { 246 | gw.light('999999').turnOffFast(function () { 247 | gw.close(); 248 | done(); 249 | }); 250 | }); 251 | }); 252 | 253 | it('gets the light level', function (done) { 254 | var gw = new Insteon(); 255 | 256 | mockHub.mockData = { 257 | '02629999990f1900': '02629999990f1900060250999999ffffff2f01ff' 258 | }; 259 | 260 | gw.connect(host, function () { 261 | gw.light('999999').level(function (err, level) { 262 | should.not.exist(err); 263 | level.should.eql(100); 264 | gw.close(); 265 | done(); 266 | }); 267 | }); 268 | }); 269 | 270 | it('sets a light level', function (done) { 271 | var gw = new Insteon(); 272 | var light = gw.light('999999'); 273 | 274 | mockHub.mockData = { 275 | '02629999990f217f': '02629999990f217f060250999999ffffff2f017f' 276 | }; 277 | 278 | gw.connect(host, function () { 279 | light.level(50, function (err, res) { 280 | should.not.exist(err); 281 | should.exist(res); 282 | gw.close(); 283 | done(); 284 | }); 285 | }); 286 | }); 287 | 288 | it('error when trying to get a light level', function (done) { 289 | var gw = new Insteon(); 290 | var light = gw.light('999999'); 291 | 292 | mockHub.mockData = { 293 | '02629999990f1900': '02629999990f190015' 294 | }; 295 | 296 | gw.connect(host, function () { 297 | light.level(function (err, res) { 298 | should.not.exist(err); 299 | should.not.exist(res); 300 | gw.close(); 301 | done(); 302 | }); 303 | }); 304 | }); 305 | 306 | it('error when trying to turn light to invalid level', function (done) { 307 | var gw = new Insteon(); 308 | 309 | mockHub.mockData = {}; 310 | 311 | gw.connect(host, function () { 312 | (function level() { 313 | gw.light('999999').level(101, function () { }); 314 | }).should.throw('level must be between 0 and 100'); 315 | gw.close(); 316 | done(); 317 | }); 318 | }); 319 | 320 | it('get the ramp rate', function (done) { 321 | var gw = new Insteon(); 322 | var light = gw.light('999999'); 323 | 324 | mockHub.mockData = 325 | [ 326 | { 327 | '02629999991f2e0001000000000000000000000000d1': 328 | [ 329 | '02629999991f2e0001000000000000000000000000d106', 330 | '0250999999ffffff2f2e00', 331 | '0251999999ffffff112e000101000020201cfe1f0000000000' 332 | ] 333 | }, 334 | { 335 | '02629999991f2e0001000000000000000000000000d1': 336 | [ 337 | '02629999991f2e0001000000000000000000000000d106', 338 | '0250999999ffffff2f2e00', 339 | '0251999999ffffff112e000101000020201cfe1f0000000000' 340 | ] 341 | } 342 | ]; 343 | 344 | gw.connect(host, function () { 345 | light.rampRate(function (err, rate) { 346 | should.not.exist(err); 347 | should.exist(rate); 348 | rate.should.eql(500); 349 | gw.close(); 350 | done(); 351 | }); 352 | light.rampRate().then(function (rate) { 353 | should.exist(rate); 354 | rate.should.eql(500); 355 | gw.close(); 356 | done(); 357 | }); 358 | }); 359 | }); 360 | 361 | it('error when trying to get a ramp rate', function (done) { 362 | var gw = new Insteon(); 363 | var light = gw.light('999999'); 364 | 365 | mockHub.mockData = { 366 | '02629999991f2e0001000000000000000000000000d1': 367 | [ 368 | '02629999991f2e0001000000000000000000000000d115' 369 | ] 370 | }; 371 | 372 | gw.connect(host, function () { 373 | light.rampRate(function (err, rate) { 374 | should.not.exist(err); 375 | should.not.exist(rate); 376 | gw.close(); 377 | done(); 378 | }); 379 | }); 380 | }); 381 | 382 | it('set the ramp rate', function (done) { 383 | var gw = new Insteon(); 384 | var light = gw.light('999999'); 385 | 386 | mockHub.mockData = { 387 | '02629999991f2e0001051c00000000000000000000b0': 388 | [ 389 | '02629999991f2e0001051c00000000000000000000b006', 390 | '0251999999ffffff112e000101000020201cfe1f0000000000' 391 | ] 392 | }; 393 | 394 | gw.connect(host, function () { 395 | light.rampRate(500, function (err, rate) { 396 | should.not.exist(err); 397 | should.exist(rate); 398 | rate.should.eql(500); 399 | gw.close(); 400 | done(); 401 | }); 402 | }); 403 | }); 404 | 405 | it('error when trying to set a ramp rate', function (done) { 406 | var gw = new Insteon(); 407 | var light = gw.light('999999'); 408 | 409 | mockHub.mockData = { 410 | '02629999991f2e0001051c00000000000000000000b0': 411 | [ 412 | '02629999991f2e0001051c00000000000000000000b015' 413 | ] 414 | }; 415 | 416 | gw.connect(host, function () { 417 | light.rampRate(500, function (err, rate) { 418 | should.not.exist(err); 419 | should.not.exist(rate); 420 | gw.close(); 421 | done(); 422 | }); 423 | }); 424 | }); 425 | 426 | it('error getting the on level', function (done) { 427 | var gw = new Insteon(); 428 | var light = gw.light('999999'); 429 | 430 | mockHub.mockData = { 431 | '02629999991f2e0001000000000000000000000000d1': '02629999991f2e0001000000000000000000000000d115' 432 | }; 433 | 434 | gw.connect(host, function () { 435 | light.onLevel().then(function (level) { 436 | should.not.exist(level); 437 | gw.close(); 438 | done(); 439 | }); 440 | }); 441 | }); 442 | 443 | it('error setting the on level', function (done) { 444 | var gw = new Insteon(); 445 | var light = gw.light('999999'); 446 | 447 | mockHub.mockData = { 448 | '02629999991f2e0001067f000000000000000000004c': '02629999991f2e0001067f000000000000000000004c15' 449 | }; 450 | 451 | gw.connect(host, function () { 452 | light.onLevel(50, function (err, level) { 453 | should.not.exist(err); 454 | should.not.exist(level); 455 | gw.close(); 456 | done(); 457 | }); 458 | }); 459 | }); 460 | 461 | it('get the on level', function (done) { 462 | var gw = new Insteon(); 463 | var light = gw.light('999999'); 464 | 465 | mockHub.mockData = 466 | [ 467 | { 468 | '02629999991f2e0001000000000000000000000000d1': 469 | [ 470 | '02629999991f2e0001000000000000000000000000d106', 471 | '0250999999ffffff2f2e00', 472 | '0251999999ffffff112e000101000020201cfe1f0000000000' 473 | ] 474 | }, 475 | { 476 | '02629999991f2e0001000000000000000000000000d1': 477 | [ 478 | '02629999991f2e0001000000000000000000000000d106', 479 | '0250999999ffffff2f2e00', 480 | '0251999999ffffff112e000101000020201cfe1f0000000000' 481 | ] 482 | } 483 | ]; 484 | 485 | gw.connect(host, function () { 486 | light.onLevel(function (err, level) { 487 | should.not.exist(err); 488 | should.exist(level); 489 | level.should.eql(100); 490 | gw.close(); 491 | done(); 492 | }); 493 | 494 | light.onLevel().then(function (level) { 495 | should.exist(level); 496 | level.should.eql(100); 497 | gw.close(); 498 | done(); 499 | }); 500 | }); 501 | }); 502 | 503 | it('set the on level', function (done) { 504 | var gw = new Insteon(); 505 | var light = gw.light('999999'); 506 | 507 | mockHub.mockData = { 508 | '02629999991f2e000106ff00000000000000000000cc': 509 | [ 510 | '02629999991f2e000106ff00000000000000000000cc06', 511 | '0251999999ffffff112e000101000020201cfe1f0000000000' 512 | ] 513 | }; 514 | 515 | gw.connect(host, function () { 516 | light.onLevel(100, function (err, level) { 517 | should.not.exist(err); 518 | should.exist(level); 519 | level.should.eql(100); 520 | gw.close(); 521 | done(); 522 | }); 523 | }); 524 | }); 525 | 526 | it('fan on', function (done) { 527 | var gw = new Insteon(); 528 | 529 | mockHub.mockData = { 530 | '02629999991f11bf020000000000000000000000002e': 531 | [ 532 | '02629999991f11bf020000000000000000000000002e06', 533 | '0250999999ffffff2f11bf' 534 | ] 535 | }; 536 | 537 | gw.connect(host, function () { 538 | gw.light('999999').fanOn(function (err) { 539 | should.not.exist(err); 540 | gw.close(); 541 | done(); 542 | }); 543 | }); 544 | }); 545 | 546 | it('fan off', function (done) { 547 | var gw = new Insteon(); 548 | 549 | mockHub.mockData = { 550 | '02629999991f110002000000000000000000000000ed': 551 | [ 552 | '02629999991f110002000000000000000000000000ed06', 553 | '0250999999ffffff2f1100' 554 | ] 555 | }; 556 | 557 | gw.connect(host, function () { 558 | gw.light('999999').fanOff(function (err) { 559 | should.not.exist(err); 560 | gw.close(); 561 | done(); 562 | }); 563 | }); 564 | }); 565 | 566 | it('fan low', function (done) { 567 | var gw = new Insteon(); 568 | 569 | mockHub.mockData = { 570 | '02629999991f113f02000000000000000000000000ae': 571 | [ 572 | '02629999991f113f02000000000000000000000000ae06', 573 | '0250999999ffffff2f113f' 574 | ] 575 | }; 576 | 577 | gw.connect(host, function () { 578 | gw.light('999999').fanLow(function (err) { 579 | should.not.exist(err); 580 | gw.close(); 581 | done(); 582 | }); 583 | }); 584 | }); 585 | 586 | 587 | it('fan medium', function (done) { 588 | var gw = new Insteon(); 589 | 590 | mockHub.mockData = { 591 | '02629999991f11bf020000000000000000000000002e': 592 | [ 593 | '02629999991f11bf020000000000000000000000002e06', 594 | '0250999999ffffff2f11bf' 595 | ] 596 | }; 597 | 598 | gw.connect(host, function () { 599 | gw.light('999999').fanMedium(function (err) { 600 | should.not.exist(err); 601 | gw.close(); 602 | done(); 603 | }); 604 | }); 605 | }); 606 | 607 | 608 | it('fan high', function (done) { 609 | var gw = new Insteon(); 610 | 611 | mockHub.mockData = { 612 | '02629999991f11ff02000000000000000000000000ee': 613 | [ 614 | '02629999991f11ff02000000000000000000000000ee06', 615 | '0250999999ffffff2f11ff' 616 | ] 617 | }; 618 | 619 | gw.connect(host, function () { 620 | gw.light('999999').fanHigh(function (err) { 621 | should.not.exist(err); 622 | gw.close(); 623 | done(); 624 | }); 625 | }); 626 | }); 627 | 628 | it('fan speed', function (done) { 629 | var plan = new Plan(4, function () { 630 | gw.close(); 631 | done(); 632 | }); 633 | var gw = new Insteon(); 634 | 635 | mockHub.mockData = [ 636 | { 637 | '02621111110f1903': '02621111110f1903060250111111ffffff2f1900' 638 | }, 639 | { 640 | '02622222220f1903': '02622222220f1903060250222222ffffff2f195f' 641 | }, 642 | { 643 | '02623333330f1903': '02623333330f1903060250333333ffffff2f19bf' 644 | }, 645 | { 646 | '02624444440f1903': '02624444440f1903060250444444ffffff2f19ff' 647 | }, 648 | ]; 649 | 650 | gw.connect(host, function () { 651 | gw.light('111111').fan(function (err, speed) { 652 | should.not.exist(err); 653 | 654 | speed.should.eql('off'); 655 | 656 | plan.ok(); 657 | }); 658 | gw.light('222222').fan(function (err, speed) { 659 | should.not.exist(err); 660 | 661 | speed.should.eql('low'); 662 | 663 | plan.ok(); 664 | }); 665 | gw.light('333333').fan(function (err, speed) { 666 | should.not.exist(err); 667 | 668 | speed.should.eql('medium'); 669 | 670 | plan.ok(); 671 | }); 672 | gw.light('444444').fan(function (err, speed) { 673 | should.not.exist(err); 674 | 675 | speed.should.eql('high'); 676 | 677 | plan.ok(); 678 | }); 679 | }); 680 | }); 681 | 682 | it('fan speed throws error', function (done) { 683 | var gw = new Insteon(); 684 | 685 | mockHub.mockData = [ 686 | /*{ 687 | '02629999990f1903': '02629999990f1903060250999999ffffff2f19bf' 688 | },*/ 689 | { 690 | '02629999990f1903': '02629999990f190315' 691 | } 692 | ]; 693 | 694 | gw.connect(host, function () { 695 | gw.light('999999').fan(function (err, speed) { 696 | should.not.exist(err); 697 | 698 | should.not.exist(speed); 699 | 700 | gw.close(); 701 | done(); 702 | }); 703 | }); 704 | }); 705 | 706 | // Light Events 707 | 708 | describe('Light Events', function () { 709 | it('emits turnOn event', function (done) { 710 | var plan = new Plan(3, function () { 711 | gw.close(); 712 | done(); 713 | }); 714 | var gw = new Insteon(); 715 | var light = gw.light('19d41c'); 716 | 717 | light.on('command', function (group, cmd1) { 718 | this.id.should.equal('19D41C'); 719 | should.exist(this.turnOff); 720 | group.should.equal(1); 721 | cmd1.should.equal('11'); 722 | plan.ok(); 723 | }); 724 | 725 | light.on('turnOn', function (group) { 726 | this.id.should.equal('19D41C'); 727 | should.exist(this.turnOff); 728 | group.should.equal(1); 729 | plan.ok(); 730 | }); 731 | 732 | gw.connect(host, function () { 733 | setTimeout(function () { // make sure server connection event fires first 734 | mockHub.send([ 735 | '025019d41c000001cb1100', 736 | '025019d41c1eb552451101', 737 | '025019d41c110101cf0600' 738 | ], function () { 739 | plan.ok(); 740 | }); 741 | }, 10); 742 | }); 743 | }); 744 | 745 | it('emits turnOff event', function (done) { 746 | var plan = new Plan(3, function () { 747 | gw.close(); 748 | done(); 749 | }); 750 | var gw = new Insteon(); 751 | var light = gw.light('19d41c'); 752 | 753 | light.on('command', function (group, cmd1) { 754 | group.should.equal(1); 755 | cmd1.should.equal('13'); 756 | plan.ok(); 757 | }); 758 | 759 | light.on('turnOff', function (group) { 760 | group.should.equal(1); 761 | plan.ok(); 762 | }); 763 | 764 | gw.connect(host, function () { 765 | setTimeout(function () { // make sure server connection event fires first 766 | mockHub.send([ 767 | '025019d41c000001cf1300', 768 | '025019d41c1eb552451301', 769 | '025019d41c130101cf0600' 770 | ], function () { 771 | plan.ok(); 772 | }); 773 | }, 10); 774 | }); 775 | }); 776 | 777 | it('emits turnOnFast event', function (done) { 778 | var plan = new Plan(3, function () { 779 | gw.close(); 780 | done(); 781 | }); 782 | var gw = new Insteon(); 783 | var light = gw.light('19d41c'); 784 | 785 | light.on('command', function (group, cmd1) { 786 | group.should.equal(1); 787 | cmd1.should.equal('12'); 788 | plan.ok(); 789 | }); 790 | 791 | light.on('turnOnFast', function (group) { 792 | group.should.equal(1); 793 | plan.ok(); 794 | }); 795 | 796 | gw.connect(host, function () { 797 | setTimeout(function () { // make sure server connection event fires first 798 | mockHub.send([ 799 | '025019d41c000001cf1200', 800 | '025019d41c1eb552451201', 801 | '025019d41c120101cf0600' 802 | ], function () { 803 | plan.ok(); 804 | }); 805 | }, 10); 806 | }); 807 | }); 808 | 809 | it('emits turnOffFast event', function (done) { 810 | var plan = new Plan(3, function () { 811 | gw.close(); 812 | done(); 813 | }); 814 | var gw = new Insteon(); 815 | var light = gw.light('19d41c'); 816 | 817 | light.on('command', function (group, cmd1) { 818 | group.should.equal(1); 819 | cmd1.should.equal('14'); 820 | plan.ok(); 821 | }); 822 | 823 | light.on('turnOffFast', function (group) { 824 | group.should.equal(1); 825 | plan.ok(); 826 | }); 827 | 828 | gw.connect(host, function () { 829 | setTimeout(function () { // make sure server connection event fires first 830 | mockHub.send([ 831 | '025019d41c000001cf1400', 832 | '025019d41c1eb552451401', 833 | '025019d41c1eb5524a1401', 834 | '025019d41c1eb5524f1401', 835 | '025019d41c140101cf0600' 836 | ], function () { 837 | plan.ok(); 838 | }); 839 | }, 10); 840 | }); 841 | }); 842 | 843 | it('emits dim events', function (done) { 844 | var plan = new Plan(5, function () { 845 | gw.close(); 846 | done(); 847 | }); 848 | var gw = new Insteon(); 849 | var light = gw.light('19d41c'); 850 | 851 | mockHub.mockData = 852 | { 853 | '026219d41c0f1600': 854 | [ 855 | '026219d41c0f160006', 856 | '025019d41c000001cf1700', 857 | '025019d41c000001cb1800' 858 | ] 859 | }; 860 | 861 | light.on('command', function (group, cmd1) { 862 | group.should.equal(1); 863 | cmd1.should.match(/1[78]/); 864 | plan.ok(); 865 | }); 866 | 867 | light.on('dimming', function (group) { 868 | group.should.equal(1); 869 | plan.ok(); 870 | }); 871 | 872 | light.on('dimmed', function (group) { 873 | group.should.equal(1); 874 | plan.ok(); 875 | }); 876 | 877 | gw.connect(host, function () { 878 | light.dim(function () { 879 | plan.ok(); 880 | }); 881 | }); 882 | }); 883 | 884 | it('emits brighten events', function (done) { 885 | var plan = new Plan(5, function () { 886 | gw.close(); 887 | done(); 888 | }); 889 | var gw = new Insteon(); 890 | var light = gw.light('19d41c'); 891 | 892 | mockHub.mockData = 893 | { 894 | '026219d41c0f1500': 895 | [ 896 | '026219d41c0f150006', 897 | '025019d41c000001cf1701', 898 | '025019d41c000001cf1800' 899 | ] 900 | }; 901 | 902 | light.on('command', function (group, cmd1, cmd2) { 903 | group.should.equal(1); 904 | cmd1.should.match(/1[78]/); 905 | cmd2.should.match(/0[01]/); 906 | plan.ok(); 907 | }); 908 | 909 | light.on('brightening', function (group) { 910 | group.should.equal(1); 911 | plan.ok(); 912 | }); 913 | 914 | light.on('brightened', function (group) { 915 | group.should.equal(1); 916 | plan.ok(); 917 | }); 918 | 919 | gw.connect(host, function () { 920 | light.brighten(function () { 921 | plan.ok(); 922 | }); 923 | }); 924 | }); 925 | 926 | it('emits heartbeat event', function (done) { 927 | var plan = new Plan(3, function () { 928 | gw.close(); 929 | done(); 930 | }); 931 | var gw = new Insteon(); 932 | var light = gw.light('19d41c'); 933 | 934 | light.on('command', function (group, cmd1) { 935 | group.should.equal(1); 936 | cmd1.should.equal('04'); 937 | plan.ok(); 938 | }); 939 | 940 | light.on('heartbeat', function (level) { 941 | level.should.equal(100); 942 | plan.ok(); 943 | }); 944 | 945 | gw.connect(host, function () { 946 | setTimeout(function () { // make sure server connection event fires first 947 | mockHub.send(['025019d41c000001cf04ff'], function () { 948 | plan.ok(); 949 | }); 950 | }, 10); 951 | }); 952 | }); 953 | 954 | it('does not emit turnOn event from command ACK', function (done) { 955 | var gw = new Insteon(); 956 | var light = gw.light('999999'); 957 | 958 | light.on('command', function () { 959 | done(new Error('command should not be emitted')); 960 | }); 961 | 962 | light.on('turnOn', function () { 963 | done(new Error('command should not be emitted')); 964 | }); 965 | 966 | mockHub.mockData = { 967 | '02629999990f117f': '02629999990f117f060250999999ffffff2f117f' 968 | }; 969 | 970 | gw.connect(host, function () { 971 | light.turnOn(50).then(function () { 972 | gw.close(); 973 | done(); 974 | }) 975 | .catch(function () { 976 | gw.close(); 977 | done(); 978 | }); 979 | }); 980 | }); 981 | 982 | it('emits turnOn event from command ACK', function (done) { 983 | var plan = new Plan(2, function () { 984 | gw.close(); 985 | done(); 986 | }); 987 | var gw = new Insteon(); 988 | gw.emitSelfAck = true; 989 | var light = gw.light('999999'); 990 | 991 | light.on('command', function (group, cmd1) { 992 | should.not.exist(group); 993 | cmd1.should.equal('11'); 994 | plan.ok(); 995 | }); 996 | 997 | light.on('turnOn', function (group, level) { 998 | should.not.exist(group); 999 | level.should.equal(50); 1000 | plan.ok(); 1001 | }); 1002 | 1003 | mockHub.mockData = { 1004 | '02629999990f117f': '02629999990f117f060250999999ffffff2f117f' 1005 | }; 1006 | 1007 | gw.connect(host, function () { 1008 | light.turnOn(50) 1009 | .then(function () { 1010 | plan.ok(); 1011 | }); 1012 | }); 1013 | }); 1014 | 1015 | it('does not emits turnOn with .info', function (done) { 1016 | var gw = new Insteon(); 1017 | var light = gw.light('112233'); 1018 | 1019 | light.on('turnOn', function () { 1020 | done(new Error('no turnOn event')); 1021 | }); 1022 | 1023 | mockHub.mockData = { 1024 | '02621122331f2e0001000000000000000000000000d1': 1025 | [ 1026 | '02621122331f2e0001000000000000000000000000d106', 1027 | '0250112233ffffff2f2e00', 1028 | '0251112233ffffff112e0001010000202018fc7f0000000000' 1029 | ] 1030 | }; 1031 | 1032 | gw.connect(host, function () { 1033 | light.info().then(function (info) { 1034 | should.exist(info); 1035 | gw.close(); 1036 | done(); 1037 | }); 1038 | }); 1039 | }); 1040 | 1041 | 1042 | it('does not emits turnOff when getting links', function (done) { 1043 | var gw = new Insteon(); 1044 | var light = gw.light('999999'); 1045 | 1046 | light.on('turnOff', function () { 1047 | done(new Error('no turnOff event')); 1048 | }); 1049 | 1050 | mockHub.mockData = [{ 1051 | '02629999991f2f0000000fff010000000000000000c2': 1052 | ['02629999991f2f0000000fff010000000000000000c206', 1053 | '0250999999ffffff2f2f00', 1054 | '0251999999ffffff112f0000010fff00aa01ffffff001c01d5'] 1055 | 1056 | }, 1057 | { 1058 | '02629999991f2f0000000ff7010000000000000000ca': 1059 | ['02629999991f2f0000000ff7010000000000000000ca06', 1060 | '0250999999ffffff2f2f00', 1061 | '0251999999ffffff112f0000010ff7000000000000000000ca'] 1062 | }]; 1063 | 1064 | gw.connect(host, function () { 1065 | gw.links('999999', function (err, links) { 1066 | should.not.exist(err); 1067 | should.exist(links); 1068 | links.length.should.eql(1); 1069 | links[0].group.should.eql(1); 1070 | links[0].id.should.eql('ffffff'); 1071 | links[0].controller.should.be.false; 1072 | links[0].isInUse.should.be.true; 1073 | links[0].isLast.should.be.false; 1074 | links[0].at.should.eql(4095); 1075 | gw.close(); 1076 | done(); 1077 | }); 1078 | }); 1079 | }); 1080 | 1081 | it('emits turnOn event from command All-Link ACK', function (done) { 1082 | var plan = new Plan(3, function () { 1083 | gw.close(); 1084 | done(); 1085 | }); 1086 | var gw = new Insteon(); 1087 | var light = gw.light('aabbcc'); 1088 | 1089 | light.on('command', function (group, cmd1) { 1090 | should.exist(group); 1091 | group.should.equal(25); 1092 | cmd1.should.equal('11'); 1093 | plan.ok(); 1094 | }); 1095 | 1096 | light.on('turnOn', function (group) { 1097 | should.exist(group); 1098 | group.should.equal(25); 1099 | plan.ok(); 1100 | }); 1101 | 1102 | mockHub.mockData = { 1103 | '0261191100': 1104 | [ 1105 | '026119110006', 1106 | '0250aabbccffffff611119', 1107 | '025806' 1108 | ] 1109 | }; 1110 | 1111 | gw.connect(host, function () { 1112 | gw.sceneOn(25) 1113 | .then(function (report) { 1114 | should.exist(report); 1115 | plan.ok(); 1116 | }); 1117 | }); 1118 | }); 1119 | 1120 | it('emits turnOnFast event from command ACK', function (done) { 1121 | var plan = new Plan(3, function () { 1122 | gw.close(); 1123 | done(); 1124 | }); 1125 | var gw = new Insteon(); 1126 | gw.emitSelfAck = true; 1127 | var light = gw.light('999999'); 1128 | 1129 | light.on('command', function (group, cmd1) { 1130 | should.not.exist(group); 1131 | cmd1.should.equal('12'); 1132 | plan.ok(); 1133 | }); 1134 | 1135 | light.on('turnOnFast', function (group, level) { 1136 | should.not.exist(group); 1137 | should.not.exist(level); 1138 | plan.ok(); 1139 | }); 1140 | 1141 | mockHub.mockData = { 1142 | '02629999990f1200': '02629999990f1200060250999999ffffff2f1200' 1143 | }; 1144 | 1145 | gw.connect(host, function () { 1146 | light.turnOnFast(function () { 1147 | plan.ok(); 1148 | }); 1149 | }); 1150 | }); 1151 | 1152 | it('emits turnOff event from command ACK', function (done) { 1153 | var plan = new Plan(2, function () { 1154 | gw.close(); 1155 | done(); 1156 | }); 1157 | var gw = new Insteon(); 1158 | gw.emitSelfAck = true; 1159 | var light = gw.light('999999'); 1160 | 1161 | light.on('command', function (group, cmd1) { 1162 | should.not.exist(group); 1163 | cmd1.should.equal('13'); 1164 | plan.ok(); 1165 | }); 1166 | 1167 | light.on('turnOnFast', function (group) { 1168 | should.not.exist(group); 1169 | plan.ok(); 1170 | }); 1171 | 1172 | mockHub.mockData = { 1173 | '02629999990f1300': '02629999990f1300060250999999ffffff2f1300' 1174 | }; 1175 | 1176 | gw.connect(host, function () { 1177 | light.turnOff() 1178 | .then(function () { 1179 | plan.ok(); 1180 | }); 1181 | }); 1182 | }); 1183 | 1184 | it('emits turnOn at ramp level event from command ACK', function (done) { 1185 | var plan = new Plan(3, function () { 1186 | gw.close(); 1187 | done(); 1188 | }); 1189 | var gw = new Insteon(); 1190 | gw.emitSelfAck = true; 1191 | var light = gw.light('999999'); 1192 | 1193 | light.on('command', function (group, cmd1) { 1194 | should.not.exist(group); 1195 | cmd1.should.equal('2e'); 1196 | plan.ok(); 1197 | }); 1198 | 1199 | light.on('turnOn', function (group, level) { 1200 | should.not.exist(group); 1201 | level.should.equal(40); 1202 | plan.ok(); 1203 | }); 1204 | 1205 | gw.connect(host, function () { 1206 | setTimeout(function () { 1207 | mockHub.send(['0250999999ffffff2f2e6f'], function () { 1208 | plan.ok(); 1209 | }); 1210 | }, 10); 1211 | }); 1212 | }); 1213 | 1214 | it('emits invalid command', function (done) { 1215 | var plan = new Plan(2, function () { 1216 | gw.close(); 1217 | done(); 1218 | }); 1219 | var gw = new Insteon(); 1220 | var light = gw.light('19d41c'); 1221 | 1222 | light.on('command', function (group, cmd1) { 1223 | group.should.equal(1); 1224 | cmd1.should.equal('ff'); 1225 | plan.ok(); 1226 | }); 1227 | 1228 | gw.connect(host, function () { 1229 | setTimeout(function () { // make sure server connection event fires first 1230 | mockHub.send(['025019d41c000001cfffff'], function () { 1231 | plan.ok(); 1232 | }); 1233 | }, 10); 1234 | }); 1235 | }); 1236 | 1237 | it('does not emit event from command ACK', function (done) { 1238 | var plan = new Plan(2, function () { 1239 | gw.close(); 1240 | done(); 1241 | }); 1242 | var gw = new Insteon(); 1243 | var light = gw.light('999999'); 1244 | light.emitOnAck = false; 1245 | 1246 | light.on('command', function () { 1247 | throw new Error('This event should have been suppressed.'); 1248 | }); 1249 | 1250 | light.on('turnOn', function () { 1251 | throw new Error('This event should have been suppressed.'); 1252 | }); 1253 | 1254 | setTimeout(function () { 1255 | plan.ok(); 1256 | }, 200); 1257 | 1258 | gw.connect(host, function () { 1259 | setTimeout(function () { 1260 | mockHub.send(['0250999999ffffff2f11ff'], function () { 1261 | plan.ok(); 1262 | }); 1263 | }, 10); 1264 | }); 1265 | }); 1266 | 1267 | it('cancels pending', function (done) { 1268 | var gw = new Insteon(); 1269 | 1270 | gw.connect(host, function () { 1271 | var light = gw.light('999999'); 1272 | var plan = new Plan(3, function () { 1273 | gw.close(); 1274 | done(); 1275 | }); 1276 | 1277 | light.turnOn().then(function () { 1278 | plan.ok(); 1279 | }); 1280 | 1281 | light.turnOff().then(function () { 1282 | throw new Error('This command should have been canceled.'); 1283 | }).fail(function (err) { 1284 | should.exist(err); 1285 | err.message.should.equal('Canceled'); 1286 | plan.ok(); 1287 | 1288 | setTimeout(function () { 1289 | mockHub.send([ 1290 | '02629999990f11ff06', 1291 | '0250999999ffffff2f11ff' 1292 | ], function () { 1293 | plan.ok(); 1294 | }); 1295 | }, 10); 1296 | }); 1297 | 1298 | setTimeout(function () { 1299 | light.cancelPending(); 1300 | }, 100); 1301 | }); 1302 | }); 1303 | 1304 | }); 1305 | 1306 | }); 1307 | 1308 | -------------------------------------------------------------------------------- /test/Meter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | 6 | var mockHub = require('../lib/Test/mockHub'); 7 | 8 | var host = '127.0.0.1'; 9 | var port = 9761; 10 | 11 | describe('Meter Commands', function () { 12 | this.timeout(5000); 13 | 14 | before(function (done) { 15 | mockHub.listen(port, host, function () { 16 | done(); 17 | }); 18 | }); 19 | 20 | after(function (done) { 21 | mockHub.close(function() { 22 | done(); 23 | }); 24 | }); 25 | 26 | it('get status', function (done) { 27 | var gw = new Insteon(); 28 | var meter = gw.meter('1987b7'); 29 | 30 | mockHub.mockData = { 31 | '02621987b70f8200': 32 | [ 33 | '02621987b70f820006', 34 | '02501987b71eb5522f8200', 35 | '02511987b71eb5521b82000002010d009100030000002a74d8' 36 | ] 37 | }; 38 | 39 | gw.connect(host, function () { 40 | meter.status() 41 | .then(function (status) { 42 | should.exist(status); 43 | status.should.eql({ energy: 0.012742916666666666, power: 3 }); 44 | gw.close(); 45 | done(); 46 | }) 47 | .catch(function() { 48 | gw.close(); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | 54 | it('get status and reset', function (done) { 55 | var gw = new Insteon(); 56 | var meter = gw.meter('1987b7'); 57 | 58 | mockHub.mockData = { 59 | '02621987b70f8200': 60 | [ 61 | '02621987b70f820006', 62 | '02501987b71eb5522f8200', 63 | '02511987b71eb5521b820000020110008100020000006fa345' 64 | ], 65 | '02621987b70f8000': [ 66 | '02621987b70f800006', 67 | '02501987b71eb5522f8000' 68 | ] 69 | }; 70 | 71 | gw.connect(host, function () { 72 | meter.status() 73 | .then(function (status) { 74 | should.exist(status); 75 | status.should.eql({ energy: 0.033677708333333334, power: 2 }); 76 | gw.close(); 77 | done(); 78 | }) 79 | .catch(function() { 80 | gw.close(); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | }); // Meter Functions 86 | 87 | -------------------------------------------------------------------------------- /test/Motion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | 6 | var mockHub = require('../lib/Test/mockHub'); 7 | var Plan = require('../lib/Test/Plan'); 8 | 9 | var host = '127.0.0.1'; 10 | var port = 9761; 11 | 12 | describe('Motion Commands', function () { 13 | this.timeout(5000); 14 | 15 | before(function (done) { 16 | mockHub.listen(port, host, function () { 17 | done(); 18 | }); 19 | }); 20 | 21 | after(function (done) { 22 | mockHub.close(function () { 23 | done(); 24 | }); 25 | }); 26 | 27 | it('get status', function (done) { 28 | var gw = new Insteon(); 29 | var motion = gw.motion('283e9e'); 30 | var plan = new Plan(2, function () { 31 | gw.close(); 32 | done(); 33 | }); 34 | 35 | mockHub.mockData = { 36 | '0262283e9e1f2e0000000000000000000000000000d2': 37 | [ 38 | '0262283e9e1f2e0000000000000000000000000000d206', 39 | '0250283e9e1eb5522f2e00', 40 | '0251283e9e1eb5521b2e0001016401800e00450e00d35f00d2' 41 | ] 42 | }; 43 | 44 | gw.connect(host, function () { 45 | motion.status() 46 | .then(function (status) { 47 | should.exist(status); 48 | status.should.eql({ 49 | 'ledLevel': 100, 50 | 'clearTimer': 60, 51 | 'duskThreshold': 50, 52 | 'options': { 53 | 'occupancyMode': false, 54 | 'ledOn': true, 55 | 'nightMode': false, 56 | 'onOnlyMode': false 57 | }, 58 | 'jumpers': { 59 | 'j2': false, 60 | 'j3': false, 61 | 'j4': false, 62 | 'j5': true 63 | }, 64 | 'lightLevel': 82, 65 | 'batteryLevel': 9.5 66 | }); 67 | plan.ok(); 68 | }) 69 | .catch(function () { 70 | gw.close(); 71 | done(); 72 | }); 73 | 74 | setTimeout(function () { // make sure server connection event fires first 75 | mockHub.send([ 76 | '0250283e9e000001cf1101' 77 | ], function () { 78 | plan.ok(); 79 | }); 80 | }, 10); 81 | }); 82 | }); 83 | 84 | it('set options', function (done) { 85 | var gw = new Insteon(); 86 | var motion = gw.motion('283e9e'); 87 | 88 | var plan = new Plan(2, function () { 89 | gw.close(); 90 | done(); 91 | }); 92 | 93 | mockHub.mockData = { 94 | '0262283e9e1f2e0000050e00000000000000000000bf': 95 | [ 96 | '0262283e9e1f2e0000050e00000000000000000000bf06', 97 | '0250283e9e1eb5522f2e00' 98 | ] 99 | }; 100 | 101 | gw.connect(host, function () { 102 | motion.options() 103 | .then(function (rsp) { 104 | should.exist(rsp); 105 | rsp.success.should.be.true; 106 | plan.ok(); 107 | }) 108 | .catch(function () { 109 | gw.close(); 110 | done(); 111 | }); 112 | 113 | setTimeout(function () { // make sure server connection event fires first 114 | mockHub.send([ 115 | '0250283e9e000001cf1101' 116 | ], function () { 117 | plan.ok(); 118 | }); 119 | }, 10); 120 | }); 121 | }); 122 | it('set clearTimer', function (done) { 123 | var gw = new Insteon(); 124 | var motion = gw.motion('283e9e'); 125 | 126 | var plan = new Plan(2, function () { 127 | gw.close(); 128 | done(); 129 | }); 130 | 131 | mockHub.mockData = { 132 | '0262283e9e1f2e0000030300000000000000000000cc': 133 | [ 134 | '0262283e9e1f2e0000030300000000000000000000cc06', 135 | '0250283e9e1eb5522f2e00' 136 | ] 137 | }; 138 | 139 | gw.connect(host, function () { 140 | motion.clearTimer(120) 141 | .then(function (rsp) { 142 | should.exist(rsp); 143 | rsp.success.should.be.true; 144 | plan.ok(); 145 | }) 146 | .catch(function () { 147 | gw.close(); 148 | done(); 149 | }); 150 | 151 | setTimeout(function () { // make sure server connection event fires first 152 | mockHub.send([ 153 | '0250283e9e000001cf1101' 154 | ], function () { 155 | plan.ok(); 156 | }); 157 | }, 10); 158 | }); 159 | }); 160 | it('set duskThreshold', function (done) { 161 | var gw = new Insteon(); 162 | var motion = gw.motion('283e9e'); 163 | 164 | var plan = new Plan(2, function () { 165 | gw.close(); 166 | done(); 167 | }); 168 | 169 | mockHub.mockData = { 170 | '0262283e9e1f2e00000480000000000000000000004e': 171 | [ 172 | '0262283e9e1f2e00000480000000000000000000004e06', 173 | '0250283e9e1eb5522f2e00' 174 | ] 175 | }; 176 | 177 | gw.connect(host, function () { 178 | motion.duskThreshold(50) 179 | .then(function (rsp) { 180 | should.exist(rsp); 181 | rsp.success.should.be.true; 182 | plan.ok(); 183 | }) 184 | .catch(function () { 185 | gw.close(); 186 | done(); 187 | }); 188 | 189 | setTimeout(function () { // make sure server connection event fires first 190 | mockHub.send([ 191 | '0250283e9e000001cf1101' 192 | ], function () { 193 | plan.ok(); 194 | }); 195 | }, 10); 196 | }); 197 | }); 198 | 199 | describe('Motion Events', function () { 200 | it('emits motion event', function (done) { 201 | var plan = new Plan(3, function () { 202 | gw.close(); 203 | done(); 204 | }); 205 | var gw = new Insteon(); 206 | var motion = gw.motion('283e9e'); 207 | 208 | motion.on('command', function (group, cmd1) { 209 | group.should.equal(1); 210 | cmd1.should.equal('11'); 211 | plan.ok(); 212 | }); 213 | 214 | motion.on('motion', function () { 215 | plan.ok(); 216 | }); 217 | 218 | gw.connect(host, function () { 219 | setTimeout(function () { // make sure server connection event fires first 220 | mockHub.send([ 221 | '0250283e9e000001cf1101', 222 | '0250283e9e1eb552411101', 223 | '0250283e9e1eb5524a1101', 224 | '0250283e9e110101cf0600', 225 | '0250283e9e110101cf0600' 226 | ], function () { 227 | plan.ok(); 228 | }); 229 | }, 10); 230 | }); 231 | }); 232 | 233 | it('emits clear event', function (done) { 234 | var plan = new Plan(3, function () { 235 | gw.close(); 236 | done(); 237 | }); 238 | var gw = new Insteon(); 239 | var motion = gw.motion('283e9e'); 240 | 241 | motion.on('command', function (group, cmd1) { 242 | group.should.equal(1); 243 | cmd1.should.equal('13'); 244 | plan.ok(); 245 | }); 246 | 247 | motion.on('clear', function () { 248 | plan.ok(); 249 | }); 250 | 251 | gw.connect(host, function () { 252 | setTimeout(function () { // make sure server connection event fires first 253 | mockHub.send([ 254 | '0250283e9e000001cf1301', 255 | '0250283e9e000001cf1301', 256 | '0250283e9e1eb552451301', 257 | '0250283e9e130101cf0600', 258 | '0250283e9e130101cf0600' 259 | ], function () { 260 | plan.ok(); 261 | }); 262 | }, 10); 263 | }); 264 | }); 265 | }); // Motion Events 266 | 267 | }); 268 | 269 | -------------------------------------------------------------------------------- /test/Thermostat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var should = require('should'); 5 | 6 | var mockHub = require('../lib/Test/mockHub'); 7 | var Plan = require('../lib/Test/Plan'); 8 | 9 | var host = '127.0.0.1'; 10 | var port = 9761; 11 | 12 | describe('Thermostat (commands)', function () { 13 | this.timeout(5000); 14 | 15 | before(function (done) { 16 | mockHub.listen(port, host, function () { 17 | done(); 18 | }); 19 | }); 20 | 21 | after(function (done) { 22 | mockHub.close(function () { 23 | done(); 24 | }); 25 | }); 26 | 27 | it('gets temp', function (done) { 28 | var gw = new Insteon(); 29 | 30 | mockHub.mockData = [ 31 | { 32 | '02622926380f6a00': 33 | [ 34 | '02622926380f6a0006', 35 | '02502926381eb5522f6a91' 36 | ] 37 | }]; 38 | 39 | gw.connect(host, function () { 40 | gw.thermostat('292638').temp(function (err, temp) { 41 | should.not.exist(err); 42 | temp.should.eql(72.5); 43 | gw.close(); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | 49 | it('gets temp for zone', function (done) { 50 | var gw = new Insteon(); 51 | 52 | mockHub.mockData = [ 53 | { 54 | '02622926380f6a01': 55 | [ 56 | '02622926380f6a0106', 57 | '02502926381eb5522f6a91' 58 | ] 59 | }]; 60 | 61 | gw.connect(host, function () { 62 | gw.thermostat('292638').temp(1, function (err, temp) { 63 | should.not.exist(err); 64 | temp.should.eql(72.5); 65 | gw.close(); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | 71 | it('get humidity', function (done) { 72 | var gw = new Insteon(); 73 | 74 | mockHub.mockData = [ 75 | { 76 | '02622926380f6a60': 77 | [ 78 | '02622926380f6a6006', 79 | '02502926381eb5522f6a30' 80 | ] 81 | }]; 82 | 83 | gw.connect(host, function () { 84 | gw.thermostat('292638').humidity(function (err, humidity) { 85 | should.not.exist(err); 86 | humidity.should.eql(48); 87 | gw.close(); 88 | done(); 89 | }); 90 | }); 91 | }); 92 | 93 | it('get setpoints', function (done) { 94 | var gw = new Insteon(); 95 | 96 | mockHub.mockData = [ 97 | { 98 | '02622926380f6a20': 99 | [ 100 | '02622926380f6a2006', 101 | '02502926381eb5522f6a8a', 102 | '02502926381eb5520f6a94' 103 | ] 104 | }]; 105 | 106 | gw.connect(host, function () { 107 | gw.thermostat('292638').setpoints(function (err, setpoints) { 108 | should.not.exist(err); 109 | setpoints.should.containEql(69); 110 | setpoints.should.containEql(74); 111 | gw.close(); 112 | done(); 113 | }); 114 | }); 115 | }); 116 | 117 | it('gets setpoints for zone', function (done) { 118 | var gw = new Insteon(); 119 | 120 | mockHub.mockData = [ 121 | { 122 | '02622926380f6a21': 123 | [ 124 | '02622926380f6a2106', 125 | '02502926381eb5522f6a8a', 126 | '02502926381eb5520f6a94' 127 | ] 128 | }]; 129 | 130 | gw.connect(host, function () { 131 | gw.thermostat('292638').setpoints(1, function (err, setpoints) { 132 | should.not.exist(err); 133 | setpoints.should.containEql(69); 134 | setpoints.should.containEql(74); 135 | gw.close(); 136 | done(); 137 | }); 138 | }); 139 | }); 140 | 141 | it('gets mode', function (done) { 142 | var gw = new Insteon(); 143 | 144 | mockHub.mockData = [ 145 | { 146 | '02622926380f6b02': 147 | [ 148 | '02622926380f6b0206', 149 | '02502926381eb5522f6b03' 150 | ] 151 | }]; 152 | 153 | gw.connect(host, function () { 154 | gw.thermostat('292638').mode(function (err, mode) { 155 | should.not.exist(err); 156 | mode.should.eql('auto'); 157 | gw.close(); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | 163 | it('sets mode', function (done) { 164 | var gw = new Insteon(); 165 | 166 | mockHub.mockData = 167 | { 168 | '02622926381f6b06000000000000000000000000008f': 169 | [ 170 | '02622926381f6b06000000000000000000000000008f06' 171 | ] 172 | }; 173 | 174 | gw.connect(host, function () { 175 | gw.thermostat('292638').mode('auto', function (err, mode) { 176 | should.not.exist(err); 177 | mode.should.eql('auto'); 178 | gw.close(); 179 | done(); 180 | }); 181 | }); 182 | }); 183 | 184 | it('sets invalid mode', function (done) { 185 | var gw = new Insteon(); 186 | 187 | mockHub.mockData = 188 | { 189 | '02622926381f6b06000000000000000000000000008f': 190 | [ 191 | '02622926381f6b06000000000000000000000000008f06', 192 | '02502926381eb5522f6b03' 193 | ] 194 | }; 195 | 196 | gw.connect(host, function () { 197 | gw.thermostat('292638').mode('automatic', function (err, mode) { 198 | should.exist(err); 199 | should.not.exist(mode); 200 | gw.close(); 201 | done(); 202 | }); 203 | }); 204 | }); 205 | 206 | it('gets error when setting mode', function (done) { 207 | var gw = new Insteon(); 208 | 209 | mockHub.mockData = 210 | { 211 | '02622926381f6b06000000000000000000000000008f': 212 | [ 213 | '02622926381f6b06000000000000000000000000008f15' 214 | //'02502926381eb5522f6b03' 215 | ] 216 | }; 217 | 218 | gw.connect(host, function () { 219 | gw.thermostat('292638').mode('auto', function (err, mode) { 220 | should.not.exist(err); 221 | should.equal(mode, null); 222 | gw.close(); 223 | done(); 224 | }); 225 | }); 226 | }); 227 | 228 | it('turn temp up', function (done) { 229 | var plan = new Plan(3, function () { 230 | gw.close(); 231 | done(); 232 | }); 233 | var gw = new Insteon(); 234 | 235 | mockHub.mockData = [ 236 | { 237 | '02622926381f68020000000000000000000000000096': 238 | [ 239 | '02622926381f6802000000000000000000000000009606', 240 | '02502926381eb5522f6802' 241 | ] 242 | }, 243 | { 244 | '02622926381f68040000000000000000000000000094': 245 | [ 246 | '02622926381f6804000000000000000000000000009406', 247 | '02502926381eb5522f6804' 248 | ] 249 | }, 250 | { 251 | '02622926381f68020000000000000000000000000096': 252 | [ 253 | '02622926381f6802000000000000000000000000009606', 254 | '02502926381eb5522f6802' 255 | ] 256 | }]; 257 | 258 | gw.connect(host, function () { 259 | var thermostat = gw.thermostat('292638'); 260 | thermostat.tempUp() 261 | .then(function (status) { 262 | should.exist(status.response.standard); 263 | plan.ok(); 264 | }); 265 | thermostat.tempUp(2) 266 | .then(function (status) { 267 | should.exist(status.response.standard); 268 | plan.ok(); 269 | }); 270 | thermostat.tempUp(function (err, status) { 271 | should.not.exist(err); 272 | should.exist(status.response.standard); 273 | plan.ok(); 274 | }); 275 | }); 276 | }); 277 | 278 | it('turn temp down', function (done) { 279 | var gw = new Insteon(); 280 | var plan = new Plan(3, function () { 281 | gw.close(); 282 | done(); 283 | }); 284 | 285 | mockHub.mockData = [ 286 | { 287 | '02622926381f69020000000000000000000000000095': 288 | [ 289 | '02622926381f6902000000000000000000000000009506', 290 | '02502926381eb5522f6902' 291 | ] 292 | }, 293 | { 294 | '02622926381f69040000000000000000000000000093': 295 | [ 296 | '02622926381f6904000000000000000000000000009306', 297 | '02502926381eb5522f6904' 298 | ] 299 | }, 300 | { 301 | '02622926381f69020000000000000000000000000095': 302 | [ 303 | '02622926381f6902000000000000000000000000009506', 304 | '02502926381eb5522f6902' 305 | ] 306 | }]; 307 | 308 | gw.connect(host, function () { 309 | var thermostat = gw.thermostat('292638'); 310 | 311 | thermostat.tempDown() 312 | .then(function (status) { 313 | should.exist(status.response.standard); 314 | plan.ok(); 315 | }); 316 | thermostat.tempDown(2) 317 | .then(function (status) { 318 | should.exist(status.response.standard); 319 | plan.ok(); 320 | }); 321 | thermostat.tempDown(function (err, status) { 322 | should.not.exist(err); 323 | should.exist(status.response.standard); 324 | plan.ok(); 325 | }); 326 | 327 | }); 328 | }); 329 | 330 | it('set heat temp', function (done) { 331 | var gw = new Insteon(); 332 | 333 | mockHub.mockData = [ 334 | { 335 | '02622926381f6d88000000000000000000000000000b': 336 | [ 337 | '02', 338 | '622926381f6d88000000000000000000000000000b06', 339 | '02502926381eb5522f6d88' 340 | ] 341 | }]; 342 | 343 | gw.connect(host, function () { 344 | gw.thermostat('292638') 345 | .heatTemp(68) 346 | .then(function (status) { 347 | should.exist(status.response.standard); 348 | }) 349 | .then(function () { 350 | gw.close(); 351 | done(); 352 | }, function () { 353 | gw.close(); 354 | done(); 355 | }); 356 | }); 357 | }); 358 | 359 | it('set cool temp', function (done) { 360 | var gw = new Insteon(); 361 | 362 | mockHub.mockData = [ 363 | { 364 | '02622926381f6c920000000000000000000000000002': 365 | [ 366 | '02', 367 | '622926381f6c9200000000000000000000000000', 368 | '0206', 369 | '02502926381eb5522f6c92' 370 | ] 371 | }]; 372 | 373 | gw.connect(host, function () { 374 | gw.thermostat('292638') 375 | .coolTemp(73) 376 | .then(function (status) { 377 | should.exist(status.response.standard); 378 | }) 379 | .then(function () { 380 | gw.close(); 381 | done(); 382 | }, function () { 383 | gw.close(); 384 | done(); 385 | }); 386 | }); 387 | }); 388 | 389 | it('set high humidity', function (done) { 390 | var gw = new Insteon(); 391 | 392 | mockHub.mockData = [ 393 | { 394 | '02622926381f2e00010b460000000000000000000080': 395 | [ 396 | '02622926381f2e00010b46000000000000000000008006' 397 | ] 398 | }]; 399 | 400 | gw.connect(host, function () { 401 | gw.thermostat('292638') 402 | .highHumidity(70) 403 | .then(function (status) { 404 | status.ack.should.be.true; 405 | }) 406 | .then(function () { 407 | gw.close(); 408 | done(); 409 | }, function () { 410 | gw.close(); 411 | done(); 412 | }); 413 | }); 414 | }); 415 | 416 | it('set low humidity', function (done) { 417 | var gw = new Insteon(); 418 | 419 | mockHub.mockData = [ 420 | { 421 | '02622926381f2e00010c2300000000000000000000a2': 422 | [ 423 | '0262', 424 | '2926381f2e00010c2300000000000000000000a206' 425 | ] 426 | }]; 427 | 428 | gw.connect(host, function () { 429 | gw.thermostat('292638') 430 | .lowHumidity(35) 431 | .then(function (status) { 432 | status.ack.should.be.true; 433 | }) 434 | .then(function () { 435 | gw.close(); 436 | done(); 437 | }, function () { 438 | gw.close(); 439 | done(); 440 | }); 441 | }); 442 | }); 443 | 444 | it('set backlight', function (done) { 445 | var gw = new Insteon(); 446 | 447 | mockHub.mockData = [ 448 | { 449 | '02622926381f2e0001051e00000000000000000000ae': 450 | [ 451 | '02622926381f2e0001051e00000000000000000000ae06' 452 | ] 453 | }]; 454 | 455 | gw.connect(host, function () { 456 | gw.thermostat('292638') 457 | .backlight(30) 458 | .then(function (status) { 459 | status.ack.should.be.true; 460 | }) 461 | .then(function () { 462 | gw.close(); 463 | done(); 464 | }, function () { 465 | gw.close(); 466 | done(); 467 | }); 468 | }); 469 | }); 470 | 471 | it('set cycle delay', function (done) { 472 | var gw = new Insteon(); 473 | 474 | mockHub.mockData = [ 475 | { 476 | '02622926381f2e0001060600000000000000000000c5': 477 | [ 478 | '02622926381f2e0001060600000000000000000000c506' 479 | ] 480 | }]; 481 | 482 | gw.connect(host, function () { 483 | gw.thermostat('292638') 484 | .cycleDelay(6) 485 | .then(function (status) { 486 | status.ack.should.be.true; 487 | }) 488 | .then(function () { 489 | gw.close(); 490 | done(); 491 | }, function () { 492 | gw.close(); 493 | done(); 494 | }); 495 | }); 496 | }); 497 | 498 | it('set energy mode change', function (done) { 499 | var gw = new Insteon(); 500 | 501 | mockHub.mockData = [ 502 | { 503 | '02622926381f2e0001070500000000000000000000c5': 504 | [ 505 | '02622926381f2e0001070500000000000000000000', 506 | 'c506' 507 | ] 508 | }]; 509 | 510 | gw.connect(host, function () { 511 | gw.thermostat('292638') 512 | .energyChange(5) 513 | .then(function (status) { 514 | status.ack.should.be.true; 515 | }) 516 | .then(function () { 517 | gw.close(); 518 | done(); 519 | }, function () { 520 | gw.close(); 521 | done(); 522 | }); 523 | }); 524 | }); 525 | 526 | it('set date (day, hour, min, sec)', function (done) { 527 | var gw = new Insteon(); 528 | 529 | mockHub.mockData = [ 530 | { 531 | '02622926381f2e0202020e271b00000000000000a500': 532 | [ 533 | '02622926381f2e0202020e271b00000000000000a50006' 534 | ] 535 | }]; 536 | 537 | gw.connect(host, function () { 538 | gw.thermostat('292638') 539 | .date(new Date(2014, 4, 6, 14, 39, 27, 0)) 540 | .then(function (status) { 541 | status.ack.should.be.true; 542 | }) 543 | .then(function () { 544 | gw.close(); 545 | done(); 546 | }, function () { 547 | gw.close(); 548 | done(); 549 | }); 550 | }); 551 | }); 552 | 553 | it('sets today\'s date', function (done) { 554 | var gw = new Insteon(); 555 | 556 | gw.connect(host, function () { 557 | gw.thermostat('112233') 558 | .date(function (err, status) { 559 | status.ack.should.be.true; 560 | gw.close(); 561 | done(); 562 | }); 563 | 564 | setTimeout(function () { 565 | mockHub.send(['02621122331f2e020204103b3a00000000000000d4c606']); 566 | }, 10); 567 | }); 568 | }); 569 | 570 | it('sets date (string)', function (done) { 571 | var gw = new Insteon(); 572 | 573 | mockHub.mockData = [ 574 | { 575 | '02622926381f2e0202020e271b00000000000000a500': 576 | [ 577 | '02622926381f2e0202020e271b00000000000000a50006' 578 | ] 579 | }]; 580 | 581 | gw.connect(host, function () { 582 | gw.thermostat('292638') 583 | .date('2014-05-06 14:39:27') 584 | .then(function (status) { 585 | status.ack.should.be.true; 586 | }) 587 | .then(function () { 588 | gw.close(); 589 | done(); 590 | }, function () { 591 | gw.close(); 592 | done(); 593 | }); 594 | }); 595 | }); 596 | 597 | it('get status - invalid crc', function (done) { 598 | var gw = new Insteon(); 599 | 600 | mockHub.mockData = [ 601 | { 602 | '02622926381f2e020000000000000000000000009296': 603 | [ 604 | '02622926381f2e02000000000000000000000000929606', 605 | '02502926381eb5522f2e02', 606 | '02512926381eb552112e0201000d0810503b00b9803c331202' 607 | ] 608 | }]; 609 | 610 | gw.connect(host, function () { 611 | gw.thermostat('292638') 612 | .status() 613 | .then(function (details) { 614 | should.not.exist(details); 615 | }) 616 | .then(function () { 617 | gw.close(); 618 | done(); 619 | }, function () { 620 | gw.close(); 621 | done(); 622 | }); 623 | }); 624 | }); 625 | 626 | it('get status - valid crc', function (done) { 627 | var gw = new Insteon(); 628 | 629 | mockHub.mockData = [ 630 | { 631 | '02622926381f2e020000000000000000000000009296': 632 | [ 633 | '02622926381f2e02000000000000000000000000929606', 634 | '02502926381eb5522f2e02', 635 | '02512926381eb552112e0201000c262c10503b00b9803c4674' 636 | ] 637 | }]; 638 | 639 | gw.connect(host, function () { 640 | gw.thermostat('292638') 641 | .status() 642 | .then(function (details) { 643 | should.exist(details); 644 | details.mode.should.eql('auto'); 645 | details.fan.should.be.false; 646 | details.date.should.eql({ day: 0, hour: 12, minute: 38, seconds: 44 }); 647 | details.setpoints.should.eql({ 648 | cool: 80, 649 | heat: 60 650 | }); 651 | details.humidity.should.eql(59); 652 | details.temperature.should.eql(65.3); 653 | details.cooling.should.be.false; 654 | details.heating.should.be.false; 655 | details.energySaving.should.be.false; 656 | details.hold.should.be.false; 657 | details.unit.should.eql('F'); 658 | }) 659 | .then(function () { 660 | gw.close(); 661 | done(); 662 | }, function () { 663 | gw.close(); 664 | done(); 665 | }); 666 | }); 667 | }); 668 | 669 | it('get status - Celcius temp', function (done) { 670 | var gw = new Insteon(); 671 | 672 | mockHub.mockData = [ 673 | { 674 | '02622926381f2e020000000000000000000000009296': 675 | [ 676 | '02622926381f2e02000000000000000000000000929606', 677 | '02502926381eb5522f2e02', 678 | '02512926381eb552112e0201000c262c10503b00b9883c5765' 679 | ] 680 | }]; 681 | 682 | gw.connect(host, function () { 683 | gw.thermostat('292638') 684 | .status() 685 | .then(function (details) { 686 | should.exist(details); 687 | details.unit.should.eql('C'); 688 | }) 689 | .then(function () { 690 | gw.close(); 691 | done(); 692 | }, function () { 693 | gw.close(); 694 | done(); 695 | }); 696 | }); 697 | }); 698 | 699 | it('gets details', function (done) { 700 | this.slow(500); 701 | var gw = new Insteon(); 702 | 703 | mockHub.mockData = [ 704 | { 705 | '02622926381f2e020000000000000000000000009296': 706 | [ 707 | '02622926381f2e02000000000000000000000000929606', 708 | '02502926381eb5522f2e02', 709 | '02502926381eb5522f2e02', 710 | '02512926381eb552112e02010209232710493900e0804451b1' 711 | ] 712 | }, 713 | { 714 | '02622926381f2e00000000000000000000000000636b': 715 | [ 716 | '02622926381f2e00000000000000000000000000636b06', 717 | '02502926381eb5522f2e00', 718 | '02512926381eb552112e00000100e03901ff01001e06050000' 719 | ] 720 | }, 721 | { 722 | '02622926381f2e000000010000000000000000009f3a': 723 | [ 724 | '026229', 725 | '26381f2e000000010000000000000000009f3a06', 726 | '02502926381eb5522f2e00', 727 | '02512926381eb552112e0000010146230d494401000100d0f7' 728 | ] 729 | }]; 730 | 731 | gw.connect(host, function () { 732 | gw.thermostat('292638') 733 | .details() 734 | .then(function (details) { 735 | details.mode.should.eql('auto'); 736 | details.fan.should.be.false; 737 | details.date.should.eql({ day: 2, hour: 9, minute: 35, seconds: 39 }); 738 | details.setpoints.should.eql({ 739 | cool: 73, 740 | heat: 68, 741 | highHumidity: 70, 742 | lowHumidity: 35 743 | }); 744 | 745 | details.humidity.should.eql(57); 746 | details.temperature.should.eql(72.32); 747 | details.cooling.should.be.false; 748 | details.heating.should.be.false; 749 | details.energySaving.should.be.false; 750 | details.hold.should.be.false; 751 | details.unit.should.eql('F'); 752 | details.backlight.should.eql(30); 753 | details.delay.should.eql(6); 754 | details.energyOffset.should.eql(5); 755 | 756 | }) 757 | .then(function () { 758 | gw.close(); 759 | done(); 760 | }, function () { 761 | gw.close(); 762 | done(); 763 | }); 764 | }); 765 | }); 766 | 767 | it('gets error getting details #1', function (done) { 768 | var gw = new Insteon(); 769 | 770 | mockHub.mockData = [ 771 | { 772 | '02622926381f2e020000000000000000000000009296': 773 | [ 774 | '02622926381f2e02000000000000000000000000929615' 775 | ] 776 | }, 777 | { 778 | '02622926381f2e00000000000000000000000000636b': 779 | [ 780 | '02622926381f2e00000000000000000000000000636b06', 781 | '02502926381eb5522f2e00', 782 | '02512926381eb552112e00000100e03901ff01001e06050000' 783 | ] 784 | }, 785 | { 786 | '02622926381f2e000000010000000000000000009f3a': 787 | [ 788 | '026229', 789 | '26381f2e000000010000000000000000009f3a06', 790 | '02502926381eb5522f2e00', 791 | '02512926381eb552112e0000010146230d494401000100d0f7' 792 | ] 793 | }]; 794 | 795 | gw.connect(host, function () { 796 | gw.thermostat('292638') 797 | .details() 798 | .then(function (details) { 799 | should.exist(details); 800 | should.not.exist(details.date); 801 | should.not.exist(details.mode); 802 | should.not.exist(details.fan); 803 | 804 | gw.close(); 805 | done(); 806 | }); 807 | }); 808 | }); 809 | 810 | it('gets error getting details #2', function (done) { 811 | var gw = new Insteon(); 812 | 813 | mockHub.mockData = [ 814 | { 815 | '02622926381f2e020000000000000000000000009296': 816 | [ 817 | '02622926381f2e02000000000000000000000000929606', 818 | '02502926381eb5522f2e02', 819 | '02502926381eb5522f2e02', 820 | '02512926381eb552112e02010209232710493900e0804451b1' 821 | ] 822 | }, 823 | { 824 | '02622926381f2e00000000000000000000000000636b': 825 | [ 826 | '02622926381f2e00000000000000000000000000636b15' 827 | ] 828 | }, 829 | { 830 | '02622926381f2e000000010000000000000000009f3a': 831 | [ 832 | '026229', 833 | '26381f2e000000010000000000000000009f3a06', 834 | '02502926381eb5522f2e00', 835 | '02512926381eb552112e0000010146230d494401000100d0f7' 836 | ] 837 | }]; 838 | 839 | gw.connect(host, function () { 840 | gw.thermostat('292638') 841 | .details() 842 | .then(function (details) { 843 | should.exist(details); 844 | should.not.exist(details.backlight); 845 | should.not.exist(details.delay); 846 | should.not.exist(details.energyOffset); 847 | 848 | gw.close(); 849 | done(); 850 | }); 851 | }); 852 | }); 853 | 854 | it('gets error getting details #3', function (done) { 855 | var gw = new Insteon(); 856 | 857 | mockHub.mockData = [ 858 | { 859 | '02622926381f2e020000000000000000000000009296': 860 | [ 861 | '02622926381f2e02000000000000000000000000929606', 862 | '02502926381eb5522f2e02', 863 | '02502926381eb5522f2e02', 864 | '02512926381eb552112e02010209232710493900e0804451b1' 865 | ] 866 | }, 867 | { 868 | '02622926381f2e00000000000000000000000000636b': 869 | [ 870 | '02622926381f2e00000000000000000000000000636b06', 871 | '02502926381eb5522f2e00', 872 | '02512926381eb552112e00000100e03901ff01001e06050000' 873 | ] 874 | }, 875 | { 876 | '02622926381f2e000000010000000000000000009f3a': 877 | [ 878 | '02622926381f2e000000010000000000000000009f3a15' 879 | ] 880 | } 881 | ]; 882 | 883 | gw.connect(host, function () { 884 | gw.thermostat('292638') 885 | .details() 886 | .then(function (details) { 887 | should.exist(details); 888 | should.not.exist(details.setpoints.highHumidity); 889 | should.not.exist(details.setpoints.lowHumidity); 890 | 891 | gw.close(); 892 | done(); 893 | }); 894 | }); 895 | }); 896 | 897 | it('enables monitoring mode #1', function (done) { 898 | this.slow(500); 899 | var gw = new Insteon(); 900 | 901 | mockHub.mockData = [ 902 | { 903 | '0260': '0260ffffff03159b06' 904 | }, 905 | { 906 | '0262aaaaaa1f2f0000000fff010000000000000000c2': 907 | [ 908 | '0262aaaaaa1f2f0000000fff010000000000000000c206', 909 | '0250aaaaaaffffff2b2f00', 910 | '0251aaaaaaffffff112f0000010fff0142efcccccc0100efb9' 911 | ] 912 | }, 913 | { 914 | '0262aaaaaa1f2f0000000ff7010000000000000000ca': 915 | [ 916 | '0262aaaaaa1f2f0000000ff7010000000000000000ca06', 917 | '0250aaaaaaffffff2b2f00', 918 | '0251aaaaaaffffff112f0000010ff701c2efffffff0000008c' 919 | ] 920 | }, 921 | { 922 | '0262aaaaaa1f2f0000000fef010000000000000000d2': 923 | [ 924 | '0262aaaaaa1f2f0000000fef010000000000000000d206', 925 | '0250aaaaaaffffff2b2f00', 926 | '0251aaaaaaffffff112f0000010fef01c2feffffff00000085' 927 | ] 928 | }, 929 | { 930 | '0262aaaaaa1f2f0000000fe7010000000000000000da': 931 | [ 932 | '0262aaaaaa1f2f0000000fe7010000000000000000da06', 933 | '0250aaaaaaffffff2b2f00', 934 | '0251aaaaaaffffff112f0000010fe701e201ffffff03159bb7' 935 | ] 936 | }, 937 | { 938 | '0262aaaaaa1f2f0000000fdf010000000000000000e2': 939 | [ 940 | '0262aaaaaa1f2f0000000fdf010000000000000000e206', 941 | '0250aaaaaaffffff2b2f00', 942 | '0251aaaaaaffffff112f0000010fdf01a201ffffff3c4b43e8' 943 | ] 944 | }, 945 | { 946 | '0262aaaaaa1f2f0000000fd7010000000000000000ea': 947 | [ 948 | '0262aaaaaa1f2f0000000fd7010000000000000000ea06', 949 | '0250aaaaaaffffff2b2f00', 950 | '0251aaaaaaffffff112f0000010fd7010000000000000000e9' 951 | ] 952 | }, 953 | { 954 | '0269': '026915' 955 | }, 956 | { 957 | '026f2082efaaaaaa000000': 958 | [ 959 | '026f2082efaaaaaa00000015' 960 | ] 961 | }, 962 | { 963 | '0262aaaaaa1f2e0008000000000000000000000000ca': 964 | [ 965 | '0262aaaaaa1f2e0008000000000000000000000000ca06' 966 | ] 967 | } 968 | ]; 969 | 970 | gw.connect(host, function () { 971 | var thermostat = gw.thermostat('aaaaaa'); 972 | 973 | thermostat.monitor(function (err, status) { 974 | should.not.exist(err); 975 | (status === null).should.be.true; 976 | gw.close(); 977 | done(); 978 | }); 979 | }); 980 | }); 981 | 982 | it('disables monitoring mode', function (done) { 983 | this.slow(500); 984 | var gw = new Insteon(); 985 | 986 | mockHub.mockData = [ 987 | { 988 | '0260': '0260ffffff03159b06' 989 | }, 990 | { 991 | '0262aaaaaa1f2f0000000fff010000000000000000c2': 992 | [ 993 | '0262aaaaaa1f2f0000000fff010000000000000000c206', 994 | '0250aaaaaaffffff2b2f00', 995 | '0251aaaaaaffffff112f0000010fff0142efcccccc0100efb9' 996 | ] 997 | }, 998 | { 999 | '0262aaaaaa1f2f0000000ff7010000000000000000ca': 1000 | [ 1001 | '0262aaaaaa1f2f0000000ff7010000000000000000ca06', 1002 | '0250aaaaaaffffff2b2f00', 1003 | '0251aaaaaaffffff112f0000010ff701c2efffffff0000008c' 1004 | ] 1005 | }, 1006 | { 1007 | '0262aaaaaa1f2f0000000fef010000000000000000d2': 1008 | [ 1009 | '0262aaaaaa1f2f0000000fef010000000000000000d206', 1010 | '0250aaaaaaffffff2b2f00', 1011 | '0251aaaaaaffffff112f0000010fef01c2feffffff00000085' 1012 | ] 1013 | }, 1014 | { 1015 | '0262aaaaaa1f2f0000000fe7010000000000000000da': 1016 | [ 1017 | '0262aaaaaa1f2f0000000fe7010000000000000000da06', 1018 | '0250aaaaaaffffff2b2f00', 1019 | '0251aaaaaaffffff112f0000010fe701e201ffffff03159bb7' 1020 | ] 1021 | }, 1022 | { 1023 | '0262aaaaaa1f2f0000000fdf010000000000000000e2': 1024 | [ 1025 | '0262aaaaaa1f2f0000000fdf010000000000000000e206', 1026 | '0250aaaaaaffffff2b2f00', 1027 | '0251aaaaaaffffff112f0000010fdf01a201ffffff3c4b43e8' 1028 | ] 1029 | }, 1030 | { 1031 | '0262aaaaaa1f2f0000000fd7010000000000000000ea': 1032 | [ 1033 | '0262aaaaaa1f2f0000000fd7010000000000000000ea06', 1034 | '0250aaaaaaffffff2b2f00', 1035 | '0251aaaaaaffffff112f0000010fd7010000000000000000e9' 1036 | ] 1037 | }, 1038 | { 1039 | '0269': '026915' 1040 | }, 1041 | { 1042 | '026f8000efaaaaaa000000': 1043 | [ 1044 | '026f8000efaaaaaa00000015' 1045 | ] 1046 | }, 1047 | { 1048 | '0262aaaaaa1f2f0000020ff70842efffffff00000093': 1049 | [ 1050 | '0262aaaaaa1f2f0000020ff70842efffffff0000009306', 1051 | '0250aaaaaa239acf2b2e00' 1052 | ] 1053 | } 1054 | ]; 1055 | 1056 | gw.connect(host, function () { 1057 | var thermostat = gw.thermostat('aaaaaa'); 1058 | 1059 | thermostat.monitor(false).then(function (status) { 1060 | (status === null).should.be.true; 1061 | gw.close(); 1062 | done(); 1063 | }); 1064 | }); 1065 | }); 1066 | 1067 | it('enables monitoring mode #2', function (done) { 1068 | this.slow(500); 1069 | var gw = new Insteon(); 1070 | 1071 | mockHub.mockData = [ 1072 | { 1073 | '0260': '0260ffffff03159b06' 1074 | }, 1075 | { 1076 | '0262aaaaaa1f2f0000000fff010000000000000000c2': 1077 | [ 1078 | '0262aaaaaa1f2f0000000fff010000000000000000c206', 1079 | '0250aaaaaaffffff2b2f00', 1080 | '0251aaaaaaffffff112f0000010fff0142efcccccc0100efb9' 1081 | ] 1082 | }, 1083 | { 1084 | '0262aaaaaa1f2f0000000ff7010000000000000000ca': 1085 | [ 1086 | '0262aaaaaa1f2f0000000ff7010000000000000000ca06', 1087 | '0250aaaaaaffffff2b2f00', 1088 | '0251aaaaaaffffff112f0000010ff701c2efffffff0000008c' 1089 | ] 1090 | }, 1091 | { 1092 | '0262aaaaaa1f2f0000000fef010000000000000000d2': 1093 | [ 1094 | '0262aaaaaa1f2f0000000fef010000000000000000d206', 1095 | '0250aaaaaaffffff2b2f00', 1096 | '0251aaaaaaffffff112f0000010fef01c2feffffff00000085' 1097 | ] 1098 | }, 1099 | { 1100 | '0262aaaaaa1f2f0000000fe7010000000000000000da': 1101 | [ 1102 | '0262aaaaaa1f2f0000000fe7010000000000000000da06', 1103 | '0250aaaaaaffffff2b2f00', 1104 | '0251aaaaaaffffff112f0000010fe701e201ffffff03159bb7' 1105 | ] 1106 | }, 1107 | { 1108 | '0262aaaaaa1f2f0000000fdf010000000000000000e2': 1109 | [ 1110 | '0262aaaaaa1f2f0000000fdf010000000000000000e206', 1111 | '0250aaaaaaffffff2b2f00', 1112 | '0251aaaaaaffffff112f0000010fdf01a201ffffff3c4b43e8' 1113 | ] 1114 | }, 1115 | { 1116 | '0262aaaaaa1f2f0000000fd7010000000000000000ea': 1117 | [ 1118 | '0262aaaaaa1f2f0000000fd7010000000000000000ea06', 1119 | '0250aaaaaaffffff2b2f00', 1120 | '0251aaaaaaffffff112f0000010fd7010000000000000000e9' 1121 | ] 1122 | }, 1123 | { 1124 | '0269': '026915' 1125 | }, 1126 | { 1127 | '026f2082efaaaaaa000000': 1128 | [ 1129 | '026f2082efaaaaaa00000015' 1130 | ] 1131 | }, 1132 | { 1133 | '0262aaaaaa1f2e0008000000000000000000000000ca': 1134 | [ 1135 | '0262aaaaaa1f2e0008000000000000000000000000ca06' 1136 | ] 1137 | } 1138 | ]; 1139 | 1140 | gw.connect(host, function () { 1141 | var thermostat = gw.thermostat('aaaaaa'); 1142 | 1143 | thermostat.monitor().then(function (status) { 1144 | (status === null).should.be.true; 1145 | gw.close(); 1146 | done(); 1147 | }); 1148 | }); 1149 | }); 1150 | 1151 | it('cancels pending command', function (done) { 1152 | var gw = new Insteon(); 1153 | 1154 | gw.connect(host, function () { 1155 | var thermostat = gw.thermostat('112233'); 1156 | var plan = new Plan(3, function () { 1157 | gw.close(); 1158 | done(); 1159 | }); 1160 | 1161 | thermostat.tempUp().then(function () { 1162 | plan.ok(); 1163 | }); 1164 | 1165 | thermostat.tempDown().then(function () { 1166 | throw new Error('This command should have been canceled.'); 1167 | }).fail(function (err) { 1168 | should.exist(err); 1169 | err.message.should.equal('Canceled'); 1170 | plan.ok(); 1171 | 1172 | setTimeout(function () { 1173 | mockHub.send([ 1174 | '02621122331f6802000000000000000000000000009606', 1175 | '02501122331eb5522f6802' 1176 | ], function () { 1177 | plan.ok(); 1178 | }); 1179 | }, 10); 1180 | }); 1181 | 1182 | setTimeout(function () { 1183 | thermostat.cancelPending(); 1184 | }, 100); 1185 | }); 1186 | }); 1187 | 1188 | describe('Thermostat Events', function () { 1189 | 1190 | it('emits monitoring events', function (done) { 1191 | var gw = new Insteon(); 1192 | var plan = new Plan(6, function () { 1193 | gw.close(); 1194 | done(); 1195 | }); 1196 | 1197 | gw.connect(host, function () { 1198 | var thermostat = gw.thermostat('aaaaaa'); 1199 | 1200 | thermostat.on('status', function (status) { 1201 | should.exist(status); 1202 | if (!!status.temperature) { 1203 | status.temperature.should.equal(88); 1204 | plan.ok(); 1205 | } else if (!!status.humidity) { 1206 | status.humidity.should.equal(41); 1207 | plan.ok(); 1208 | } else if (!!status.mode) { 1209 | status.mode.should.equal('heat'); 1210 | status.fan.should.equal(true); 1211 | plan.ok(); 1212 | } else if (!!status.coolSetpoint) { 1213 | status.coolSetpoint.should.equal(75); 1214 | plan.ok(); 1215 | } else if (!!status.heatSetpoint) { 1216 | status.heatSetpoint.should.equal(70); 1217 | plan.ok(); 1218 | } else { 1219 | throw new Error('Uknown status report.', status); 1220 | } 1221 | }); 1222 | 1223 | setTimeout(function () { 1224 | mockHub.send([ 1225 | '0250aaaaaaffffff016eb0', // temperature 1226 | '0250aaaaaaffffff016f29', // humidity 1227 | '0250aaaaaaffffff017011', // mode 1228 | '0250aaaaaaffffff01714b', // coolSetpoint 1229 | '0250aaaaaaffffff017246' // heatSetpoint 1230 | ], function () { 1231 | plan.ok(); 1232 | }); 1233 | }, 10); 1234 | }); 1235 | }); 1236 | 1237 | it('emits cooling event', function (done) { 1238 | var plan = new Plan(3, function () { 1239 | gw.close(); 1240 | done(); 1241 | }); 1242 | var gw = new Insteon(); 1243 | var thermostat = gw.thermostat('292638'); 1244 | 1245 | thermostat.on('command', function (group, cmd1) { 1246 | group.should.equal(1); 1247 | cmd1.should.equal('11'); 1248 | plan.ok(); 1249 | }); 1250 | 1251 | thermostat.on('cooling', function () { 1252 | plan.ok(); 1253 | }); 1254 | 1255 | gw.connect(host, function () { 1256 | setTimeout(function () { // make sure server connection event fires first 1257 | mockHub.send([ 1258 | '0250292638000001cb1100', 1259 | '02502926381eb552401101', 1260 | '02502926381eb552451101', 1261 | '0250292638110101cf0600', 1262 | '0250292638110101cf0600' 1263 | ], function () { 1264 | plan.ok(); 1265 | }); 1266 | }, 10); 1267 | }); 1268 | }); 1269 | 1270 | it('emits heating event', function (done) { 1271 | var plan = new Plan(3, function () { 1272 | gw.close(); 1273 | done(); 1274 | }); 1275 | var gw = new Insteon(); 1276 | var thermostat = gw.thermostat('292638'); 1277 | 1278 | thermostat.on('command', function (group, cmd1) { 1279 | group.should.equal(2); 1280 | cmd1.should.equal('11'); 1281 | plan.ok(); 1282 | }); 1283 | 1284 | thermostat.on('heating', function () { 1285 | plan.ok(); 1286 | }); 1287 | 1288 | gw.connect(host, function () { 1289 | setTimeout(function () { // make sure server connection event fires first 1290 | mockHub.send([ 1291 | '0250292638000002cb1100', 1292 | '02502926381eb552401102', 1293 | '02502926381eb552451102', 1294 | '0250292638110202cf0600', 1295 | '0250292638110202cf0600' 1296 | ], function () { 1297 | plan.ok(); 1298 | }); 1299 | }, 10); 1300 | }); 1301 | }); 1302 | 1303 | it('emits off event', function (done) { 1304 | var plan = new Plan(3, function () { 1305 | gw.close(); 1306 | done(); 1307 | }); 1308 | var gw = new Insteon(); 1309 | var thermostat = gw.thermostat('292638'); 1310 | 1311 | thermostat.on('command', function (group, cmd1) { 1312 | group.should.equal(1); 1313 | cmd1.should.equal('13'); 1314 | plan.ok(); 1315 | }); 1316 | 1317 | thermostat.on('off', function () { 1318 | plan.ok(); 1319 | }); 1320 | 1321 | gw.connect(host, function () { 1322 | setTimeout(function () { // make sure server connection event fires first 1323 | mockHub.send([ 1324 | '0250292638000001cb1300', 1325 | '02502926381eb552401301', 1326 | '02502926381eb552451301', 1327 | '0250292638130101cf0600', 1328 | '0250292638130101cf0600' 1329 | ], function () { 1330 | plan.ok(); 1331 | }); 1332 | }, 10); 1333 | }); 1334 | }); 1335 | 1336 | it('emits highHumidity event', function (done) { 1337 | var plan = new Plan(3, function () { 1338 | gw.close(); 1339 | done(); 1340 | }); 1341 | var gw = new Insteon(); 1342 | var thermostat = gw.thermostat('292638'); 1343 | 1344 | thermostat.on('command', function (group, cmd1) { 1345 | group.should.equal(3); 1346 | cmd1.should.equal('11'); 1347 | plan.ok(); 1348 | }); 1349 | 1350 | thermostat.on('highHumidity', function () { 1351 | plan.ok(); 1352 | }); 1353 | 1354 | gw.connect(host, function () { 1355 | setTimeout(function () { // make sure server connection event fires first 1356 | mockHub.send([ 1357 | '0250292638000003cb1100', 1358 | '02502926381eb552401103', 1359 | '02502926381eb552451103', 1360 | '0250292638110303cf0600', 1361 | '0250292638110303cf0600' 1362 | ], function () { 1363 | plan.ok(); 1364 | }); 1365 | }, 10); 1366 | }); 1367 | }); 1368 | 1369 | it('emits lowHumidity event', function (done) { 1370 | var plan = new Plan(3, function () { 1371 | gw.close(); 1372 | done(); 1373 | }); 1374 | var gw = new Insteon(); 1375 | var thermostat = gw.thermostat('292638'); 1376 | 1377 | thermostat.on('command', function (group, cmd1) { 1378 | group.should.equal(4); 1379 | cmd1.should.equal('11'); 1380 | plan.ok(); 1381 | }); 1382 | 1383 | thermostat.on('lowHumidity', function () { 1384 | plan.ok(); 1385 | }); 1386 | 1387 | gw.connect(host, function () { 1388 | setTimeout(function () { // make sure server connection event fires first 1389 | mockHub.send([ 1390 | '0250292638000004cb1100', 1391 | '02502926381eb552401104', 1392 | '02502926381eb552451104', 1393 | '0250292638110404cf0600', 1394 | '0250292638110404cf0600' 1395 | ], function () { 1396 | plan.ok(); 1397 | }); 1398 | }, 10); 1399 | }); 1400 | }); 1401 | 1402 | it('emits normalHumidity event', function (done) { 1403 | var plan = new Plan(3, function () { 1404 | gw.close(); 1405 | done(); 1406 | }); 1407 | var gw = new Insteon(); 1408 | var thermostat = gw.thermostat('292638'); 1409 | 1410 | thermostat.on('command', function (group, cmd1) { 1411 | group.should.equal(4); 1412 | cmd1.should.equal('13'); 1413 | plan.ok(); 1414 | }); 1415 | 1416 | thermostat.on('normalHumidity', function () { 1417 | plan.ok(); 1418 | }); 1419 | 1420 | gw.connect(host, function () { 1421 | setTimeout(function () { // make sure server connection event fires first 1422 | mockHub.send([ 1423 | '0250292638000004cb1300', 1424 | '02502926381eb552401304', 1425 | '02502926381eb552451304', 1426 | '0250292638130404cf0600', 1427 | '0250292638130404cf0600' 1428 | ], function () { 1429 | plan.ok(); 1430 | }); 1431 | }, 10); 1432 | }); 1433 | }); 1434 | 1435 | it('receives invalid event', function (done) { 1436 | var plan = new Plan(2, function () { 1437 | gw.close(); 1438 | done(); 1439 | }); 1440 | var gw = new Insteon(); 1441 | var thermostat = gw.thermostat('292638'); 1442 | 1443 | thermostat.on('command', function (group, cmd1) { 1444 | group.should.equal(4); 1445 | cmd1.should.equal('17'); 1446 | plan.ok(); 1447 | }); 1448 | 1449 | gw.connect(host, function () { 1450 | setTimeout(function () { // make sure server connection event fires first 1451 | mockHub.send([ 1452 | '0250292638000004cb1700', 1453 | ], function () { 1454 | plan.ok(); 1455 | }); 1456 | }, 10); 1457 | }); 1458 | }); 1459 | 1460 | }); //discribe Thermostat Events 1461 | 1462 | }); 1463 | 1464 | -------------------------------------------------------------------------------- /test/X10.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Insteon = require('../').Insteon; 4 | var mockHub = require('../lib/Test/mockHub'); 5 | 6 | var host = '127.0.0.1'; 7 | var port = 9761; 8 | 9 | describe('X10 Functions', function () { 10 | this.timeout(5000); 11 | 12 | before(function (done) { 13 | mockHub.listen(port, host, function () { 14 | done(); 15 | }); 16 | }); 17 | 18 | after(function (done) { 19 | mockHub.close(function() { 20 | done(); 21 | }); 22 | }); 23 | 24 | it('turn on', function (done) { 25 | var gw = new Insteon(); 26 | 27 | mockHub.mockData = [{ 28 | '02636600': '0263660006' 29 | }, { 30 | '02636280': '0263628006' 31 | }]; 32 | 33 | gw.connect(host, function () { 34 | var x10 = gw.x10('A', 1); 35 | x10.turnOn(function() { 36 | gw.close(); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | it('turn off', function (done) { 43 | var gw = new Insteon(); 44 | 45 | mockHub.mockData = [{ 46 | '0263cc00': '0263cc0006' 47 | }, { 48 | '0263c380': '0263c38006' 49 | }]; 50 | 51 | gw.connect(host, function () { 52 | var x10 = gw.x10('p', 16); 53 | x10.turnOff(function() { 54 | gw.close(); 55 | done(); 56 | }); 57 | }); 58 | }); 59 | }); // X10 Functions 60 | --------------------------------------------------------------------------------