diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cc6c6..6f9b927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## 1.5.0 + +- [FIX] Warning with homebridge 1.3.0 #315 +- [FIX] Duplicate sequence names not handled #333 +- [NEW] devicesToPublishAsAccessoriesSwitch allow single switch to have a steta and different command for on / off - SEE README #308 #286 +- [NEW] remoteOverrideCommandsList allow command sequence #288 (numberOfCommands not supported anymore, you have to repeat the command - same syntax as in devicesToPublishAsAccessoriesSwitch) +- [NEW] new parameters to set timeout and retry polling parameters #339 + ## 1.4.1 - [FIX] Renaming mute/volumeup/volumedown does not persist even if not linked to tv accessory #313 @@ -106,7 +114,7 @@ All notable changes to this project will be documented in this file. ## 1.2.0 -- [NEW] Supports config UI X configuration interface. **Be aware that remoteOverrideCommandsList is not in the same format anymore f you edit your config through the interface.** +- [NEW] Supports config UI X configuration interface. **Be aware that remoteOverrideCommandsList is not in the same format anymore if you edit your config through the interface.** ## 1.1.1 diff --git a/README.md b/README.md index 2b0ed3b..b3d0c0a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,18 @@ This plugin is publishing harmony hub activities and devices as switches, with s 2. Install this plugin using: `npm install -g homebridge-harmony` 3. Update your Homebridge `config.json` using the sample below. +## Note about homebridge configuration + +Since this plugin can expose external accessories (TV), you should probably set ports configuration in your homebridge conf with a dedicated range, like this : + +```json + "ports": { + "start": 52100, + "end": 52150, + "comment": "This section is used to control the range of ports that separate accessory (like camera or television) should be bind to." + }, +``` + ## Migration from 0.X to 1.X **You have to move your other platforms if you have more than one in a new key : "otherPlatforms": [{ }] , see sample below. The plugin MUST be adde donly One time in your config** @@ -110,6 +122,11 @@ Fields: - `platform` **GLOBAL** must be "HarmonyHubWebSocket" (required). - `publishAllTVAsExternalAccessory` **GLOBAL** publish all TV accessory as external Accessories. This way, if another plugin on the same homebridge instance as one, the one on harmony will also be visible, but you will have to add them manually after the hub itself. Defaults to TRUE (if set to false, only second tv accessory or following will be published by this plugin as external accessories, first one will be linked to the hub and might not display a TV icon). - `cleanCache` **GLOBAL** option to clean all cached Accessory. Please use with caution, might be needed if you change names / config of the hub and there is some ghost devices in Homekit. Be sure that all your icloud sync is done while launching Homebridge with this option set to true. Set it back to false after and launch again ! It does not affect external accessories. + +- `DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS` **GLOBAL** retry timer in case of network loss (optionnal - defaults 60s). +- `HUB_CONNECT_TIMEOUT` **GLOBAL** connect timeout (optionnal - defaults 10s). +- `HUB_SEND_TIMEOUT` **GLOBAL** send timeout (optionnal - defaults 30s). + - `name` is the name of the published Platform (required). - `hubName` is the name of your hub in harmony app (optional, but mandatory if you have mutliple hubs). In case both hubName and hubIP are not set, it will discover your hub automatically, providing there is only one - `hubIP` is the static IP address of the hub (optional). A static IP address is required. @@ -166,6 +183,22 @@ will add All commands available are displayed at startup. If no name is specified, it will be added with a generated name. +If you use a "/", it will do a non stateless switch and send commands bfire the / on On, and after on Off. Be aware that it can be out of sync . + +As a sample : + +```json + "devicesToPublishAsAccessoriesSwitch" : ["Apple TV Gen 4|Button1;Play/Pause","Caisson;/","Sony PS4;/","MyDevice;/","MyDevice;Up;Up|2500;Down/Down;Down|2500;Up"] +``` + +will add + +- a switch for "Apple TV Gen 4" "Play" command on on, "Pause" command on off, named Button1, +- a powerToggle switch for the device named "Caisson", which will send PowerToggle on on and off, +- a powerOff switch only for PS4 (since there is no powerToggle nor powerOn command for it) - thus no effect +- a switch that will PowerOn on On and PowerOff on off to MyDevice since it doe not have a powerToggle and have both powerOn / PowerOff +- a switch that will send Up , then Up, then wait 2.5 seconds, then send Down to MyDevice on On and reverse on Off + **Option** `sequencesToPublishAsAccessoriesSwitch` is an array that behaves this way : - You should put the name of the sequence as it is named in harmony app, @@ -185,28 +218,9 @@ See [Logitech Harmony Sequence Configuration](https://support.myharmony.com/en-u **Option** `remoteOverrideCommandsList` is an array that behaves this way : - You should put the name of the activity as it is named in harmony app, -- Then you should put the name of the command you want to ovverride -- Then you should put the name of the targeted device -- And finally the name of the command -- Optionally, number of commands to send - -As a sample : - -(before 1.2.0) - -```json - "remoteOverrideCommandsList": { - "La TV": { - "REWIND": "Ampli;Number0", - "BACK": "TV;Back" - }, - "Un Film": { - "ARROW_LEFT": "TV;PreviousChannel" - } - } -``` - -(after 1.2.0) +- Then an Array CommandsList with : + - the name of the command you want to ovverride + - the commands like in devicesToPublishAsAccessoriesSwitch (with the name of the device first) ```json "remoteOverrideCommandsList": [ @@ -214,8 +228,8 @@ As a sample : "ActivityName": "La TV", "CommandsList": [ { - "CommandName": "REWIND", - "NewCommand": "Ampli;Number0;5" + "CommandName": "PAUSE", + "NewCommand": "Ampli;Number0;Number0" }, { "CommandName": "BACK", @@ -238,7 +252,7 @@ As a sample : will bahaves this way : - for "La TV" activity : - - override REWIND button in the remote with Number0 command for Ampli device, and send it 5 times + - override PAUSE button in the remote with Number0 command for Ampli device, and send it 5 times - override BACK button in the remote with Back Command of TV device - for "Un Film" activity : - override ARROW_LEFT in the remote with PreviousChannel of TV device diff --git a/config.schema.json b/config.schema.json index 0f644a7..d687175 100644 --- a/config.schema.json +++ b/config.schema.json @@ -22,6 +22,18 @@ "title": "Hub Name in your Harmony App", "type": "string" }, + "DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS": { + "title": "retry timer in case of network loss (optionnal - defaults 60s)", + "type": "integer" + }, + "HUB_CONNECT_TIMEOUT": { + "title": "connect timeout (optionnal - defaults 10s)", + "type": "integer" + }, + "HUB_SEND_TIMEOUT": { + "title": "send timeout (optionnal - defaults 30s)", + "type": "integer" + }, "cleanCache": { "title": "Clean cached accessories at startup", "type": "boolean" @@ -291,6 +303,21 @@ "type": "string", "required": false }, + "DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS": { + "title": "retry timer in case of network loss (optionnal - defaults 60s)", + "type": "integer", + "required": false + }, + "HUB_CONNECT_TIMEOUT": { + "title": "connect timeout (optionnal - defaults 10s)", + "type": "integer", + "required": false + }, + "HUB_SEND_TIMEOUT": { + "title": "send timeout (optionnal - defaults 30s)", + "type": "integer", + "required": false + }, "TVAccessory": { "title": "TV accessory in homekit (Defaults to true if not set)", "type": "boolean" diff --git a/harmonyAsTVKeysTools.js b/harmonyAsTVKeysTools.js index 91951b3..116fd0e 100644 --- a/harmonyAsTVKeysTools.js +++ b/harmonyAsTVKeysTools.js @@ -33,89 +33,76 @@ module.exports = { platform._currentActivity > HarmonyConst.CURRENT_ACTIVITY_NOT_SET_VALUE && platform._currentInputService !== undefined ) { - keysMap[Characteristic.RemoteKey.ARROW_UP] = this.getOverrideCommand( - platform, - 'ARROW_UP', - platform._currentInputService.DirectionUpCommand - ); - - keysMap[Characteristic.RemoteKey.ARROW_DOWN] = this.getOverrideCommand( - platform, - 'ARROW_DOWN', - platform._currentInputService.DirectionDownCommand - ); - - keysMap[Characteristic.RemoteKey.ARROW_LEFT] = this.getOverrideCommand( - platform, - 'ARROW_LEFT', - platform._currentInputService.DirectionLeftCommand - ); - - keysMap[Characteristic.RemoteKey.ARROW_RIGHT] = this.getOverrideCommand( - platform, - 'ARROW_RIGHT', - platform._currentInputService.DirectionRightCommand - ); - - keysMap[Characteristic.RemoteKey.SELECT] = this.getOverrideCommand( - platform, - 'SELECT', - getSelectKey(platform._currentInputService) - ); - - keysMap[Characteristic.RemoteKey.PLAY_PAUSE] = this.getOverrideCommand( - platform, - 'PLAY', - platform._currentInputService.PlayCommand - ); - - keysMap[Characteristic.RemoteKey.INFORMATION] = this.getOverrideCommand( - platform, - 'INFORMATION', - getInfoKey(platform._currentInputService) - ); - - keysMap[Characteristic.RemoteKey.BACK] = this.getOverrideCommand( - platform, - 'BACK', - getBackKey(platform._currentInputService) - ); - - keysMap[Characteristic.RemoteKey.EXIT] = this.getOverrideCommand( - platform, - 'EXIT', - getExitKey(platform._currentInputService) - ); - - keysMap[Characteristic.RemoteKey.REWIND] = this.getOverrideCommand( - platform, - 'REWIND', - platform._currentInputService.RewindCommand - ); - - keysMap[Characteristic.RemoteKey.FAST_FORWARD] = this.getOverrideCommand( - platform, - 'FAST_FORWARD', - platform._currentInputService.FastForwardCommand - ); - - keysMap[Characteristic.RemoteKey.NEXT_TRACK] = this.getOverrideCommand( - platform, - 'NEXT_TRACK', - platform._currentInputService.SkipForwardCommand - ); - - keysMap[Characteristic.RemoteKey.PREVIOUS_TRACK] = this.getOverrideCommand( - platform, - 'PREVIOUS_TRACK', - platform._currentInputService.SkipBackwardCommand - ); + let overrideARROWUP = this.getOverrideCommand(platform, 'ARROW_UP'); + keysMap[Characteristic.RemoteKey.ARROW_UP] = overrideARROWUP + ? overrideARROWUP + : platform._currentInputService.DirectionUpCommand; + + let overrideARROWDOWN = this.getOverrideCommand(platform, 'ARROW_DOWN'); + keysMap[Characteristic.RemoteKey.ARROW_DOWN] = overrideARROWDOWN + ? overrideARROWDOWN + : platform._currentInputService.DirectionDownCommand; + + let overrideARROWLEFT = this.getOverrideCommand(platform, 'ARROW_LEFT'); + keysMap[Characteristic.RemoteKey.ARROW_LEFT] = overrideARROWLEFT + ? overrideARROWLEFT + : platform._currentInputService.DirectionLeftCommand; + + let overrideARROWRIGHT = this.getOverrideCommand(platform, 'ARROW_RIGHT'); + keysMap[Characteristic.RemoteKey.ARROW_RIGHT] = overrideARROWRIGHT + ? overrideARROWRIGHT + : platform._currentInputService.DirectionRightCommand; + + let overrideSELECT = this.getOverrideCommand(platform, 'SELECT'); + keysMap[Characteristic.RemoteKey.SELECT] = overrideSELECT + ? overrideSELECT + : getSelectKey(platform._currentInputService); + + let overridePLAY = this.getOverrideCommand(platform, 'PLAY'); + keysMap[Characteristic.RemoteKey.PLAY_PAUSE] = overridePLAY + ? overridePLAY + : platform._currentInputService.PlayCommand; + + let overrideINFORMATION = this.getOverrideCommand(platform, 'INFORMATION'); + keysMap[Characteristic.RemoteKey.INFORMATION] = overrideINFORMATION + ? overrideINFORMATION + : getInfoKey(platform._currentInputService); + + let overrideBACK = this.getOverrideCommand(platform, 'BACK'); + keysMap[Characteristic.RemoteKey.BACK] = overrideBACK + ? overrideBACK + : getBackKey(platform._currentInputService); + + let overrideEXIT = this.getOverrideCommand(platform, 'EXIT'); + keysMap[Characteristic.RemoteKey.EXIT] = overrideEXIT + ? overrideEXIT + : getExitKey(platform._currentInputService); + + let overrideREWIND = this.getOverrideCommand(platform, 'REWIND'); + keysMap[Characteristic.RemoteKey.REWIND] = overrideREWIND + ? overrideREWIND + : platform._currentInputService.RewindCommand; + + let overrideFASTFORWARD = this.getOverrideCommand(platform, 'FAST_FORWARD'); + keysMap[Characteristic.RemoteKey.FAST_FORWARD] = overrideFASTFORWARD + ? overrideFASTFORWARD + : platform._currentInputService.FastForwardCommand; + + let overrideNEXTTRACK = this.getOverrideCommand(platform, 'NEXT_TRACK'); + keysMap[Characteristic.RemoteKey.NEXT_TRACK] = overrideNEXTTRACK + ? overrideNEXTTRACK + : platform._currentInputService.SkipForwardCommand; + + let overridePREVIOUSTRACK = this.getOverrideCommand(platform, 'PREVIOUS_TRACK'); + keysMap[Characteristic.RemoteKey.PREVIOUS_TRACK] = overridePREVIOUSTRACK + ? overridePREVIOUSTRACK + : platform._currentInputService.SkipBackwardCommand; } platform.log.debug('(' + platform.name + ')' + 'keysMap is :' + JSON.stringify(keysMap)); return keysMap; }, - getOverrideCommand: function (platform, command, defaultCommand, numberOfCommands = 1) { + getOverrideCommand: function (platform, command) { if ( platform.remoteOverrideCommandsList && platform.remoteOverrideCommandsList[platform._currentInputService.activityName] && @@ -124,49 +111,60 @@ module.exports = { let override = platform.remoteOverrideCommandsList[platform._currentInputService.activityName][command]; - let overrideArray = override.split(';'); - let device = overrideArray[0]; - let cmd = overrideArray[1]; - - if (overrideArray.length > 2) numberOfCommands = overrideArray[2]; - - let commandToSend = platform.harmonyBase.deviceCommands[[device, cmd]]; - - if (commandToSend) { - commandToSend = commandToSend + '|' + numberOfCommands; + let functionsForCommand = []; + let commands = override.split(';'); + let device = commands[0]; + + for (let l = 1, len = commands.length; l < len; l++) { + let commandTosend = commands[l].split('|'); + let cmd = platform.harmonyBase.deviceCommands[[device, commandTosend[0]]]; + if (cmd) { + if (commandTosend.length === 2) { + let fctWithDelay = cmd + '|' + commandTosend[1]; + functionsForCommand.push(fctWithDelay); + } else { + functionsForCommand.push(cmd); + } + platform.log.debug( + '(' + + platform.name + + ')' + + 'INFO - commands found for ovverride : ' + + platform._currentInputService.activityName + + '-' + + command + + ':' + + functionsForCommand + ); + } else { + platform.log( + '(' + + platform.name + + ')' + + 'WARNING - Command not found for ovverride : ' + + platform._currentInputService.activityName + + '-' + + device + + '-' + + commandTosend[0] + ); + } + } - platform.log.debug( - '(' + - platform.name + - ')' + - 'INFO - Found Override Command for ' + - command + - ' : ' + - device + - '/' + - cmd + - ' - ' + - commandToSend - ); - return commandToSend; + if (functionsForCommand.length > 0) { + return functionsForCommand; } else { - platform.log.debug( + platform.log( '(' + platform.name + ')' + - 'WARNING - Did not found Override Command for ' + - command + - ' : ' + - device + - '/' + - cmd + 'ERROR - No commands found for ovverride : ' + + platform._currentInputService.activityName + + '-' + + command ); } } - - defaultCommand = defaultCommand + '|' + numberOfCommands; - - return defaultCommand; }, }; diff --git a/harmonyBase.js b/harmonyBase.js index 9400ae4..40e19c6 100644 --- a/harmonyBase.js +++ b/harmonyBase.js @@ -26,6 +26,20 @@ HarmonyBase.prototype = { harmonyPlatform.hubIP = config['hubIP']; harmonyPlatform.hubName = config['hubName']; + harmonyPlatform.DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS = HarmonyTools.checkParameter( + config['DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS'], + HarmonyConst.DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS + ); + + harmonyPlatform.HUB_CONNECT_TIMEOUT = HarmonyTools.checkParameter( + config['HUB_CONNECT_TIMEOUT'], + HarmonyConst.HUB_CONNECT_TIMEOUT + ); + harmonyPlatform.HUB_SEND_TIMEOUT = HarmonyTools.checkParameter( + config['HUB_SEND_TIMEOUT'], + HarmonyConst.HUB_SEND_TIMEOUT + ); + harmonyPlatform.name = config['name']; harmonyPlatform.devMode = HarmonyTools.checkParameter(config['DEVMODE'], false); @@ -289,8 +303,8 @@ HarmonyBase.prototype = { } }); - this.harmony.sendTimeout = HarmonyConst.HUB_SEND_TIMEOUT; - this.harmony.connectTimeout = HarmonyConst.HUB_CONNECT_TIMEOUT; + this.harmony.sendTimeout = harmonyPlatform.HUB_SEND_TIMEOUT; + this.harmony.connectTimeout = harmonyPlatform.HUB_CONNECT_TIMEOUT; }, setupFoundAccessories(harmonyPlatform, accessoriesToAdd, data, homedata) { @@ -450,7 +464,7 @@ HarmonyBase.prototype = { ); this.numberOfErrors = 0; this.refreshCurrentActivity(harmonyPlatform, () => {}); - }, HarmonyConst.DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS); + }, harmonyPlatform.DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS); } else { if ( harmonyPlatform._currentActivity > HarmonyConst.CURRENT_ACTIVITY_NOT_SET_VALUE && @@ -689,31 +703,11 @@ HarmonyBase.prototype = { let subType = (harmonyPlatform.devMode ? 'DEV' : '') + 'GeneralVolumeSlider'; let name = subType; - if ( - harmonyPlatform.savedNames && - harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUME_TYPE] - ) { - name = harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUME_TYPE]; - } - var myHarmonyAccessory = this.checkVolumeAccessory(harmonyPlatform, accessoriesToAdd, name); let service = this.getSliderService(harmonyPlatform, myHarmonyAccessory, name, subType); service.type = HarmonyConst.GENERALVOLUME_TYPE; - //handling name if linked to TV service && external - if ( - (harmonyPlatform.mainPlatform._oneTVAdded || - harmonyPlatform.mainPlatform.publishAllTVAsExternalAccessory) && - harmonyPlatform.TVFoundAccessory && - harmonyPlatform.linkVolumeControlToTV - ) { - harmonyPlatform.bindConfiguredNameCharacteristic( - service.getCharacteristic(Characteristic.ConfiguredName), - service - ); - } - //array of commands var volumeUpCommandsMap = new Object(); var volumeDownCommandsMap = new Object(); @@ -770,19 +764,6 @@ HarmonyBase.prototype = { let volumeUpName = volumeUpSubType; let volumeDownName = volumeDownSubType; - if ( - harmonyPlatform.savedNames && - harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUMEUP_TYPE] - ) { - volumeUpName = harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUMEUP_TYPE]; - } - if ( - harmonyPlatform.savedNames && - harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUMEDOWN_TYPE] - ) { - volumeDownName = harmonyPlatform.savedNames[HarmonyConst.GENERALVOLUMEDOWN_TYPE]; - } - // Create the accesories and add them to our array to add below var volumeUpAccessory = this.checkVolumeAccessory( @@ -816,23 +797,6 @@ HarmonyBase.prototype = { ); volumeDownService.type = HarmonyConst.GENERALVOLUMEDOWN_TYPE; - //handling name if linked to TV service && external - if ( - (harmonyPlatform.mainPlatform._oneTVAdded || - harmonyPlatform.mainPlatform.publishAllTVAsExternalAccessory) && - harmonyPlatform.TVFoundAccessory && - harmonyPlatform.linkVolumeControlToTV - ) { - harmonyPlatform.bindConfiguredNameCharacteristic( - volumeUpService.getCharacteristic(Characteristic.ConfiguredName), - volumeUpService - ); - harmonyPlatform.bindConfiguredNameCharacteristic( - volumeDownService.getCharacteristic(Characteristic.ConfiguredName), - volumeDownService - ); - } - //array of commands var volumeUpCommandsMap = new Object(); var volumeDownCommandsMap = new Object(); @@ -894,27 +858,11 @@ HarmonyBase.prototype = { let name = subType; var accessoriesToAdd = []; - if (harmonyPlatform.savedNames && harmonyPlatform.savedNames[HarmonyConst.GENERALMUTE_TYPE]) { - name = harmonyPlatform.savedNames[HarmonyConst.GENERALMUTE_TYPE]; - } var myHarmonyAccessory = this.checkVolumeAccessory(harmonyPlatform, accessoriesToAdd, name); let service = this.getSwitchService(harmonyPlatform, myHarmonyAccessory, name, subType); service.type = HarmonyConst.GENERALMUTE_TYPE; - //handling name if linked to TV service && external - if ( - (harmonyPlatform.mainPlatform._oneTVAdded || - harmonyPlatform.mainPlatform.publishAllTVAsExternalAccessory) && - harmonyPlatform.TVFoundAccessory && - harmonyPlatform.linkVolumeControlToTV - ) { - harmonyPlatform.bindConfiguredNameCharacteristic( - service.getCharacteristic(Characteristic.ConfiguredName), - service - ); - } - //array of commands var muteCommandsMap = new Object(); let activities = data.data.activity; @@ -975,11 +923,14 @@ HarmonyBase.prototype = { for (let i = 0, len = sequences.length; i < len; i++) { let switchName = sequences[i].name; + if (harmonyPlatform.sequencesToPublishAsAccessoriesSwitch.includes(switchName)) { if (harmonyPlatform.devMode) { switchName = 'DEV' + switchName; } + switchName = switchName + '(' + sequences[i].id + ')'; + harmonyPlatform.log( '(' + harmonyPlatform.name + ')' + 'INFO - Discovered sequence : ' + switchName ); @@ -1122,7 +1073,8 @@ HarmonyBase.prototype = { harmonyPlatform, controlGroup, device, - customSwitchName + customSwitchName, + isStateless ) { let accessoriesToAdd = []; @@ -1133,12 +1085,22 @@ HarmonyBase.prototype = { ); let foundToggle = false; + let foundNonStateless = false; let commandFunctions = this.populateCommands(harmonyPlatform, controlGroup, switchName); if (commandFunctions.some((e) => e.key == 'PowerToggle')) { foundToggle = true; } + if ( + !foundToggle && + commandFunctions.some((e) => e.key == 'PowerOn') && + commandFunctions.some((e) => e.key == 'PowerOff') && + !isStateless + ) { + foundNonStateless = true; + } + if (commandFunctions.length == 0) { harmonyPlatform.log( '(' + harmonyPlatform.name + ')' + 'Error - No function found for ' + switchName @@ -1147,7 +1109,7 @@ HarmonyBase.prototype = { for (let j = 0, len = commandFunctions.length; j < len; j++) { if ((foundToggle && commandFunctions[j].key === 'PowerToggle') || !foundToggle) { if (harmonyPlatform.publishDevicesAsIndividualAccessories) { - let name = switchName + '-' + commandFunctions[j].key; + let name = switchName + '-' + foundNonStateless ? 'Power' : commandFunctions[j].key; myHarmonyAccessory = this.checkAccessory(harmonyPlatform, name); if (!myHarmonyAccessory) { myHarmonyAccessory = this.createAccessory(harmonyPlatform, name); @@ -1158,7 +1120,7 @@ HarmonyBase.prototype = { harmonyPlatform._confirmedAccessories.push(myHarmonyAccessory); } - let subType = switchName + '-' + commandFunctions[j].key; + let subType = switchName + '-' + foundNonStateless ? 'Power' : commandFunctions[j].key; let service = this.getSwitchService( harmonyPlatform, myHarmonyAccessory, @@ -1168,7 +1130,20 @@ HarmonyBase.prototype = { service.deviceId = device.id; service.type = HarmonyConst.DEVICE_TYPE; - service.command = commandFunctions[j].value; + + if (!isStateless) { + if (commandFunctions[j].key === 'PowerToggle') { + service.command = commandFunctions[j].value; + service.offCommand = commandFunctions[j].value; + } else if (commandFunctions[j].key === 'PowerOn') { + service.command = commandFunctions[j].value; + } else if (commandFunctions[j].key === 'PowerOff') { + service.offCommand = commandFunctions[j].value; + } + } else { + service.command = commandFunctions[j].value; + } + service.isStateless = isStateless; harmonyPlatform._confirmedServices.push(service); this.bindCharacteristicEventsForSwitch(harmonyPlatform, service); @@ -1194,32 +1169,44 @@ HarmonyBase.prototype = { '(' + harmonyPlatform.name + ')' + 'INFO - Discovered Device : ' + switchName ); let functionsForSwitch = []; + let functionsOffForSwitch = []; let functionsKey = ''; - for (let l = 1, len = commands.length; l < len; l++) { - for (let j = 0, len = controlGroup.length; j < len; j++) { - let functions = controlGroup[j].function; - for (let k = 0, len = functions.length; k < len; k++) { - let commandTosend = commands[l].split('|'); + //check stateless + var OnOffcommands = commands.split('/'); - if (functions[k].name === commandTosend[0]) { - harmonyPlatform.log.debug( - '(' + - harmonyPlatform.name + - ')' + - 'INFO - Activating Macro ' + - commandTosend[0] + - ' for ' + - switchName - ); + for (let a = 0, len = OnOffcommands.length; a < len; a++) { + let commands = OnOffcommands[a].split(';'); + + for (let l = a == 0 ? 1 : 0, len = commands.length; l < len; l++) { + for (let j = 0, len = controlGroup.length; j < len; j++) { + let functions = controlGroup[j].function; + for (let k = 0, len = functions.length; k < len; k++) { + let commandTosend = commands[l].split('|'); + + if (functions[k].name === commandTosend[0]) { + harmonyPlatform.log.debug( + '(' + + harmonyPlatform.name + + ')' + + 'INFO - Activating Macro ' + + commandTosend[0] + + ' for ' + + switchName + ); - if (commandTosend.length === 2) { - let fctWithDelay = functions[k].action + '|' + commandTosend[1]; - functionsForSwitch.push(fctWithDelay); - } else { - functionsForSwitch.push(functions[k].action); + if (commandTosend.length === 2) { + let fctWithDelay = functions[k].action + '|' + commandTosend[1]; + a == 0 + ? functionsForSwitch.push(fctWithDelay) + : functionsOffForSwitch.push(fctWithDelay); + } else { + a == 0 + ? functionsForSwitch.push(functions[k].action) + : functionsOffForSwitch.push(functions[k].action); + } + functionsKey = functionsKey + commandTosend[0]; } - functionsKey = functionsKey + commandTosend[0]; } } } @@ -1253,6 +1240,12 @@ HarmonyBase.prototype = { service.deviceId = device.id; service.type = HarmonyConst.DEVICEMACRO_TYPE; service.command = JSON.stringify(functionsForSwitch); + if (functionsOffForSwitch.length > 0) { + service.offCommand = JSON.stringify(functionsOffForSwitch); + service.isStateless = false; + } else { + service.isStateless = true; + } harmonyPlatform._confirmedServices.push(service); this.bindCharacteristicEventsForSwitch(harmonyPlatform, service); @@ -1297,14 +1290,17 @@ HarmonyBase.prototype = { for (let i = 0, len = devices.length; i < len; i++) { let nameSwitchArray = commands[0].split('|'); let ServiceName = nameSwitchArray[0]; - let customSwitchName = nameSwitchArray.length > 1 ? nameSwitchArray[1] : undefined; + if (devices[i].label === ServiceName) { + let customSwitchName = nameSwitchArray.length > 1 ? nameSwitchArray[1] : undefined; //check functions - let controlGroup = devices[i].controlGroup; //default mode - if (commands.length === 1) { + if (commands.length === 1 || (commands.length === 2 && commands[1] === '/')) { + var isStateless = + harmonyPlatform.devicesToPublishAsAccessoriesSwitch[c].split('/').length == 1; + accessoriesToAdd.push.apply( accessoriesToAdd, this.handleDefaultCommandMode( @@ -1312,24 +1308,27 @@ HarmonyBase.prototype = { harmonyPlatform, controlGroup, devices[i], - customSwitchName + customSwitchName, + isStateless ) ); } //specifc command or list mode else { + var commandsToparse = harmonyPlatform.devicesToPublishAsAccessoriesSwitch[c]; accessoriesToAdd.push.apply( accessoriesToAdd, this.handleSpecificCommandMode( myHarmonyAccessory, harmonyPlatform, - commands, + commandsToparse, controlGroup, devices[i], customSwitchName ) ); } + break; } } } @@ -1342,12 +1341,16 @@ HarmonyBase.prototype = { //ACCESSORIES, SERVICES AND CHARACERISTICS handleCharacteristicUpdate: function (harmonyPlatform, characteristic, value, callback) { if (harmonyPlatform._currentActivity == HarmonyConst.CURRENT_ACTIVITY_NOT_SET_VALUE) { - this.updateCharacteristicToErr(characteristic, callback); + if (callback) { + callback(1); + } + //this.updateCharacteristicToErr(characteristic, callback); } else { this.updateCharacteristic(characteristic, value, callback); } }, + /* updateCharacteristicToErr: function (characteristic, callback) { try { if (callback) { @@ -1359,6 +1362,7 @@ HarmonyBase.prototype = { characteristic.updateValue(undefined); } }, + */ updateCharacteristic: function (characteristic, characteristicValue, callback) { try { @@ -1412,7 +1416,7 @@ HarmonyBase.prototype = { myHarmonyAccessory.name = fullName; myHarmonyAccessory.model = harmonyPlatform.name; - myHarmonyAccessory.manufacturer = 'Harmony'; + myHarmonyAccessory.manufacturer = 'Logitech'; myHarmonyAccessory.serialNumber = uuid; /* @@ -1474,12 +1478,20 @@ HarmonyBase.prototype = { let commandToSend = {}; commandToSend[service.HomeId] = command; this.sendAutomationCommand(harmonyPlatform, commandToSend); - } else if (value) { + } else if (value || !service.isStateless) { if (service.type === HarmonyConst.DEVICE_TYPE) { let command = service.command; + if (!service.isStateless && !value) { + command = service.offCommand; + } this.sendCommand(harmonyPlatform, command); } else if (service.type === HarmonyConst.DEVICEMACRO_TYPE) { - let commands = JSON.parse(service.command); + let command = service.command; + if (!service.isStateless && !value) { + command = service.offCommand; + } + + let commands = JSON.parse(command); HarmonyTools.processCommands(this, harmonyPlatform, commands); } else if (service.type === HarmonyConst.SEQUENCE_TYPE) { let command = '{"sequenceId":"' + service.sequenceId + '"}'; @@ -1517,11 +1529,13 @@ HarmonyBase.prototype = { } // In order to behave like a push button reset the status to off - HarmonyTools.resetCharacteristic( - service, - Characteristic.On, - HarmonyConst.DELAY_FOR_STATELESS_SWITCH_UPDATE - ); + if (service.isStateless === undefined || service.isStateless === true) { + HarmonyTools.resetCharacteristic( + service, + Characteristic.On, + HarmonyConst.DELAY_FOR_STATELESS_SWITCH_UPDATE + ); + } } callback(); @@ -1542,10 +1556,12 @@ HarmonyBase.prototype = { ); }); } else { + let stateless = (service.isStateless === undefined) | service.isStateless; + this.handleCharacteristicUpdate( harmonyPlatform, service.getCharacteristic(Characteristic.On), - false, + stateless ? false : service.getCharacteristic(Characteristic.On).value, callback ); } diff --git a/harmonyConst.js b/harmonyConst.js index f8126a6..e98d227 100644 --- a/harmonyConst.js +++ b/harmonyConst.js @@ -21,7 +21,7 @@ DEFAULT_VOLUME = 50; HUB_CONNECT_TIMEOUT = 10000; HUB_SEND_TIMEOUT = 30000; MAX_SOCKET_ERROR = 5; -DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS = 20000; +DELAY_BEFORE_RETRY_AFTER_NETWORK_LOSS = 60000; DELAY_FOR_AUTO_DISCOVERY = 6000; module.exports = { diff --git a/harmonySubPlatform.js b/harmonySubPlatform.js index 1934a6f..aa6e48f 100644 --- a/harmonySubPlatform.js +++ b/harmonySubPlatform.js @@ -397,7 +397,6 @@ HarmonySubPlatform.prototype = { Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE ) - //.setCharacteristic(Characteristic.ActiveIdentifier, -1) .setCharacteristic(Characteristic.Active, false); this.bindCharacteristicEventsForTV(accessory); @@ -504,7 +503,7 @@ HarmonySubPlatform.prototype = { this.harmonyBase.handleCharacteristicUpdate( this, this.mainService.getCharacteristic(Characteristic.ActiveIdentifier), - this._currentInputService !== undefined ? this._currentInputService.activityId : -1, + HarmonyTools.transformActivityIdToActiveIdentifier(this._currentInputService), null ); }, @@ -525,7 +524,7 @@ HarmonySubPlatform.prototype = { service.type == HarmonyConst.GENERALVOLUMEUP_TYPE || service.type == HarmonyConst.GENERALVOLUMEDOWN_TYPE ) - this.refreshService(service, undefined); + this.refreshService(service, false); } } } @@ -560,7 +559,7 @@ HarmonySubPlatform.prototype = { refreshCurrentActivityOnSubPlatform: function (response) { this._currentActivityLastUpdate = Date.now(); - if (response == undefined) return; + if (response === undefined) return; this._currentActivity = response; this.localRefresh(); @@ -687,14 +686,14 @@ HarmonySubPlatform.prototype = { this.playStatus[this._currentActivity] = ''; } else { this.log.debug('(' + this.name + ')' + 'INFO - sending PauseCommand for PLAY_PAUSE'); - this.harmonyBase.sendCommand( - this, - HarmonyAsTVKeysTools.getOverrideCommand( - this, - 'PAUSE', - this._currentInputService.PauseCommand - ) - ); + + let overridePAUSE = HarmonyAsTVKeysTools.getOverrideCommand(this, 'PAUSE'); + if (!overridePAUSE) { + this.harmonyBase.sendCommand(this, this._currentInputService.PauseCommand); + } else { + HarmonyTools.processCommands(this.harmonyBase, this, overridePAUSE); + } + this.playStatus[this._currentActivity] = 'PAUSED'; } }, @@ -724,12 +723,12 @@ HarmonySubPlatform.prototype = { this.name + ')' + 'INFO - refreshCharacteristic : updating Characteristic.ActiveIdentifier to ' + - (this._currentInputService !== undefined ? this._currentInputService.activityId : -1) + HarmonyTools.transformActivityIdToActiveIdentifier(this._currentInputService) ); this.harmonyBase.handleCharacteristicUpdate( this, characteristic, - this._currentInputService !== undefined ? this._currentInputService.activityId : -1, + HarmonyTools.transformActivityIdToActiveIdentifier(this._currentInputService), callback ); } @@ -740,7 +739,7 @@ HarmonySubPlatform.prototype = { if (characteristic.UUID == Characteristic.Active.UUID) { this.harmonyBase.handleCharacteristicUpdate(this, characteristic, false, callback); } else if (characteristic.UUID == Characteristic.ActiveIdentifier.UUID) { - this.harmonyBase.handleCharacteristicUpdate(this, characteristic, -1, callback); + this.harmonyBase.handleCharacteristicUpdate(this, characteristic, 0, callback); } } }); @@ -770,8 +769,9 @@ HarmonySubPlatform.prototype = { //we push back the execution to let the second event be taken care of in case of switching on with a dedicated input. setTimeout(() => { if (this._currentInputService == undefined) { - var currentActivity = service.getCharacteristic(Characteristic.ActiveIdentifier) - .value; + var currentActivity = HarmonyTools.transformActiveIdentifierToActivityId( + service.getCharacteristic(Characteristic.ActiveIdentifier).value + ); if (currentActivity <= 0) { this.log.debug( @@ -823,7 +823,10 @@ HarmonySubPlatform.prototype = { this.log.debug( '(' + this.name + ')' + 'INFO - SET Characteristic.ActiveIdentifier ' + value ); - this.sendInputCommand(homebridgeAccessory, '' + value); + this.sendInputCommand( + homebridgeAccessory, + '' + HarmonyTools.transformActiveIdentifierToActivityId(value) + ); callback(null); }.bind(this) ); @@ -854,8 +857,19 @@ HarmonySubPlatform.prototype = { if (newValue === Characteristic.RemoteKey.PLAY_PAUSE) { this.handlePlayPause(); } else if (this.keysMap[newValue]) { - this.log.debug('(' + this.name + ')' + 'INFO - sending command for ' + newValue); - this.harmonyBase.sendCommand(this, this.keysMap[newValue]); + this.log.debug( + '(' + + this.name + + ')' + + 'INFO - sending command ' + + this.keysMap[newValue] + + 'for ' + + newValue + ); + + if (Array.isArray(this.keysMap[newValue])) + HarmonyTools.processCommands(this.harmonyBase, this, this.keysMap[newValue]); + else this.harmonyBase.sendCommand(this, this.keysMap[newValue]); } else { this.log.debug('(' + this.name + ')' + 'INFO - no command to send for ' + newValue); } @@ -871,14 +885,13 @@ HarmonySubPlatform.prototype = { function (value, callback) { if (this._currentInputService !== undefined) { this.log.debug('(' + this.name + ')' + 'INFO - SET Characteristic.Mute : ' + value); - this.harmonyBase.sendCommand( - this, - HarmonyAsTVKeysTools.getOverrideCommand( - this, - 'MUTE', - this._currentInputService.MuteCommand - ) - ); + + let overrideMUTE = HarmonyAsTVKeysTools.getOverrideCommand(this, 'MUTE'); + if (!overrideMUTE) { + this.harmonyBase.sendCommand(this, this._currentInputService.MuteCommand); + } else { + HarmonyTools.processCommands(this.harmonyBase, this, overrideMUTE); + } } callback(null); }.bind(this) @@ -902,25 +915,29 @@ HarmonySubPlatform.prototype = { '(' + this.name + ')' + 'INFO - SET Characteristic.VolumeSelector : ' + value ); if (value === Characteristic.VolumeSelector.DECREMENT) { - this.harmonyBase.sendCommand( - this, - HarmonyAsTVKeysTools.getOverrideCommand( + let overrideVOLUMEDOWN = HarmonyAsTVKeysTools.getOverrideCommand(this, 'VOLUME_DOWN'); + if (!overrideVOLUMEDOWN) { + this.harmonyBase.sendCommand( this, - 'VOLUME_DOWN', - this._currentInputService.VolumeDownCommand, - this.numberOfCommandsSentForVolumeControl - ) - ); + this._currentInputService.VolumeDownCommand + + '|' + + this.numberOfCommandsSentForVolumeControl + ); + } else { + HarmonyTools.processCommands(this.harmonyBase, this, overrideVOLUMEDOWN); + } } else { - this.harmonyBase.sendCommand( - this, - HarmonyAsTVKeysTools.getOverrideCommand( + let overrideVOLUMEUP = HarmonyAsTVKeysTools.getOverrideCommand(this, 'VOLUME_UP'); + if (!overrideVOLUMEUP) { + this.harmonyBase.sendCommand( this, - 'VOLUME_UP', - this._currentInputService.VolumeUpCommand, - this.numberOfCommandsSentForVolumeControl - ) - ); + this._currentInputService.VolumeUpCommand + + '|' + + this.numberOfCommandsSentForVolumeControl + ); + } else { + HarmonyTools.processCommands(this.harmonyBase, this, overrideVOLUMEUP); + } } } callback(null); @@ -962,7 +979,6 @@ HarmonySubPlatform.prototype = { var idConf = 0; if (service.UUID == Service.InputSource.UUID) idConf = service.activityId; - else if (service.type !== undefined) idConf = service.type; this.savedNames[idConf] = value; fs.writeFile(this.savedNamesFile, JSON.stringify(this.savedNames), (err) => { @@ -1076,14 +1092,13 @@ HarmonySubPlatform.prototype = { this.log.debug( '(' + this.name + ')' + 'INFO - SET Characteristic.PowerModeSelection : ' + value ); - this.harmonyBase.sendCommand( - this, - HarmonyAsTVKeysTools.getOverrideCommand( - this, - 'SETUP', - this._currentInputService.SetupCommand - ) - ); + + let overrideSETUP = HarmonyAsTVKeysTools.getOverrideCommand(this, 'SETUP'); + if (!overrideSETUP) { + this.harmonyBase.sendCommand(this, this._currentInputService.SetupCommand); + } else { + HarmonyTools.processCommands(this.harmonyBase, this, overrideSETUP); + } } callback(null); }.bind(this) diff --git a/harmonyTools.js b/harmonyTools.js index c81a471..0801092 100644 --- a/harmonyTools.js +++ b/harmonyTools.js @@ -51,6 +51,17 @@ module.exports = { } }, + transformActivityIdToActiveIdentifier: function (currentInputService) { + if (currentInputService !== undefined && currentInputService.activityId > 0) + return currentInputService.activityId; + else return 0; + }, + + transformActiveIdentifierToActivityId: function (activeIdentifier) { + if (activeIdentifier == 0) return -1; + else return activeIdentifier; + }, + checkTurnOffActivityOption: function (str) { if (str == null || str == undefined) return false; diff --git a/package.json b/package.json index 1b52600..5c86f77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-harmony", - "version": "1.4.1", + "version": "1.5.0", "author": "Nicolas Dujardin", "description": "Publish your harmony activities as homekit accessories", "main": "index.js",