UNPKG

homebridge-isy-js

Version:

Homebridge plugin for ISY series of home controllers from universal devices. Unsupported and no longer under active development.

837 lines (787 loc) 48.4 kB
var FakeHomeBridge = require('./fakehomebridge').FakeHomeBridge; var Service = require('./fakeservice'); var Characteristic = require('./fakecharacteristic'); var assert = require('assert'); var ISYFanAccessory = require('../index').ISYFanAccessory; var ISYLightAccessory = require('../index').ISYLightAccessory; var ISYLockAccessory = require('../index').ISYLockAccessory; var ISYOutletAccessory = require('../index').ISYOutletAccessory; var ISYDoorWindowSensorAccessory = require('../index').ISYDoorWindowSensorAccessory; var ISYElkAlarmPanelAccessory = require('../index').ISYElkAlarmPanelAccessory; var ISYMotionSensorAccessory = require('../index').ISYMotionSensorAccessory; var ISYSceneAccessory = require('../index').ISYSceneAccessory; var ISYGarageDoorAccessory = require('../index').ISYGarageDoorAccessory; var restler = require('restler'); var testServerAddress = '127.0.0.1:3000'; var testServerUserName = 'admin'; var testServerPassword = 'password'; var sampleDimmableLightWhichIsOff = '33 9B DC 1'; var sampleDimmableLightWhichIsOn = '17 28 71 1'; var sampleUnLockedDoorLock = 'ZW002_1'; var sampleLockedDoorLock = 'ZW005_1'; var sampleFanOnHigh = '14 A6 C5 2'; var sampleFanOff = '14 A7 12 2'; var sampleInsteonContactSensor = '14 47 41 1'; var sampleOutlet = '1F 46 F0 1'; var sampleMotionSensor = '14 5C B2 1'; var sampleGarageDoorOpener = '17 79 81 1'; var sampleGarageDoorOpenerOpen = '17 80 81 1'; var sampleScene = '27346'; var sampleSceneDevices = ['18 12 18 1', '19 53 90 1']; var numExpectedDevices = 92; var numExpectedScenes = 2; var numExpectedLights = 42; var numExpectedLocks = 3; var numExpectedMotionSensors = 5; var numExpectedAlarmSystems = 1; var numExpectedOutlets = 1; var numExpectedFans = 3; var numExpectedDoorWindowSensor = 33; var numExpectedGarageDoorOpeners = 2; function findDevice(bridge, isyAddress) { for(var deviceIndex = 0; deviceIndex < bridge.deviceList.length; deviceIndex++) { if(bridge.deviceList[deviceIndex].device.address == isyAddress) { return bridge.deviceList[deviceIndex]; } } return null; } describe('HomeBridge startup and device enumeration', function() { it('Basic startup should work and return the right type of devices', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var lightCount = 0; var lockCount = 0; var motionCount = 0; var alarmCount = 0; var outletCount = 0; var fanCount = 0; var doorWindowCount = 0; var garageDoorOpenerCount = 0; var sceneCount = 0; for(var deviceIndex = 0; deviceIndex < bridge.deviceList.length; deviceIndex++) { var device = bridge.deviceList[deviceIndex]; assert(device.uuid_base != null && device.uuid_base != undefined && device.uuid_base != "", device.device.address + ' Must have uuid_base'); if(device instanceof ISYFanAccessory) { bridge.checkValidFan(device, device.device.address); fanCount++; } else if(device instanceof ISYLightAccessory) { bridge.checkValidLight(device, device.device.dimmable, device.device.address); lightCount++; } else if(device instanceof ISYSceneAccessory) { bridge.checkValidScene(device, device.device.address); sceneCount++ } else if(device instanceof ISYLockAccessory) { bridge.checkValidLock(device, device.device.address); lockCount++; } else if(device instanceof ISYOutletAccessory) { bridge.checkValidOutlet(device, device.device.address); outletCount++; } else if(device instanceof ISYDoorWindowSensorAccessory) { bridge.checkValidContactSensor(device, device.device.address); doorWindowCount++; } else if(device instanceof ISYElkAlarmPanelAccessory) { bridge.checkValidAlarmPanel(device, device.device.address); alarmCount++; } else if(device instanceof ISYMotionSensorAccessory) { bridge.checkValidMotionSensor(device, device.device.address); motionCount++; } else if(device instanceof ISYGarageDoorAccessory) { bridge.checkValidGarageDoorOpener(device, device.device.address); garageDoorOpenerCount++; } else { assert(false, 'Found an unexpected device type'); } } assert.equal(fanCount, numExpectedFans, 'Should have expected count of fans'); assert.equal(lightCount, numExpectedLights, 'Should have expected count of lights'); assert.equal(lockCount, numExpectedLocks, 'Should have expected count of locks'); assert.equal(outletCount, numExpectedOutlets, 'Should have expected count of outlets'); assert.equal(doorWindowCount, numExpectedDoorWindowSensor, 'Should have expected count of door window sensors'); assert.equal(alarmCount, numExpectedAlarmSystems, 'Should have expected count of alarms'); assert.equal(motionCount, numExpectedMotionSensors, 'Should have expected count of motion sensors'); assert.equal(sceneCount, numExpectedScenes, 'Should have expected number of scenes'); assert.equal(garageDoorOpenerCount, numExpectedGarageDoorOpeners, 'Should have expected number of garage door openers'); assert.equal(bridge.deviceList.length, numExpectedDevices, 'Did not get the expected number of devices'); done(); }); }); it('Ensure startup works with no ignore section and devices capped at 100', function (done) { var bridge = new FakeHomeBridge('./testconfig.bad.ignore.json'); bridge.startPlatform('../index.js', function () { assert.ok(bridge.deviceList!=undefined, 'Must have a device list'); assert.ok(bridge.deviceList.length > 0, 'Must have devices'); assert(bridge.deviceList.length == 100, 'Device list should have been capped at 100 devices. Saw '+bridge.deviceList.length+' devices'); done(); }); }); it('Test new simplified ignore syntax', function (done) { var bridge = new FakeHomeBridge('./testconfig.withsimplifiedsyntax.json'); bridge.startPlatform('../index.js', function () { assert.equal(bridge.deviceList.length, numExpectedDevices, 'Did not get the expected number of devices'); done(); }); }); it('Ensure rename Devices works', function (done) { var bridge = new FakeHomeBridge('./testconfig.withrename.json'); bridge.startPlatform('../index.js', function () { var renamesFound = 0; for(var deviceIndex = 0; deviceIndex < bridge.deviceList.length; deviceIndex++) { if(bridge.deviceList[deviceIndex].name == 'Sarah Light 2') { renamesFound++; } if(bridge.deviceList[deviceIndex].name == 'Sam Light 2') { renamesFound++; } } assert(renamesFound == 2, 'Should have had 2 devices renamed, actually had '+renamesFound); done(); }); }); }); function resetServerState(done) { restler.get('http://'+testServerAddress+'/config/reset').on('complete', function(result, response) { done(); }); } function sendISYCommand(address, command, parameter, done) { var url = 'http://'+testServerUserName+':'+testServerPassword+'@'+testServerAddress+'/rest/nodes/'+address+'/CMD/'+command; if(parameter != null) { url += '/' + parameter; } restler.get(url).on('complete', function() { done(); }); } function getSpecificServiceForDevice(bridge, device, serviceType) { var servicesList = bridge.getServiceListByUuid(device.uuid_base); for (var index = 0; index < servicesList.length; index++) { if(servicesList[index] instanceof serviceType) { return servicesList[index]; } } return null; } function resetServerState(done) { restler.get('http://'+testServerAddress+'/config/reset').on('complete', function(result, response) { done(); }); } function sendCommandAndCheckState(deviceAddress, char, commandToSend, parameterToSend, stateToTry, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); var stateToExpect = stateToTry; assert(deviceToChange != null, 'Could not find test device '+deviceAddress); // Add a notification handler so we know when state is set bridge.notifyOfSet = function(characteristic) { if(characteristic.name == char.name) { if (stateToExpect == characteristic.value) { done(); } } } sendISYCommand(deviceAddress, commandToSend, parameterToSend, function () {}); }); } function sendCommandAndCheckStateViaIsy(deviceAddress, char, commandToSet, parameterForSet, stateToTry, expectedCount, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); var stateToExpect = stateToTry; assert(deviceToChange != null, 'Could not find test device '+deviceAddress); var callbacksReceived = 0; // Add a notification handler so we know when state is set bridge.notifyOfSet = function(characteristic) { if(characteristic.name == char.name) { if (stateToExpect == characteristic.value) { callbacksReceived++; if(callbacksReceived == expectedCount) { done(); } } } } deviceToChange.device[commandToSet](parameterForSet, function() {}); }); } function setCharacteristicAndCheckResult(deviceAddress, service, char, stateToTry, deviceCheckFunction, deviceStateToExpect, expectedCount, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); assert(deviceToChange != null, 'Could not find test device '+deviceAddress); // Hook device change notifications var realCallback = deviceToChange.device.isy.changeCallback; var callbacksReceived = 0; function interceptionCallback(isy,device) { console.log('CALLBACK: Device: '+device.address); realCallback(isy,device); if(device.address == deviceToChange.device.address) { callbacksReceived++; if(callbacksReceived == expectedCount) { assert(deviceToChange.device[deviceCheckFunction]()==deviceStateToExpect, 'State should have been updated'); // Restore callback in case we want to make another change isy.changeCallback = realCallback; done(); } } } deviceToChange.device.isy.changeCallback = interceptionCallback; deviceToChange[service].setCharacteristic(char,stateToTry); }); } function sendIdentifyAndCheckResult(deviceAddress, service, deviceCheckFunction, deviceStateToExpect, expectedCount, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); assert(deviceToChange != null, 'Could not find test device '+deviceAddress); // Hook device change notifications var realCallback = deviceToChange.device.isy.changeCallback; var callbacksReceived = 0; function interceptionCallback(isy,device) { console.log('CALLBACK: Device: '+device.address); realCallback(isy,device); if(device.address == deviceToChange.device.address) { callbacksReceived++; if(callbacksReceived == expectedCount) { assert(deviceToChange.device[deviceCheckFunction]()==deviceStateToExpect, 'State should have been updated'); // Restore callback in case we want to make another change isy.changeCallback = realCallback; done(); } } } deviceToChange.device.isy.changeCallback = interceptionCallback; deviceToChange.identify(); }); } function setCharacteristicAndCheckResult(deviceAddress, service, char, stateToTry, deviceCheckFunction, deviceStateToExpect, expectedCount, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); assert(deviceToChange != null, 'Could not find test device '+deviceAddress); // Hook device change notifications var realCallback = deviceToChange.device.isy.changeCallback; var callbacksReceived = 0; function interceptionCallback(isy,device) { console.log('CALLBACK: Device: '+device.address); realCallback(isy,device); if(device.address == deviceToChange.device.address) { callbacksReceived++; if(callbacksReceived == expectedCount) { assert(deviceToChange.device[deviceCheckFunction]()==deviceStateToExpect, 'State should have been updated'); // Restore callback in case we want to make another change isy.changeCallback = realCallback; done(); } } } deviceToChange.device.isy.changeCallback = interceptionCallback; deviceToChange[service].setCharacteristic(char,stateToTry); }); } function setCharacteristicAndCheckResultAlt(deviceAddress, service, char, stateToTry, deviceStateToExpect, expectedCount, done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var deviceToChange = findDevice(bridge, deviceAddress); assert(deviceToChange != null, 'Could not find test device '+deviceAddress); // Hook device change notifications var realCallback = deviceToChange.device.isy.changeCallback; var callbacksReceived = 0; function interceptionCallback(isy,device) { console.log('CALLBACK: Device: '+device.address); realCallback(isy,device); if(device.address == deviceToChange.device.address) { callbacksReceived++; if(callbacksReceived == expectedCount) { assert(deviceToChange.calculatePowerState()==deviceStateToExpect, 'State should have been updated'); // Restore callback in case we want to make another change isy.changeCallback = realCallback; done(); } } } deviceToChange.device.isy.changeCallback = interceptionCallback; deviceToChange[service].setCharacteristic(char,stateToTry); }); } describe('LIGHT TESTS', function() { describe('LIGHT: Device changes update homebridge', function() { beforeEach(function(done) { resetServerState(function() { done(); }); }); it('Light switching on should update homebridge on state', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOff, Characteristic.On, 'sendLightCommand', true, true, 1, done); }); it('Light switching off should update homebridge on state', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOn, Characteristic.On, 'sendLightCommand', false, false, 1, done); }); it('Light switching on should update homebridge dimness state to 100', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOff, Characteristic.Brightness, 'sendLightCommand', true, 100, 1, done); }); it('Light switching off should update homebridge dimness state to 0', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOn, Characteristic.Brightness, 'sendLightCommand', 0, 0, 1, done); }); it('Light switching off should update homebridge off state', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOn, Characteristic.On, 'sendLightDimCommand', 0, false, 1, done); }); it('Light switching to 0% should switch the light off', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOn, Characteristic.Brightness, 'sendLightDimCommand', 0, 0, 1, done); }); it('Light switching on to 50% dim from off should update homebridge dimness state to 50', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOff, Characteristic.Brightness, 'sendLightDimCommand', 50, 50, 1, done); }); it('Light switching on to 50% dim from off should update homebridge on state to on', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOff, Characteristic.On, 'sendLightDimCommand', 50, true, 1, done); }); it('Light switching from 100% on to 50% dim from off should update homebridge dim state to 50', function (done) { sendCommandAndCheckStateViaIsy(sampleDimmableLightWhichIsOn, Characteristic.Brightness, 'sendLightDimCommand', 50, 50, 1, done); }); }); describe('LIGHT: Making direct changes changes the device', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Switching light on results in update to on state and then off to off state', function (done) { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOff, 'lightService', Characteristic.On, true, 'getCurrentLightState', true, 1, function() { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOff, 'lightService', Characteristic.On, false, 'getCurrentLightState', false, 1, done) } ); }); it('Switching light to 50% results in update to on state to on', function (done) { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOff, 'lightService', Characteristic.Brightness, 50, 'getCurrentLightState', true, 1, done); }); it('Switching light to 0% results in update to on state to off', function (done) { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOn, 'lightService', Characteristic.Brightness, 0, 'getCurrentLightState', false, 1, done); }); it('Switching light to 50% results in update to dim level to 50', function (done) { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOff, 'lightService', Characteristic.Brightness, 50, 'getCurrentLightDimState', 50, 1, done); }); it('Switching light to 0% results in update to dim level to 0', function (done) { setCharacteristicAndCheckResult(sampleDimmableLightWhichIsOn, 'lightService', Characteristic.Brightness, 0, 'getCurrentLightDimState', 0, 1, done); }); }); describe('LIGHT: Identify works and doesnt crash', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Sending identify turns light on then off', function (done) { sendIdentifyAndCheckResult(sampleDimmableLightWhichIsOff, 'lightService', 'getCurrentLightState', false, 2, done); }); }); }); describe('LOCK TESTS', function() { describe('LOCK: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Door locking updates lock current state to locked', function (done) { sendCommandAndCheckStateViaIsy(sampleUnLockedDoorLock, Characteristic.LockCurrentState, 'sendLockCommand', true, Characteristic.LockCurrentState.SECURED, 1, done); }); it('Door unlocking updates lock current state to unlocked', function (done) { sendCommandAndCheckStateViaIsy(sampleLockedDoorLock, Characteristic.LockCurrentState, 'sendLockCommand', false, Characteristic.LockCurrentState.UNSECURED, 1, done); }); it('Door locking updates lock target state to locked', function (done) { sendCommandAndCheckStateViaIsy(sampleUnLockedDoorLock, Characteristic.LockTargetState, 'sendLockCommand', true, Characteristic.LockCurrentState.SECURED, 1, done); }); it('Door unlocking updates lock target state to unlocked', function (done) { sendCommandAndCheckStateViaIsy(sampleLockedDoorLock, Characteristic.LockTargetState, 'sendLockCommand', false, Characteristic.LockCurrentState.UNSECURED, 1, done); }); }); describe('LOCK: Making direct changes changes the device', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Locking the door lock results in a lock state change to locked then unlocked', function (done) { setCharacteristicAndCheckResult(sampleLockedDoorLock, 'lockService', Characteristic.LockTargetState, Characteristic.LockTargetState.UNSECURED, 'getCurrentLockState', false, 1, function() { setCharacteristicAndCheckResult(sampleLockedDoorLock, 'lockService', Characteristic.LockTargetState, Characteristic.LockTargetState.SECURED, 'getCurrentLockState', true, 1, done) } ); }); }); }); describe('FAN TESTS', function() { describe('FAN: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Fan switching to high switches state to on', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.On, 'sendFanCommand', 'High', true, 1, done); }); it('Fan switching to medium switches state to on', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.On, 'sendFanCommand', 'Medium', true, 1, done); }); it('Fan switching to low switches state to on', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.On, 'sendFanCommand', 'Low', true, 1, done); }); it('Fan switching to off switches state to off', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOnHigh, Characteristic.On, 'sendFanCommand', 'Off', false, 1, done); }); it('Fan switching to high switches speed to right value', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.RotationSpeed, 'sendFanCommand', 'High', 100, 1, done); }); it('Fan switching to medium switches speed to right value', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.RotationSpeed, 'sendFanCommand', 'Medium', 67, 1, done); }); it('Fan switching to low switches speed to right value', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOff, Characteristic.RotationSpeed, 'sendFanCommand', 'Low', 32, 1, done); }); it('Fan switching to off switches speed to right value', function (done) { sendCommandAndCheckStateViaIsy(sampleFanOnHigh, Characteristic.RotationSpeed, 'sendFanCommand', 'Off', 0, 1, done); }); }); describe('FAN: Making direct changes changes the device', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Setting fan to off turns off the fan', function (done) { setCharacteristicAndCheckResult(sampleFanOnHigh, 'fanService', Characteristic.On, false, 'getCurrentFanState', 'Off', 1, done); }); it('Setting fan to on sets fan to Medium', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.On, true, 'getCurrentFanState', 'Medium', 1, done); }); it('Setting fan to 100% rotation speed sets fan to high', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 100, 'getCurrentFanState', 'High', 1, done); }); it('Setting fan to 0% rotation speed sets fan to off', function (done) { setCharacteristicAndCheckResult(sampleFanOnHigh, 'fanService', Characteristic.RotationSpeed, 0, 'getCurrentFanState', 'Off', 1, done); }); it('Setting fan to 32% rotation speed sets fan to low', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 32, 'getCurrentFanState', 'Low', 1, done); }); it('Setting fan to 67% rotation speed sets fan to medium', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 67, 'getCurrentFanState', 'Medium', 1, done); }); it('Setting fan to above 67% rotation speed sets fan to high', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 82, 'getCurrentFanState', 'High', 1, done); }); it('Setting fan to above 32% and below 67% rotation speed sets fan to medium', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 45, 'getCurrentFanState', 'Medium', 1, done); }); it('Setting fan to above 0% and below 32% rotation speed sets fan to low', function (done) { setCharacteristicAndCheckResult(sampleFanOff, 'fanService', Characteristic.RotationSpeed, 16, 'getCurrentFanState', 'Low', 1, done); }); it('Setting fan which is high to low switches rotation speed sets fan to low', function (done) { setCharacteristicAndCheckResult(sampleFanOnHigh, 'fanService', Characteristic.RotationSpeed, 16, 'getCurrentFanState', 'Low', 1, done); }); }); }); describe('CONTACT TESTS', function() { describe('CONTACT SENSOR: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Sensor detecting open updates local sensor', function (done) { sendCommandAndCheckState(sampleInsteonContactSensor, Characteristic.ContactSensorState, 'DON', null, true, function() { sendCommandAndCheckState(sampleInsteonContactSensor, Characteristic.ContactSensorState, 'DOF', null, false, done); }); }); }); }); describe('OUTLET TESTS', function() { describe('OUTLET: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('OUTLET: Outlet switching to on switches On state to on and opposite to off', function (done) { sendCommandAndCheckStateViaIsy(sampleOutlet, Characteristic.On, 'sendOutletCommand', true, true, 1, function() { sendCommandAndCheckStateViaIsy(sampleOutlet, Characteristic.On, 'sendOutletCommand', false, false, 1, done); }); }); }); describe('OUTLET: Making direct changes changes the device propogates to the device', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('OUTLET: Outlet switching to on switches On state to on and opposite to off', function (done) { setCharacteristicAndCheckResult(sampleOutlet, 'outletService', Characteristic.On, true, 'getCurrentOutletState', true, 1, function() { setCharacteristicAndCheckResult(sampleOutlet, 'outletService', Characteristic.On, false, 'getCurrentOutletState', false, 1, done); }); }); }); }); describe('MOTION SENSOR TESTS', function() { describe('MOTION SENSOR: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Motion sensor switching to on switches On state to on and opposite to off', function (done) { sendCommandAndCheckState(sampleMotionSensor, Characteristic.MotionDetected, 'DON', null, true, function() { sendCommandAndCheckState(sampleMotionSensor, Characteristic.MotionDetected, 'DOF', null, false, done); }); }); }); }); describe('SCENE TESTS', function() { describe('SCENE: Device changes update homebridge', function() { beforeEach(function(done) { resetServerState(function() { done(); }); }); it('Scene switching on then off should update homebridge on state to on then off', function (done) { sendCommandAndCheckStateViaIsy(sampleScene, Characteristic.On, 'sendLightCommand', true, true, 2, function() { sendCommandAndCheckStateViaIsy(sampleScene, Characteristic.On, 'sendLightCommand', false, false, 2, done); }); done(); }); }); describe('SCENE: Making direct changes changes the device', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Switching light on results in update to on state and then off to off state', function (done) { setCharacteristicAndCheckResult(sampleScene, 'lightService', Characteristic.On, true, 'getCurrentLightState', true, 1, function () { setCharacteristicAndCheckResultAlt(sampleScene, 'lightService', Characteristic.On, false, false, 1, done); }); }); }); describe('SCENE: Test on behavior - Only on if all devices are on', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('Switching light on a single light in a scene doesnt change scene to on but switching all of them does', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { // Need to jump through hoops. Override current state of one of the lights so we can test partial state. var deviceToSwitch = bridge.deviceList[0].device.isy.getDevice(sampleSceneDevices[0]); deviceToSwitch.handleIsyUpdate(255); var sceneToCheck = findDevice(bridge, sampleScene); assert(!sceneToCheck.calculatePowerState(), "With only one device on this scene should be marked off"); deviceToSwitch = bridge.deviceList[0].device.isy.getDevice(sampleSceneDevices[1]); deviceToSwitch.handleIsyUpdate(255); assert(sceneToCheck.calculatePowerState(), "After tweaking both devices scene should be on"); done(); }); }); }); }); function setGarageState(bridge, address, current, target) { var garageDevice = findDevice(bridge, address); assert(garageDevice != null, "Should have a garage device."); garageDevice.currentGarageState = current; garageDevice.targetGarageState = target; } function setGarageDoorSensor(bridge, address, state, done) { var sensorDevice = bridge.deviceList[0].device.isy.getDevice(address); assert(sensorDevice != null, "Should have a garage sensor device."); sensorDevice.sendLightCommand(state, done); } describe('GARAGE DOOR OPENER TESTS', function() { describe('GARAGE DOOR OPENER: Device changes update homebridge', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('GARAGE DOOR OPENER: Garage door is closed, starting an open remotely enters opening state and then gets to opened state', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { // Set the garage to closed state setGarageState(bridge, sampleGarageDoorOpener, Characteristic.CurrentDoorState.CLOSED, Characteristic.TargetDoorState.CLOSED); var callbackCount = 0; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "First change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPENING, "First change should be to the state of opening"); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Second change should be current state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "Second change should be to the state of opening"); } else if(callbackCount == 2) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Final change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPEN, "Final change should be to the state of open"); done(); } callbackCount++; } // Poke the sensor to make it look like door is opening setGarageDoorSensor(bridge, sampleGarageDoorOpener, true, function() {}); }); }); it('GARAGE DOOR OPENER: Garage door is opened, starting a close enters closing state and then gets to closed state', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { // Set the garage to closed state setGarageState(bridge, sampleGarageDoorOpenerOpen, Characteristic.CurrentDoorState.OPEN, Characteristic.TargetDoorState.OPEN); var callbackCount = 0; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "First change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSED, "First change should be to the state of closing"); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Second change should be current state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.CLOSED, "Second change should be to the state of opening"); done(); } callbackCount++; } // Poke the sensor to make it look like door is opening setTimeout(function() { setGarageDoorSensor(bridge, sampleGarageDoorOpenerOpen, false, function () {}); }, 500); }); }); }); describe('GARAGE DOOR OPENER: Commanding the device makes changes', function() { beforeEach(function (done) { resetServerState(function () { done(); }); }); it('GARAGE DOOR OPENER: Garage door is closed, start an open and the state enters opening and transitions to open', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var callbackCount = 0; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "First change should be target state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "First change should be to the state of open"); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Second change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPENING, "Second change should be to the state of opening"); } else if(callbackCount == 2) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Third change should be target state and be redundant (from change notification from is)"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "Third change should be to the state of open and be redundant (from change notification from is)"); } else if(callbackCount == 3) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Final change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPEN, "Final change should be to the state of open"); done(); } callbackCount++; } var garageDevice = findDevice(bridge, sampleGarageDoorOpener); garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.OPEN); // This should happen if the command worked so we need to simulate it. setGarageDoorSensor(bridge, sampleGarageDoorOpener, true, function () {}); }); }); it('GARAGE DOOR OPENER: Garage door is open, start a close and the state enters closing and transitions to closed', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var callbackCount = 0; var garageDevice = findDevice(bridge, sampleGarageDoorOpenerOpen); garageDevice.timeToOpen = 10000; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "First change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSING, "First change should be to the state of closing"); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Second change should be target state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.CLOSED, "Second change should be to the state of closed"); } else if(callbackCount == 2) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Third change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSED, "Third change should be to the state of open"); } else if(callbackCount == 3) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Final change should be target state and be redundant (from change notification from is)"); assert.equal(characteristic.value, Characteristic.TargetDoorState.CLOSED, "Final change should be to the state of open and be redundant (from change notification from is)"); done(); } callbackCount++; } garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED); // This should happen if the command worked so we need to simulate it. // Poke the sensor to make it look like door is opening setTimeout(function() { setGarageDoorSensor(bridge, sampleGarageDoorOpenerOpen, false, function () {}); }, 1000); }); }); it('GARAGE DOOR OPENER: Garage door is open, start a close and then do an open. State enters closing and transitions to opening and then open', function (done) { var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var callbackCount = 0; var garageDevice = findDevice(bridge, sampleGarageDoorOpenerOpen); garageDevice.timeToOpen = 1000; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "First change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSING, "First change should be to the state of closing"); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Second change should be target state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.CLOSED, "Second change should be to the state of closed"); } else if(callbackCount == 2) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Third change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPENING, "Third change should be to the state of open"); } else if(callbackCount == 3) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Fourth change should be target state and be redundant (from change notification from is)"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "Fourth change should be to the state of open and be redundant (from change notification from is)"); } else if(callbackCount == 4) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Final change should be current state and should be open"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPEN, "Final change should be current state and should be open"); done(); } callbackCount++; } garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED); // This should happen if the command worked so we need to simulate it. // Poke the sensor to make it look like door is opening setTimeout(function() { garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.OPEN); }, 500); }); }); it('GARAGE DOOR OPENER: Garage door is closed, start an open and then do a close. State enters opening and transitions to closing and then to closed', function (done) { this.timeout(10000); var bridge = new FakeHomeBridge('./testconfig.json'); bridge.startPlatform('../index.js', function () { var callbackCount = 0; var garageDevice = findDevice(bridge, sampleGarageDoorOpenerOpen); garageDevice.timeToOpen = 12000; bridge.notifyOfSet = function(characteristic) { if(callbackCount == 0) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "First change should be target state"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "First change should be to the state of open"); setGarageDoorSensor(bridge, sampleGarageDoorOpener, true, function () {}); } else if(callbackCount == 1) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Second change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.OPENING, "Second change should be to the state of opening"); } else if(callbackCount == 2) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Third change should be target state and be redundant (from change notification from is)"); assert.equal(characteristic.value, Characteristic.TargetDoorState.OPEN, "Third change should be to the state of open and be redundant (from change notification from is)"); } else if(callbackCount == 3) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Fourth change should be current state"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSING, "Fourth change should be to the state of closing"); } else if(callbackCount == 4) { assert.equal(characteristic.name, Characteristic.TargetDoorState.name, "Fourth change should be target state and be redundant (from change notification from is)"); assert.equal(characteristic.value, Characteristic.TargetDoorState.CLOSED, "Fourth change should be to the state of open and be redundant (from change notification from is)"); } else if(callbackCount == 5) { assert.equal(characteristic.name, Characteristic.CurrentDoorState.name, "Final change should be current state and should be closed"); assert.equal(characteristic.value, Characteristic.CurrentDoorState.CLOSED, "Final change should be current state and should be closed"); done(); } callbackCount++; } garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.OPEN); setGarageDoorSensor(bridge, sampleGarageDoorOpener, true, function () {}); // This should happen if the command worked so we need to simulate it. // Poke the sensor to make it look like door is opening setTimeout(function() { garageDevice.garageDoorService.setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED); setTimeout(function() { setGarageDoorSensor(bridge, sampleGarageDoorOpener, false, function () {}); }, 250); }, 250); }); }); }); });