diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d7624..8caf25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ All notable changes to this project will be documented in this file. - [BREAKING] skipedIfSameStateActivities -> skippedIfSameStateActivities / addAllActivitiesToSkipedIfSameStateActivitiesList -> addAllActivitiesToSkippedIfSameStateActivitiesList - [BREAKING] MENU command (inside properties of TV Service) renamed to SETUP and will be mapped to setup function if avaliable (can be ovewridden) +## 0.6.1 + +- [NEW] swithcing to @lopelex 1.0.9 + +## 0.6.0 + +- [NEW] support for Exposing Home Control buttons #67 +- [BUG] ReferenceError #110 + ## 0.5.5 - [BUG] - showTurnOffActivity doesn’t work properly when inverterted #107 @@ -152,7 +161,7 @@ All notable changes to this project will be documented in this file. ## 0.2.3 -- [NEW] TV MODE - restoring `SkippedIfSameStateActivities` options #46 +- [NEW] TV MODE - restoring `SkipedIfSameStateActivities` options #46 ## 0.2.2 diff --git a/README.md b/README.md index b302dd8..9334f9a 100755 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ Fields: - `publishDevicesAsIndividualAccessories` option to publish devices as individual accessories. Defaults to true. - `sequencesToPublishAsAccessoriesSwitch` array of Sequences to exposes through a switch. - `publishSequencesAsIndividualAccessories` option to publish sequences as individual accessories. Defaults to true. +- `publishHomeControlButtons` set to true if you want to publish home controls as switches +- `publishHomeControlsAsIndividualAccessories` option to publish home controls as individual accessories. Defaults to true. - `TVPlatformMode` option to try TV mode . STILL WORK IN PROGRESS - NEEDS IOS 12.2 / HOMEBRIDGE 0.0.46 - `mainActivity` set the mainactivity of the TV mode - `playPauseBehavior` play/pause behavior in TV mode : if set to true, will send pause if played was set and vice-verca. Be aware that both commands must be available, and that it might be out of sync in case of external events (defaults : false - always send play command) @@ -196,10 +198,11 @@ Thanks to - [iandday] for the Harmoney WebSocket protocol implementation and understanding of it. - [lopelex] for his clean plugin. -- every tester / contributor that test, and give feedback in any way ! +- every tester / contributor that test, and give feedback in any way , and especially [gitgayhub] for Home controls in 0.6.0 [lopelex]: https://github.com/lopelex/harmony-websocket [iandday]: https://github.com/iandday/pyharmony/blob/126e4d5042883f5f718e97d30de67083deedcea0/pyharmony/client.py +[gitgayhub]: https://github.com/gitgayhub ## Donating diff --git a/harmonyAsSwitches.js b/harmonyAsSwitches.js index 1df90bc..368777a 100644 --- a/harmonyAsSwitches.js +++ b/harmonyAsSwitches.js @@ -30,7 +30,7 @@ HarmonyPlatformAsSwitches.prototype = { return activity.id != -1 || this.showTurnOffActivity; }, - readAccessories: function(data) { + readAccessories: function(data, homedata) { let activities = data.data.activity; let accessoriesToAdd = []; @@ -82,7 +82,12 @@ HarmonyPlatformAsSwitches.prototype = { } } - this.harmonyBase.setupFoundAccessories(this, accessoriesToAdd, data); + this.harmonyBase.setupFoundAccessories( + this, + accessoriesToAdd, + data, + homedata + ); }, //Cache call method @@ -173,16 +178,6 @@ HarmonyPlatformAsSwitches.prototype = { } }, - isActivityOk: function(data) { - return ( - data && data.code && data.code == 200 && data.msg && data.msg == 'OK' - ); - }, - - isActivityInProgress: function(data) { - return data && (data.code == 202 || data.code == 100); - }, - handleActivityOk: function(commandToSend) { this._currentSetAttemps = 0; @@ -262,9 +257,9 @@ HarmonyPlatformAsSwitches.prototype = { 'INFO - activityCommand : Returned from hub ' + JSON.stringify(data) ); - if (this.isActivityOk(data)) { + if (HarmonyTools.isCommandOk(data)) { this.handleActivityOk(commandToSend); - } else if (this.isActivityInProgress(data)) { + } else if (HarmonyTools.isCommandInProgress(data)) { this.log.debug( 'WARNING - activityCommand : could not SET status : ' + JSON.stringify(data) diff --git a/harmonyAsTVPlatform.js b/harmonyAsTVPlatform.js index c51350b..d2c272e 100644 --- a/harmonyAsTVPlatform.js +++ b/harmonyAsTVPlatform.js @@ -199,7 +199,7 @@ HarmonyPlatformAsTVPlatform.prototype = { return inputSourceService; }, - readAccessories: function(data) { + readAccessories: function(data, homedata) { let activities = data.data.activity; let accessoriesToAdd = []; @@ -253,7 +253,12 @@ HarmonyPlatformAsTVPlatform.prototype = { this.bindCharacteristicEventsForInputs(myHarmonyAccessory); - this.harmonyBase.setupFoundAccessories(this, accessoriesToAdd, data); + this.harmonyBase.setupFoundAccessories( + this, + accessoriesToAdd, + data, + homedata + ); }, //Cache call method @@ -414,13 +419,7 @@ HarmonyPlatformAsTVPlatform.prototype = { activityCommand: function(homebridgeAccessory, commandToSend) { this.harmonyBase.harmony.startActivity(commandToSend).then(data => { - if ( - data && - data.code && - data.code == 200 && - data.msg && - data.msg == 'OK' - ) { + if (HarmonyTools.isCommandOk(data)) { this._currentSetAttemps = 0; this.log.debug('INFO - activityCommand : command sent'); diff --git a/harmonyBase.js b/harmonyBase.js index 72fa5a0..09f2b39 100644 --- a/harmonyBase.js +++ b/harmonyBase.js @@ -39,12 +39,18 @@ HarmonyBase.prototype = { harmonyPlatform.sequencesToPublishAsAccessoriesSwitch = config['sequencesToPublishAsAccessoriesSwitch']; - harmonyPlatform.publishSequencesAsIndividualAccessories = HarmonyTools.checkParameter( config['publishSequencesAsIndividualAccessories'], true ); + harmonyPlatform.publishHomeControlButtons = + config['publishHomeControlButtons']; + harmonyPlatform.publishHomeControlsAsIndividualAccessories = HarmonyTools.checkParameter( + config['publishHomeControlsAsIndividualAccessories'], + true + ); + harmonyPlatform.cleanCache = config['cleanCache']; harmonyPlatform.showCommandsAtStartup = config['showCommandsAtStartup']; @@ -156,40 +162,55 @@ HarmonyBase.prototype = { }, HarmonyConst.DELAY_BEFORE_RECONNECT); }); + this.harmony.on('automationState', message => { + //DEBUG + //message = JSON.parse('{"type":"automation.state?notify","data":{"hue-light.harmony_virtual_button_2":{"color":{"mode":"xy","xy":{"y":0,"x":0},"temp":300,"hueSat":{"hue":0,"sat":0}},"brightness":254,"on":true,"status":0}}}'); + + harmonyPlatform.log.debug( + 'INFO - onMessage : Refreshing Home Automation Switch ' + + JSON.stringify(message.data) + ); + this.refreshHomeSwitch(harmonyPlatform, message.data); + }); + this.harmony.on('stateDigest', message => { harmonyPlatform.log.debug( 'INFO - onMessage : received message : ' + JSON.stringify(message) ); if ( - message.type === 'connect.stateDigest?get' || - (message.type === 'connect.stateDigest?notify' && - message.data.activityStatus === 2 && + (message.data.activityStatus === 2 && message.data.activityId === message.data.runningActivityList) || - (message.type === 'connect.stateDigest?notify' && - message.data.activityStatus === 0 && + (message.data.activityStatus === 0 && message.data.activityId === '-1' && message.data.runningActivityList === '') ) { - harmonyPlatform.log( + harmonyPlatform.log.debug( 'INFO - onMessage : Refreshing activity to ' + message.data.activityId ); harmonyPlatform.onMessage(message.data.activityId); } }); + this.harmony.sendTimeout = HarmonyConst.HUB_SEND_TIMEOUT; + this.harmony.connectTimeout = HarmonyConst.HUB_CONNECT_TIMEOUT; + this.harmony - .connect( - harmonyPlatform.hubIP, - HarmonyConst.HUB_CONNECT_TIMEOUT, - HarmonyConst.HUB_SEND_TIMEOUT - ) + .connect(harmonyPlatform.hubIP) .then(() => this.harmony.getConfig()) .then(response => { harmonyPlatform.log.debug( 'INFO - Hub config : ' + JSON.stringify(response) ); - harmonyPlatform.readAccessories(response); - this.numberAttemps = 0; + this.getHomeControlsAccessories(harmonyPlatform).then(responseHome => { + //DEBUG + + responseHome = JSON.parse( + ' {"cmd":"harmony.automation?getstate","code":200,"id":"0.11199321450018873","msg":"OK","data":{"hue-light.harmony_virtual_button_3":{"color":{"mode":"xy","xy":{"y":0,"x":0},"temp":300,"hueSat":{"hue":0,"sat":0}},"brightness":254,"on":true,"status":0},"hue-light.harmony_virtual_button_4":{"color":{"mode":"xy","xy":{"y":0,"x":0},"temp":300,"hueSat":{"hue":0,"sat":0}},"brightness":254,"on":false,"status":0},"hue-light.harmony_virtual_button_1":{"color":{"mode":"xy","xy":{"y":0,"x":0},"temp":300,"hueSat":{"hue":0,"sat":0}},"brightness":254,"on":false,"status":0},"hue-light.harmony_virtual_button_2":{"color":{"mode":"xy","xy":{"y":0,"x":0},"temp":300,"hueSat":{"hue":0,"sat":0}},"brightness":254,"on":false,"status":0}}}' + ); + + harmonyPlatform.readAccessories(response, responseHome); + this.numberAttemps = 0; + }); }) .catch(e => { var that = this; @@ -209,12 +230,13 @@ HarmonyBase.prototype = { }); }, - setupFoundAccessories(harmonyPlatform, accessoriesToAdd, data) { + setupFoundAccessories(harmonyPlatform, accessoriesToAdd, data, homedata) { //creating accessories this.addAccesories(harmonyPlatform, accessoriesToAdd); this.getDevicesAccessories(harmonyPlatform, data); this.getSequencesAccessories(harmonyPlatform, data); + this.handleHomeControls(harmonyPlatform, homedata); //first refresh setTimeout(function() { @@ -266,6 +288,117 @@ HarmonyBase.prototype = { } }, + refreshHomeAccessory(harmonyPlatform) { + this.getHomeControlsAccessories(harmonyPlatform).then(responseHome => { + if (responseHome && responseHome.data) { + harmonyPlatform.log.debug( + 'INFO - got home controls : ' + JSON.stringify(responseHome) + ); + this.refreshHomeSwitch(harmonyPlatform, responseHome.data); + } + }); + }, + + refreshHomeSwitch(harmonyPlatform, data) { + for (let a = 0; a < harmonyPlatform._foundAccessories.length; a++) { + let myHarmonyAccessory = harmonyPlatform._foundAccessories[a]; + + for (let s = 0; s < myHarmonyAccessory.services.length; s++) { + let service = myHarmonyAccessory.services[s]; + if (service.type == HOME_TYPE) { + let newValue = data[service.id]; + + if (newValue) { + let characteristic = service.getCharacteristic(Characteristic.On); + + harmonyPlatform.log.debug( + 'INFO - refreshHomeSwitch - Refreshing home switch ' + + service.displayName + + ' to ' + + newValue.on + ); + characteristic.updateValue(newValue.on); + } + } + } + } + }, + + handleHomeControls: function(harmonyPlatform, data) { + if (!data || !data.data) { + return; + } + + harmonyPlatform.log.debug( + 'INFO - got Home Control : ' + JSON.stringify(data) + ); + + let homeControls = data.data; + let services = []; + + var accessoriesToAdd = []; + var myHarmonyAccessory; + + if (!harmonyPlatform.publishHomeControlsAsIndividualAccessories) { + let name = harmonyPlatform.name + '-HomeControls'; + myHarmonyAccessory = this.checkAccessory(harmonyPlatform, name); + if (!myHarmonyAccessory) { + myHarmonyAccessory = this.createAccessory(harmonyPlatform, name); + accessoriesToAdd.push(myHarmonyAccessory); + } + } + + for (var key in homeControls) { + let switchName = key; + let accessoryName = harmonyPlatform.name + '-' + switchName; + + if (harmonyPlatform.devMode) { + switchName = 'DEV' + switchName; + } + + harmonyPlatform.log('INFO - Discovered Home Control : ' + switchName); + + if (harmonyPlatform.publishHomeControlsAsIndividualAccessories) { + myHarmonyAccessory = this.checkAccessory( + harmonyPlatform, + accessoryName + ); + if (!myHarmonyAccessory) { + myHarmonyAccessory = this.createAccessory( + harmonyPlatform, + accessoryName + ); + accessoriesToAdd.push(myHarmonyAccessory); + } + } + + let subType = switchName; + let service = this.getSwitchService( + harmonyPlatform, + myHarmonyAccessory, + switchName, + subType + ); + + service.HomeId = key; + service.type = HarmonyConst.HOME_TYPE; + this.bindCharacteristicEventsForSwitch(harmonyPlatform, service); + } + + //creating accessories + this.addAccesories(harmonyPlatform, accessoriesToAdd); + }, + + getHomeControlsAccessories: function(harmonyPlatform) { + if (harmonyPlatform.publishHomeControlButtons) { + harmonyPlatform.log.debug('INFO - getting home controls ...'); + return this.harmony.getAutomationCommands(); + } else { + var responseHome = {}; + return Promise.resolve(responseHome); + } + }, + getSequencesAccessories: function(harmonyPlatform, data) { if ( harmonyPlatform.sequencesToPublishAsAccessoriesSwitch && @@ -662,7 +795,13 @@ HarmonyBase.prototype = { 'set', function(value, callback) { //send command - if (value) { + if (service.type === HarmonyConst.HOME_TYPE) { + let command = {}; + command.on = value; + let commandToSend = {}; + commandToSend[service.HomeId] = command; + this.sendAutomationCommand(harmonyPlatform, commandToSend); + } else if (value) { if (service.type === HarmonyConst.DEVICE_TYPE) { let command = service.command; this.sendCommand(harmonyPlatform, command); @@ -673,14 +812,14 @@ HarmonyBase.prototype = { let command = '{"sequenceId":"' + service.sequenceId + '"}'; this.sendCommand(harmonyPlatform, command); } - } - // In order to behave like a push button reset the status to off - setTimeout(function() { - service - .getCharacteristic(Characteristic.On) - .updateValue(false, undefined); - }, HarmonyConst.DELAY_FOR_STATELESS_SWITCH_UPDATE); + // In order to behave like a push button reset the status to off + setTimeout(function() { + service + .getCharacteristic(Characteristic.On) + .updateValue(false, undefined); + }, HarmonyConst.DELAY_FOR_STATELESS_SWITCH_UPDATE); + } callback(); }.bind(this) @@ -688,12 +827,33 @@ HarmonyBase.prototype = { .on( 'get', function(callback) { - this.handleCharacteristicUpdate( - harmonyPlatform, - service.getCharacteristic(Characteristic.On), - false, - callback - ); + if (service.type === HarmonyConst.HOME_TYPE) { + this.getHomeControlsAccessories(harmonyPlatform).then( + responseHome => { + var newValue = false; + if ( + responseHome && + responseHome.data && + responseHome.data[service.controlService.id] + ) + newValue = responseHome.data[service.controlService.id].on; + + this.handleCharacteristicUpdate( + harmonyPlatform, + service.getCharacteristic(Characteristic.On), + newValue, + callback + ); + } + ); + } else { + this.handleCharacteristicUpdate( + harmonyPlatform, + service.getCharacteristic(Characteristic.On), + false, + callback + ); + } }.bind(this) ); }, @@ -706,7 +866,7 @@ HarmonyBase.prototype = { harmonyPlatform.log.debug('INFO - sendingCommand' + commandToSend); return this.harmony - .sendCommands(commandToSend) + .sendCommand(commandToSend) .then(data => { harmonyPlatform.log.debug( 'INFO - sendCommand done' + JSON.stringify(data) @@ -716,4 +876,32 @@ HarmonyBase.prototype = { harmonyPlatform.log('ERROR - sendCommand : ' + e); }); }, + + sendAutomationCommand: function(harmonyPlatform, commandToSend) { + if (!commandToSend) { + harmonyPlatform.log.debug( + 'INFO - sendAutomationCommand : Command not available ' + ); + return; + } + harmonyPlatform.log.debug( + 'INFO - sendingAutomationCommand' + JSON.stringify(commandToSend) + ); + + return this.harmony + .sendAutomationCommand(commandToSend) + .then(data => { + harmonyPlatform.log.debug( + 'INFO - sendingAutomationCommand done' + JSON.stringify(data) + ); + + if (!HarmonyTools.isCommandOk(data)) { + this.refreshHomeAccessory(harmonyPlatform); + } + }) + .catch(e => { + harmonyPlatform.log('ERROR - sendingAutomationCommand : ' + e); + this.refreshHomeAccessory(harmonyPlatform); + }); + }, }; diff --git a/harmonyConst.js b/harmonyConst.js index ba58283..f58debe 100644 --- a/harmonyConst.js +++ b/harmonyConst.js @@ -11,9 +11,10 @@ DEVICE_TYPE = 'DEVICE'; ACTIVITY_TYPE = 'ACTVIVITY'; DEVICEMACRO_TYPE = 'DEVICEMACRO'; SEQUENCE_TYPE = 'SEQUENCE'; +HOME_TYPE = 'HOME'; DEFAULT_VOLUME = 50; -HUB_CONNECT_TIMEOUT = 8000; -HUB_SEND_TIMEOUT = 3000; +HUB_CONNECT_TIMEOUT = 10000; +HUB_SEND_TIMEOUT = 30000; module.exports = { TIMEOUT_REFRESH_CURRENT_ACTIVITY, @@ -27,6 +28,7 @@ module.exports = { DEVICE_TYPE, ACTIVITY_TYPE, DEVICEMACRO_TYPE, + HOME_TYPE, DELAY_FOR_MACRO, SEQUENCE_TYPE, DEFAULT_VOLUME, diff --git a/harmonyTools.js b/harmonyTools.js index 90718df..306625a 100644 --- a/harmonyTools.js +++ b/harmonyTools.js @@ -10,7 +10,8 @@ module.exports = { return ( service.type === HarmonyConst.DEVICE_TYPE || service.type === HarmonyConst.DEVICEMACRO_TYPE || - service.type === HarmonyConst.SEQUENCE_TYPE + service.type === HarmonyConst.SEQUENCE_TYPE || + service.type === HarmonyConst.HOME_TYPE ); }, @@ -75,6 +76,16 @@ module.exports = { ); } }, + + isCommandOk: function(data) { + return ( + data && data.code && data.code == 200 && data.msg && data.msg == 'OK' + ); + }, + + isCommandInProgress: function(data) { + return data && (data.code == 202 || data.code == 100); + }, }; async function processCommand(hb, platform, command, timeToWait) { diff --git a/package.json b/package.json index 4b2693a..9cdea4a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "homekit" ], "dependencies": { - "harmony-websocket": "^1.0.8", + "harmony-websocket": "^1.0.9", "request": "^2.65.0", "websocket": "^1.0.28", "websocket-as-promised": "^0.9.0"