diff --git a/.vscode/launch.json b/.vscode/launch.json index 76b1657..ee206d5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,16 +1,19 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "preLaunchTask": "build and install", - "program": "/Users/brandonwatson/.npm-global/bin/homebridge", - "sourceMaps": true, - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "preLaunchTask": "build", + "program": "/Users/brandonwatson/.nvm/versions/node/v14.15.0/lib/node_modules/homebridge/bin/homebridge", + "env": { + "HOMEBRIDGE_OPTS": "/Users/brandonwatson/.homebridge" + }, + "sourceMaps": true + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ad92e87..ab4213d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,17 +1,18 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "build", - "problemMatcher": [] - }, - { - "type": "shell", - "label": "build and install", - "command": "npm run build&&sudo npm install -g --unsafe-perm ." - } - ] -} \ No newline at end of file + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "label": "build", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "build and install", + "command": "npm run build&&sudo npm install -g --unsafe-perm ." + } + ] +} diff --git a/config.json b/config.json deleted file mode 100644 index 8e0df6f..0000000 --- a/config.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:74", - "port": 51826, - "pin": "031-45-154" - }, - "platforms": [ - { - "platform": "websocket", - "name": "websocket", - "port": 4050 - }, - { - "platform": "HarmonyHubMatrix", - "hubIp": "192.168.1.14", - "Matrix": { - "DeviceName": "Gefen AV Switch", - "Inputs": [ - { - "InputNumber": "1", - "InputDevice": "Microsoft Xbox One" - }, - { - "InputNumber": "5", - "InputDevice": "Apple TV Gen 4" - }, - { - "InputNumber": "3", - "InputDevice": "Chromecast" - }, - { - "InputNumber": "2", - "InputDevice": "Sony PS4" - } - ], - "Outputs": [ - { - "OutputLetter": "B", - "OutputDevice": "LG TV" - }, - { - "OutputLetter": "C", - "OutputDevice": "Vizio TV" - }, - { - "OutputLetter": "D", - "OutputDevice": "Westinghouse TV" - } - ] - }, - "ControlUnits": [ - { - "DisplayName": "Living Room", - "Activities": [ - { - "DisplayName": "Chromecast", - "DeviceSetupList": [ - { - "DeviceName": "LG TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "JVC AV Receiver", - "Input": "Dvd" - }, - { - "DeviceName": "Chromecast", - "Input": [] - } - ], - "VolumeDevice": "JVC AV Receiver", - "ControlDevice": "Chromecast", - "OutputDevice": "LG TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Play Xbox One", - "DeviceSetupList": [ - { - "DeviceName": "Microsoft Xbox One", - "Input": [] - }, - { - "DeviceName": "LG TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "JVC AV Receiver", - "Input": "Dvd" - } - ], - "VolumeDevice": "JVC AV Receiver", - "ControlDevice": "Microsoft Xbox One", - "OutputDevice": "LG TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Play PS4", - "DeviceSetupList": [ - { - "DeviceName": "Sony PS4", - "Input": [] - }, - { - "DeviceName": "LG TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "JVC AV Receiver", - "Input": "Dvd" - } - ], - "VolumeDevice": "JVC AV Receiver", - "ControlDevice": "Sony PS4", - "OutputDevice": "LG TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Apple TV", - "DeviceSetupList": [ - { - "DeviceName": "Apple TV Gen 4", - "Input": [] - }, - { - "DeviceName": "LG TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "JVC AV Receiver", - "Input": "Dvd" - } - ], - "VolumeDevice": "JVC AV Receiver", - "ControlDevice": "Apple TV Gen 4", - "OutputDevice": "LG TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Listen to Music", - "DeviceSetupList": [ - { - "DeviceName": "JVC AV Receiver", - "Input": "CD" - } - ], - "VolumeDevice": "JVC AV Receiver", - "ControlDevice": [], - "OutputDevice": [], - "UseMatrix": "false" - } - ] - }, - { - "DisplayName": "Bedroom", - "Activities": [ - { - "DisplayName": "Chromecast", - "DeviceSetupList": [ - { - "DeviceName": "Vizio TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "Harman Kardon AV Receiver", - "Input": "Game" - }, - { - "DeviceName": "Chromecast", - "Input": [] - } - ], - "VolumeDevice": "Harman Kardon AV Receiver", - "ControlDevice": "Chromecast", - "OutputDevice": "Vizio TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Play Xbox One", - "DeviceSetupList": [ - { - "DeviceName": "Microsoft Xbox One", - "Input": [] - }, - { - "DeviceName": "Vizio TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "Harman Kardon AV Receiver", - "Input": "Game" - } - ], - "VolumeDevice": "Harman Kardon AV Receiver", - "ControlDevice": "Microsoft Xbox One", - "OutputDevice": "Vizio TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Play PS4", - "DeviceSetupList": [ - { - "DeviceName": "Sony PS4", - "Input": [] - }, - { - "DeviceName": "Vizio TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "Harman Kardon AV Receiver", - "Input": "Dvd" - } - ], - "VolumeDevice": "Harman Kardon AV Receiver", - "ControlDevice": "Sony PS4", - "OutputDevice": "LG TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Apple TV", - "DeviceSetupList": [ - { - "DeviceName": "Apple TV Gen 4", - "Input": [] - }, - { - "DeviceName": "Vizio TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "Harman Kardon AV Receiver", - "Input": "Game" - } - ], - "VolumeDevice": "Harman Kardon AV Receiver", - "ControlDevice": "Apple TV Gen 4", - "OutputDevice": "Vizio TV", - "UseMatrix": "true" - } - ] - }, - { - "DisplayName": "Kitchen", - "Activities": [ - { - "DisplayName": "Chromecast", - "DeviceSetupList": [ - { - "DeviceName": "Chromecast", - "Input": [] - }, - { - "DeviceName": "Westinghouse TV", - "Input": "Hdmi1" - } - ], - "VolumeDevice": "Westinghouse TV", - "ControlDevice": "Chromecast", - "OutputDevice": "Westinghouse TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Watch Apple Tv", - "DeviceSetupList": [ - { - "DeviceName": "Apple TV Gen 4", - "Input": [] - }, - { - "DeviceName": "Westinghouse TV", - "Input": "Hdmi1" - } - ], - "VolumeDevice": "Westinghouse TV", - "ControlDevice": "Apple TV Gen 4", - "OutputDevice": "Westinghouse TV", - "UseMatrix": "true" - }, - { - "DisplayName": "Play Xbox One", - "DeviceSetupList": [ - { - "DeviceName": "Westinghouse TV", - "Input": "Hdmi1" - }, - { - "DeviceName": "Microsoft Xbox One", - "Input": [] - } - ], - "VolumeDevice": "Westinghouse TV", - "ControlDevice": "Microsoft Xbox One", - "OutputDevice": "Westinghouse TV", - "UseMatrix": "true" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 6d228b6..4c0f7d3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash remote_user="bmw" -remote_server="192.168.1.31" +remote_server="linuxhost.me" deploy_location="/home/bmw/homebridge-harmony-control" homebridge_location="/var/lib/homebridge/" diff --git a/package-lock.json b/package-lock.json index 948adad..3cf3fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -865,10 +865,36 @@ "to-fast-properties": "^2.0.0" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "@homebridge/ciao": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0.tgz", + "integrity": "sha512-fpFUy/1PQ5eMPiTdRewoA/gnFCLBkBlNsFJpHO8/Wk/p1fQM0YLMDdyvR956CF0QdArhz2SBB3VeGmgVkOrLFw==", + "requires": { + "debug": "^4.3.1", + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.19", + "tslib": "^2.0.3" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@types/node": { "version": "12.12.37", @@ -903,16 +929,6 @@ "sprintf-js": "~1.0.2" } }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" - }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -931,14 +947,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "requires": { - "array-filter": "^1.0.0" - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -976,18 +984,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "bonjour-hap": { - "version": "3.5.9", - "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.5.9.tgz", - "integrity": "sha512-uqph9WOC1k9ZoocprvdfhS/tZmzQpMa+u64u/B2jlsZTAbK4QSzlaw/qW7lON6uCtbAZPxPWmDveMfFHlpLgIg==", - "requires": { - "array-flatten": "^2.1.2", - "deep-equal": "^2.0.2", - "ip": "^1.1.5", - "multicast-dns": "^7.2.2", - "multicast-dns-service-types": "^1.1.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1067,9 +1063,9 @@ } }, "commander": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz", - "integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" }, "concat-map": { "version": "0.0.1", @@ -1132,30 +1128,9 @@ } }, "decimal.js": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", - "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==" - }, - "deep-equal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", - "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.1.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" }, "define-properties": { "version": "1.1.3", @@ -1176,15 +1151,6 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "dns-packet": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-4.2.0.tgz", - "integrity": "sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==", - "requires": { - "ip": "^1.1.5", - "safe-buffer": "^5.1.1" - } - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1217,20 +1183,6 @@ "string.prototype.trimright": "^2.1.1" } }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -1328,9 +1280,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-srp-hap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.1.tgz", - "integrity": "sha512-dClwnyfRd3BZxu3KAdhvAayozq7fLazXGlDc4HAHJV1M+olqGKAT52pygXQu5UiDSHxz/WB3KRvsojqQ5zmXOA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.2.tgz", + "integrity": "sha512-wABhZRrFhlovqJQ1HygOUB4R6WZW2hmlpvVYh2dVCy8BPLabDrB/Tu6XI3B4QfmhtHk8s1OeiFqJHY7FBsphug==" }, "find-up": { "version": "2.1.0", @@ -1340,11 +1292,6 @@ "locate-path": "^2.0.0" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1371,6 +1318,11 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "futoin-hkdf": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz", + "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==" + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -1404,14 +1356,15 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "hap-nodejs": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.6.11.tgz", - "integrity": "sha512-mYy+m/ghmnEGwr+g7UeanEu+vYdTWddCM3paycbljBwcmYGpu13cqyx0dGZ36tWtWt8WZeCEl9FoE10MlYO0oQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.8.3.tgz", + "integrity": "sha512-1bX0UPmIfthYgF43M0rhNsyLayPVXAPvqgwUBfTUNSw4XuesepfbZFnhT8eYZqqClQorKaZr6FeHWw785FQ1Tg==", "requires": { - "bonjour-hap": "~3.5.8", + "@homebridge/ciao": "~1.1.0", "debug": "^4.1.1", "decimal.js": "^10.2.0", - "fast-srp-hap": "2.0.1", + "fast-srp-hap": "2.0.2", + "futoin-hkdf": "~1.3.2", "ip": "^1.1.3", "node-persist": "^0.0.11", "tweetnacl": "^1.0.3" @@ -1468,32 +1421,31 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "homebridge": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.0.4.tgz", - "integrity": "sha512-L0ZMefUVqFoZ7kU03nFCgR5L9UiGPoej6aS/u4zuQSjalXOn/c1aO9oVRkatL60rGlJtpHBzCp2io0b7Vez3RA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.2.4.tgz", + "integrity": "sha512-uyhLgJumhivAQrauoH5EfxKC2hcX2r8fC2ckAScBx3M7/+XuobHVNYF7yNND69mlg+Jd/KBfaF5zS3ckVl807g==", "requires": { - "chalk": "^3.0.0", - "commander": "5.0.0", - "hap-nodejs": "0.6.11", + "chalk": "^4.1.0", + "commander": "5.1.0", + "hap-nodejs": "~0.8.3", "node-persist": "^0.0.11", "qrcode-terminal": "^0.12.0", - "semver": "^7.1.3", - "source-map-support": "^0.5.16" + "semver": "^7.3.2", + "source-map-support": "^0.5.19" }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1518,14 +1470,17 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { "has-flag": "^4.0.0" } @@ -1571,21 +1526,6 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" - }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==" - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" - }, "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", @@ -1596,16 +1536,6 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==" - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" - }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", @@ -1614,16 +1544,6 @@ "has": "^1.0.3" } }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==" - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -1632,37 +1552,11 @@ "has-symbols": "^1.0.1" } }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==" - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1762,6 +1656,14 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", @@ -1802,20 +1704,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "multicast-dns": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.2.tgz", - "integrity": "sha512-XqSMeO8EWV/nOXOpPV8ztIpNweVfE1dSpz6SQvDPp71HD74lMXjt4m/mWB1YBMG0kHtOodxRWc5WOb/UNN1A5g==", - "requires": { - "dns-packet": "^4.0.0", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" - }, "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", @@ -1850,15 +1738,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -2006,15 +1885,6 @@ "private": "^0.1.8" } }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, "regexpu-core": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", @@ -2136,15 +2006,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -2239,11 +2100,6 @@ "has-flag": "^3.0.0" } }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -2418,42 +2274,6 @@ "promise.prototype.finally": "^3.1.1" } }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", - "requires": { - "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2464,6 +2284,11 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index fa57858..f728bf4 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,42 @@ { - "name": "homebridge-harmony-control", - "version": "1.0.7", - "description": "Homebridge platform to control smart home equipment by room.", - "main": "bin/index.js", - "scripts": { - "build": "tsc", - "prepublishOnly": "npm run build" - }, - "repository": { - "type": "git", - "url": "ssh://git@watsonb8.ddns.net:2122/misc/homebridge-harmony-control.git" - }, - "author": "Brandon Watson", - "license": "ISC", - "keywords": [ - "homebridge-plugin", - "harmony", - "websocket", - "harmonyhub", - "homekit" - ], - "engines": { - "homebridge": ">=0.4.21", - "node": ">=7.6.0" - }, - "devDependencies": { - "rollup-plugin-typescript": "^1.0.1", - "tslib": "^1.10.0", - "tslint": "^5.17.0", - "typescript": "^3.5.1" - }, - "dependencies": { - "@babel/core": "^7.4.5", - "@babel/preset-env": "^7.4.5", - "@babel/preset-typescript": "^7.3.3", - "@types/node": "^12.0.7", - "harmony-websocket": "^1.1.0", - "homebridge": "^1.0.4", - "request": "^2.88.0" - } + "name": "homebridge-harmony-control", + "version": "1.0.7", + "description": "Homebridge platform to control smart home equipment by room.", + "main": "bin/index.js", + "scripts": { + "build": "tsc --build", + "prepublishOnly": "npm run build" + }, + "repository": { + "type": "git", + "url": "ssh://git@watsonb8.ddns.net:2122/misc/homebridge-harmony-control.git" + }, + "author": "Brandon Watson", + "license": "ISC", + "keywords": [ + "homebridge-plugin", + "harmony", + "websocket", + "harmonyhub", + "homekit" + ], + "engines": { + "homebridge": ">=1.1.6", + "node": ">=7.6.0" + }, + "devDependencies": { + "rollup-plugin-typescript": "^1.0.1", + "tslib": "^1.10.0", + "tslint": "^5.17.0", + "typescript": "^3.5.1" + }, + "dependencies": { + "@babel/core": "^7.4.5", + "@babel/preset-env": "^7.4.5", + "@babel/preset-typescript": "^7.3.3", + "@types/node": "^12.0.7", + "harmony-websocket": "^1.1.0", + "homebridge": "^1.1.16", + "request": "^2.88.0" + } } diff --git a/src/Accessories/ControlUnit.ts b/src/Accessories/ControlUnit.ts index 557728a..bce2a5d 100644 --- a/src/Accessories/ControlUnit.ts +++ b/src/Accessories/ControlUnit.ts @@ -1,286 +1,286 @@ -import * as Config from "../Models/Config" -import callbackify from '../Util/Callbackify'; -import HarmonyDataProvider from '../DataProviders/HarmonyDataProvider'; -import { API, Logging, AccessoryPlugin, PlatformAccessory } from "homebridge"; -import { Characteristic, Service, Accessory, RemoteController } from "hap-nodejs"; +import { PlatformAccessory, RemoteController, Service } from "homebridge"; +import HarmonyDataProvider from "../DataProviders/HarmonyDataProvider"; +import { IActivity } from "../Models/Config"; +import { Platform } from "../platform"; +import callbackify from "../Util/Callbackify"; /** * Enum describing remote key presses from homebridge. */ export enum RemoteKey { - REWIND = 0, - FAST_FORWARD = 1, - NEXT_TRACK = 2, - PREVIOUS_TRACK = 3, - ARROW_UP = 4, - ARROW_DOWN = 5, - ARROW_LEFT = 6, - ARROW_RIGHT = 7, - SELECT = 8, - BACK = 9, - EXIT = 10, - PLAY_PAUSE = 11, - INFORMATION = 15, + REWIND = 0, + FAST_FORWARD = 1, + NEXT_TRACK = 2, + PREVIOUS_TRACK = 3, + ARROW_UP = 4, + ARROW_DOWN = 5, + ARROW_LEFT = 6, + ARROW_RIGHT = 7, + SELECT = 8, + BACK = 9, + EXIT = 10, + PLAY_PAUSE = 11, + INFORMATION = 15, } -export interface IControlUnitProps { - dataProvider: HarmonyDataProvider, - displayName: string, - activities: Array, - api: API, - log: Logging, +export class ControlUnit { + constructor( + private readonly _platform: Platform, + private readonly _accessory: PlatformAccessory, + private _dataProvider: HarmonyDataProvider, + private _activities: Array + ) { + this._accessory + .getService(this._platform.Service.AccessoryInformation)! + .setCharacteristic( + this._platform.Characteristic.Manufacturer, + "Brandon Watson" + ) + .setCharacteristic( + this._platform.Characteristic.Model, + "Matrix Output Television" + ) + .setCharacteristic( + this._platform.Characteristic.SerialNumber, + "123-456-789" + ); + + const televisionUUID = this._platform.api.hap.uuid.generate( + `${this._accessory.displayName} Television` + ); + //Init services + const televisionService = + this._accessory.getService(this._platform.Service.Television) || + this._accessory.addService( + this._platform.Service.Television, + "Television", + televisionUUID + ); + this.configureTvService(televisionService); + + const televisionSpeakerUUID = this._platform.api.hap.uuid.generate( + `${this._accessory.displayName} Television Speaker` + ); + const televisionSpeakerService = + this._accessory.getService(this._platform.Service.TelevisionSpeaker) || + this._accessory.addService( + this._platform.Service.TelevisionSpeaker, + "Television Speaker", + televisionSpeakerUUID + ); + this.configureTvSpeakerService(televisionService, televisionSpeakerService); + + this.configureInputSourceService(televisionService); + } + + /************* + * + * Tv Service + * + *************/ + + /** + * Configure television service + */ + private configureTvService(televisionService: Service): void { + televisionService.setCharacteristic( + this._platform.Characteristic.ConfiguredName, + this._accessory.displayName + ); + //@ts-ignore + televisionService.setCharacteristic( + this._platform.Characteristic.SleepDiscoveryMode, + this._platform.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE + ); + televisionService.setCharacteristic( + this._platform.Characteristic.ActiveIdentifier, + 1 + ); + televisionService.setCharacteristic( + this._platform.Characteristic.Active, + false + ); + + //setup listeners + televisionService + .getCharacteristic(this._platform.Characteristic.Active) + //@ts-ignore + .on("set", callbackify(this.onSetAccessoryActive)) + //@ts-ignore + .on("get", callbackify(this.onGetAccessoryActive)); + + //Set remote characteristics if is external + televisionService + .getCharacteristic(this._platform.Characteristic.RemoteKey) + //@ts-ignore + .on("set", callbackify(this.onSetRemoteKey)); + + televisionService + .getCharacteristic(this._platform.Characteristic.ActiveIdentifier) + //@ts-ignore + .on("set", callbackify(this.onSetActiveIdentifier)) + //@ts-ignore + .on("get", callbackify(this.onGetActiveIdentifier)); + } + + /** + * Event handler for SET active characteristic + */ + private onSetAccessoryActive = async (value: any) => { + switch (value) { + case 0: + this._dataProvider.powerOff(this._accessory.displayName); + break; + //Turn on with first activity + case 1: + this._dataProvider.powerOn( + this._accessory.displayName, + this._activities[0] + ); + break; + } + }; + + /** + * Event handler for GET active characteristic + */ + private onGetAccessoryActive = async () => { + //@ts-ignore + return this._dataProvider.getIsActive(this._accessory.displayName) + ? this._platform.Characteristic.Active.ACTIVE + : this._platform.Characteristic.Active.INACTIVE; + }; + + /** + * Event handler for SET remote key + */ + private onSetRemoteKey = async (key: any) => { + this._dataProvider.sendKeyPress(this._accessory.displayName, key); + }; + + /** + * Event handler for SET active identifier characteristic + */ + private onSetActiveIdentifier = async (identifier: any) => { + this._dataProvider.startActivity( + this._accessory.displayName, + this._activities[identifier] + ); + }; + + /** + * Event handler for GET active identifier characteristic + */ + private onGetActiveIdentifier = async () => { + let currentActivity: IActivity = this._dataProvider.getIsActive( + this._accessory.displayName + )!; + let identifier: number = 0; + if (currentActivity) { + identifier = this._activities.findIndex( + (e) => e.DisplayName === currentActivity.DisplayName + ); + } + return identifier; + }; + + /****************** + * + * Speaker Service + * + *****************/ + + /** + * Configure Speaker Service + */ + private configureTvSpeakerService( + televisionService: Service, + televisionSpeakerService: Service + ): void { + televisionSpeakerService.setCharacteristic( + this._platform.Characteristic.Name, + this._accessory.displayName + ); + //@ts-ignore + televisionSpeakerService.setCharacteristic( + this._platform.Characteristic.Active, + this._platform.Characteristic.Active.ACTIVE + ); + //@ts-ignore + televisionSpeakerService.setCharacteristic( + this._platform.Characteristic.VolumeControlType, + this._platform.Characteristic.VolumeControlType.ABSOLUTE + ); + televisionSpeakerService.subtype = this._accessory.displayName + "Volume"; + if (televisionService) { + //@ts-ignore + televisionService.addLinkedService(televisionSpeakerService); + } + + //Setup listeners + televisionSpeakerService + .getCharacteristic(this._platform.Characteristic.VolumeSelector) + //@ts-ignore + .on("set", callbackify(this.onSetVolumeSelector)); + } + + /** + * Event handler for SET volume characteristic + */ + private onSetVolumeSelector = async (value: any) => { + switch (value) { + case 0: + this._dataProvider.volumeUp(this._accessory.displayName); + break; + case 1: + this._dataProvider.volumeDown(this._accessory.displayName); + break; + } + }; + + /***************** + * + * Input services + * + *****************/ + + /** + * Configure input service + */ + private configureInputSourceService(televisionService: Service): void { + let inputs: Array = []; + this._activities.forEach((activity: IActivity, index: number) => { + const inputUUID = this._platform.api.hap.uuid.generate( + `${activity.DisplayName} Input` + ); + let inputService = + this._accessory.getService(activity.DisplayName) || + this._accessory.addService( + this._platform.Service.InputSource, + activity.DisplayName, + inputUUID + ); + + inputService + .setCharacteristic(this._platform.Characteristic.Identifier, index) + .setCharacteristic( + this._platform.Characteristic.ConfiguredName, + activity.DisplayName + ) + .setCharacteristic( + this._platform.Characteristic.IsConfigured, + //@ts-ignore + this._platform.Characteristic.IsConfigured.CONFIGURED + ) + //@ts-ignore + .setCharacteristic( + this._platform.Characteristic.InputSourceType, + this._platform.Characteristic.InputSourceType.HDMI + ); + + //@ts-ignore + televisionService!.addLinkedService(inputService); + inputs.push(inputService); + }); + } } - -/** - * ControlUnit accessory - */ -export class ControlUnit implements AccessoryPlugin { - //fields - private log: Logging; - private displayName: string = ""; - private _api: API; - private _name: string; - - //Service fields - private _platformAccessory: PlatformAccessory; - private _televisionService: Service = new Service.Television( - this.displayName, - 'tvService' - ); - private _televisionSpeakerService: Service = new Service.TelevisionSpeaker( - this.displayName, - 'tvSpeakerService' - ); - private _infoService: Service; - private _inputServices: Array = []; - - //Harmony fields - private _activities: Array = []; - private _dataProvider: HarmonyDataProvider; - - /** - * Constructor - * @param props Input properties - */ - constructor(props: IControlUnitProps) { - //Assign class variables - this.log = props.log; - this._api = props.api; - this._name = props.displayName; - this.displayName = props.displayName; - - this._activities = props.activities; - - this._dataProvider = props.dataProvider; - - this._platformAccessory = new PlatformAccessory(this.displayName, this.generateUUID(), Accessory.Categories.TELEVISION); - this._platformAccessory.configureController(new RemoteController()); - - //@ts-ignore - this._infoService = new this._api.hap.Service.AccessoryInformation(); - this._infoService.setCharacteristic(Characteristic.Manufacturer, "Brandon Watson") - this._infoService.setCharacteristic(Characteristic.Model, "Matrix Output Television") - this._infoService.setCharacteristic(Characteristic.SerialNumber, "123-456-789"); - - //Configure services - this.configureTvService(); - - this.configureTvSpeakerService(); - - - // this.configureAccessoryInformation(); - this.configureInputSourceService(); - - //Configure external services - this.getServices().forEach(service => { - try { - this._platformAccessory.addService(service); - } catch (error) { } - - //@ts-ignore - if (service.linked) { - //@ts-ignore - this._televisionService!.addLinkedService(service); - } - }); - } - - public get platformAccessory(): PlatformAccessory { - return this._platformAccessory; - } - - /************* - * - * Tv Service - * - *************/ - - /** - * Configure television service - */ - private configureTvService(): void { - - this._televisionService.setCharacteristic(Characteristic.ConfiguredName, this.displayName); - //@ts-ignore - this._televisionService.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); - this._televisionService.setCharacteristic(Characteristic.ActiveIdentifier, 1); - this._televisionService.setCharacteristic(Characteristic.Active, false); - - //setup listeners - this._televisionService.getCharacteristic(Characteristic.Active) - //@ts-ignore - .on("set", callbackify(this.onSetAccessoryActive)) - //@ts-ignore - .on("get", callbackify(this.onGetAccessoryActive)); - - //Set remote characteristics if is external - this._televisionService.getCharacteristic(Characteristic.RemoteKey) - //@ts-ignore - .on("set", callbackify(this.onSetRemoteKey)); - - - this._televisionService.getCharacteristic(Characteristic.ActiveIdentifier) - //@ts-ignore - .on("set", callbackify(this.onSetActiveIdentifier)) - //@ts-ignore - .on("get", callbackify(this.onGetActiveIdentifier)); - } - - /** - * Event handler for SET active characteristic - */ - private onSetAccessoryActive = async (value: any) => { - switch (value) { - case 0: this._dataProvider.powerOff(this._name); break; - //Turn on with first activity - case 1: this._dataProvider.powerOn(this._name, this._activities[0]); break; - } - } - - /** - * Event handler for GET active characteristic - */ - private onGetAccessoryActive = async () => { - //@ts-ignore - return this._dataProvider.getIsActive(this._name) ? Characteristic.Active.Active : Characteristic.Active.Inactive - } - - /** - * Event handler for SET remote key - */ - private onSetRemoteKey = async (key: any) => { - this._dataProvider.sendKeyPress(this._name, key); - } - - /** - * Event handler for SET active identifier characteristic - */ - private onSetActiveIdentifier = async (identifier: any) => { - this._dataProvider.startActivity(this._name, this._activities[identifier]); - } - - /** - * Event handler for GET active identifier characteristic - */ - private onGetActiveIdentifier = async () => { - let currentActivity: Config.IActivity = this._dataProvider.getIsActive(this._name)!; - let identifier: number = 0; - if (currentActivity) { - identifier = this._activities.findIndex(e => e.DisplayName === currentActivity.DisplayName); - } - return identifier; - } - - /****************** - * - * Speaker Service - * - *****************/ - - /** - * Configure Speaker Service - */ - private configureTvSpeakerService(): void { - this._televisionSpeakerService.setCharacteristic(Characteristic.Name, this.displayName); - //@ts-ignore - this._televisionSpeakerService.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE); - //@ts-ignore - this._televisionSpeakerService.setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE); - this._televisionSpeakerService.subtype = this.displayName + "Volume"; - if (this._televisionService) { - //@ts-ignore - this._televisionService.addLinkedService(this._televisionSpeakerService); - } - - //Setup listeners - this._televisionSpeakerService.getCharacteristic(Characteristic.VolumeSelector) - //@ts-ignore - .on("set", callbackify(this.onSetVolumeSelector)); - } - - /** - * Event handler for SET volume characteristic - */ - private onSetVolumeSelector = async (value: any) => { - switch (value) { - case 0: this._dataProvider.volumeUp(this._name); break; - case 1: this._dataProvider.volumeDown(this._name); break; - } - } - - /***************** - * - * Input services - * - *****************/ - - /** - * Configure input service - */ - private configureInputSourceService(): void { - let inputs: Array = []; - this._activities.forEach((activity: Config.IActivity, index: number) => { - let inputService = new Service.InputSource(activity.DisplayName, 'activity' + activity.DisplayName); - inputService - .setCharacteristic(Characteristic.Identifier, index) - .setCharacteristic( - Characteristic.ConfiguredName, - activity.DisplayName) - .setCharacteristic( - Characteristic.IsConfigured, - //@ts-ignore - Characteristic.IsConfigured.CONFIGURED - ) - //@ts-ignore - .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI); - - //@ts-ignore - this._televisionService!.addLinkedService(inputService); - inputs.push(inputService); - }); - this._inputServices = inputs; - } - - private generateUUID(): string { // Public Domain/MIT - var d = new Date().getTime(); - if (typeof performance !== 'undefined' && typeof performance.now === 'function') { - d += performance.now(); //use high-precision timer if available - } - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = (d + Math.random() * 16) % 16 | 0; - d = Math.floor(d / 16); - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }); - } - - /** - * Called by homebridge to gather services for this accessory. - */ - getServices(): Array { - let services: Array = [this._infoService, this._televisionService!, this._televisionSpeakerService!]; - this._inputServices.forEach((service: Service) => { - services.push(service!); - }); - return (services); - } -} \ No newline at end of file diff --git a/src/Accessories/DeviceButton.ts b/src/Accessories/DeviceButton.ts index 9dea209..9ed159f 100644 --- a/src/Accessories/DeviceButton.ts +++ b/src/Accessories/DeviceButton.ts @@ -1,148 +1,111 @@ +import { PlatformAccessory, Service } from "homebridge"; import HarmonyDataProvider from "../DataProviders/HarmonyDataProvider"; import { IDeviceButton } from "../Models/Config"; -import { ICommand } from "../Models"; import { HarmonyDevice } from "../Models/HarmonyDevice"; -import { Service, PlatformAccessory, AccessoryPlugin, API, Characteristic } from "homebridge"; +import { Platform } from "../platform"; -export interface IDeviceButtonProps { - dataProvider: HarmonyDataProvider, - buttonName: string, - displayName: string, - deviceInfo: IDeviceButton, - api: API, - log: any, +export class DeviceButton { + private _buttonState: boolean; + private _device!: HarmonyDevice; + private _switchService: Service; + + constructor( + private readonly _platform: Platform, + private readonly _accessory: PlatformAccessory, + private _dataProvider: HarmonyDataProvider, + private _deviceInfo: IDeviceButton + ) { + this._buttonState = false; + + if (this._deviceInfo.NumberOfKeyPresses && this._deviceInfo.IsStateful) { + throw new Error( + "A button cannot be stateful and be pressed more than once" + ); + } + + this._accessory + .getService(this._platform.Service.AccessoryInformation)! + .setCharacteristic( + this._platform.Characteristic.Manufacturer, + "Brandon Watson" + ) + .setCharacteristic(this._platform.Characteristic.Model, "Device Button") + .setCharacteristic( + this._platform.Characteristic.SerialNumber, + "123-456-789" + ); + + const switchUUID = this._platform.api.hap.uuid.generate( + `${this._accessory.displayName} Switch` + ); + + this._switchService = + this._accessory.getService(this._platform.Service.Switch) || + this._accessory.addService( + this._platform.Service.Switch, + this._accessory.displayName, + switchUUID + ); + + this._switchService + .getCharacteristic(this._platform.Characteristic.On) + //@ts-ignore + .on("set", this.onSwitchSet) + .updateValue(this._buttonState) + .on("get", this.onSwitchGet); + } + + /** + * Handler for switch set event + * @param callback The callback function to call when complete + */ + private onSwitchSet = async ( + newState: boolean, + callback: (error?: Error | null | undefined) => void + ) => { + if (!this._deviceInfo.IsStateful && newState === this._buttonState) { + return callback(); + } + + //Get device command if we don't have it + if (!this._device) { + this._device = this._dataProvider.getDeviceFromName( + this._deviceInfo.DeviceName + ); + } + + //Execute command + if (this._device) { + //change state if stateful + if (this._deviceInfo.IsStateful && this._buttonState != newState) { + this._buttonState = newState; + await this._device.sendCommand(this._deviceInfo.ButtonName); + } else if (!this._deviceInfo.IsStateful) { + //Send the number of configured key presses + for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) { + await this._device.sendCommand(this._deviceInfo.ButtonName); + } + this._switchService + .getCharacteristic(this._platform.Characteristic.On) + .updateValue(false); + return callback(new Error("Normal Response")); + } + } + return callback(); + }; + + /** + * Handler for switch get event + * @param callback The callback function to call when complete + */ + private onSwitchGet = ( + callback: (error: Error | null, value: boolean) => void + ) => { + //Only return state if button is stateful + if (this._deviceInfo.IsStateful) { + return callback(null, this._buttonState); + } else { + return callback(null, false); + } + }; } - -export class DeviceButton implements AccessoryPlugin { - private _api: API; - private _homebridge: any; - private _log: any = {}; - - //Service fields - private _switchService: Service; - private _infoService: Service; - - private _buttonInfo: IDeviceButton; - - private _dataProvider: HarmonyDataProvider; - - private _device!: HarmonyDevice; - - private _buttonState: boolean; - - constructor(props: IDeviceButtonProps) { - //Assign class variables - this._log = props.log; - this._api = props.api; - this.name = props.displayName; - - this._buttonInfo = props.deviceInfo; - - this._dataProvider = props.dataProvider; - - this._buttonState = false; - - if (this._buttonInfo.NumberOfKeyPresses && this._buttonInfo.IsStateful) { - throw new Error("A button cannot be stateful and be pressed more than once"); - } - - //Assign default number of key presses - if (!this._buttonInfo.NumberOfKeyPresses) { - this._buttonInfo.NumberOfKeyPresses = 1; - } - - this.platformAccessory = new this._homebridge.platformAccessory(this.name, this.generateUUID(), this._homebridge.hap.Accessory.Categories.SWITCH); - - //@ts-ignore - this._infoService = new Service.AccessoryInformation(); - this._infoService.setCharacteristic(Characteristic.Manufacturer, "Brandon Watson") - this._infoService.setCharacteristic(Characteristic.Model, "Device Button") - this._infoService.setCharacteristic(Characteristic.SerialNumber, "123-456-789"); - - this._switchService = new Service.Switch( - this.name, - 'switchService' - ) - - this._switchService.getCharacteristic(Characteristic.On) - //@ts-ignore - .on("set", this.onSwitchSet) - .updateValue(this._buttonState) - .on("get", this.onSwitchGet); - } - - /** - * Required by homebridge. - */ - public name: string; - - public platformAccessory: any; - - /** - * Called by homebridge to gather services. - */ - public getServices = (): Array => { - return [this._infoService, this._switchService!]; - } - - /** - * Handler for switch set event - * @param callback The callback function to call when complete - */ - private onSwitchSet = async (newState: boolean, callback: (error?: Error | null | undefined) => void) => { - if (!this._buttonInfo.IsStateful && newState === this._buttonState) { - return callback(); - } - - //Get device command if we don't have it - if (!this._device) { - this._device = this._dataProvider.getDeviceFromName(this._buttonInfo.DeviceName); - } - - //Execute command - if (this._device) { - //change state if stateful - if (this._buttonInfo.IsStateful && this._buttonState != newState) { - this._buttonState = newState - await this._device.sendCommand(this._buttonInfo.ButtonName); - } else if (!this._buttonInfo.IsStateful) { - //Send the number of configured key presses - for (let i = 0; i < this._buttonInfo.NumberOfKeyPresses; i++) { - await this._device.sendCommand(this._buttonInfo.ButtonName); - } - this._switchService.getCharacteristic(Characteristic.On).updateValue(false); - return callback(new Error("Normal Response")); - } - } - return callback(); - } - - /** - * Handler for switch get event - * @param callback The callback function to call when complete - */ - private onSwitchGet = (callback: (error: Error | null, value: boolean) => void) => { - //Only return state if button is stateful - if (this._buttonInfo.IsStateful) { - return callback(null, this._buttonState); - } else { - return callback(null, false) - } - - } - - /** - * Helper function to generate a UUID - */ - private generateUUID(): string { // Public Domain/MIT - var d = new Date().getTime(); - if (typeof performance !== 'undefined' && typeof performance.now === 'function') { - d += performance.now(); //use high-precision timer if available - } - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = (d + Math.random() * 16) % 16 | 0; - d = Math.floor(d / 16); - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }); - } -} \ No newline at end of file diff --git a/src/DataProviders/HarmonyDataProvider.ts b/src/DataProviders/HarmonyDataProvider.ts index dc58958..150b39a 100644 --- a/src/DataProviders/HarmonyDataProvider.ts +++ b/src/DataProviders/HarmonyDataProvider.ts @@ -1,347 +1,404 @@ import { IActivity } from "../Models/Config/IActivity"; import { IDeviceSetupItem } from "../Models/Config/IDeviceSetupItem"; import { IInput, IMatrix, IOutput } from "../Models/Config/IMatrix"; -import { RemoteKey } from '../Accessories/ControlUnit'; +import { RemoteKey } from "../Accessories/ControlUnit"; import { EventEmitter } from "events"; -import { ICommand } from '../Models/IDevice'; import { IHub } from "../Models/Config/IHub"; import { IDeviceConfig } from "../Models/Config/IDeviceConfig"; import { HarmonyDevice } from "../Models/HarmonyDevice"; import { HarmonyHub } from "../Models/HarmonyHub"; -import { Characteristic } from "homebridge"; const Harmony = require("harmony-websocket"); interface IActivityState { - currentActivity: IActivity + currentActivity: IActivity; } interface IHarmonyDataProviderProps { - hubs: Array; - deviceConfigs: Array; - log: any, - matrix: IMatrix + hubs: Array; + deviceConfigs: Array; + log: any; + matrix: IMatrix; } class HarmonyDataProvider extends EventEmitter { - private _log: any; - private _hubsByDevice: { [deviceName: string]: string } = {}; - private _hubs: { [hubName: string]: HarmonyHub } = {}; - // private _devicesByHub: { [hubName: string]: { [deviceName: string]: HarmonyDevice } } = {}; - private _states: { [controlUnitName: string]: (IActivityState | undefined) } = {}; + private _log: any; + private _hubsByDevice: { [deviceName: string]: string } = {}; + private _hubs: { [hubName: string]: HarmonyHub } = {}; + // private _devicesByHub: { [hubName: string]: { [deviceName: string]: HarmonyDevice } } = {}; + private _states: { + [controlUnitName: string]: IActivityState | undefined; + } = {}; - private _matrix: IMatrix; + private _matrix: IMatrix; - constructor(props: IHarmonyDataProviderProps) { - super(); - this._log = props.log; - this._matrix = props.matrix; - props.deviceConfigs.forEach((deviceConfig: IDeviceConfig) => { - this._hubsByDevice[deviceConfig.Name] = deviceConfig.Hub; - }); - // this._deviceConfigs = props.deviceConfigs; + constructor(props: IHarmonyDataProviderProps) { + super(); + this._log = props.log; + this._matrix = props.matrix; + props.deviceConfigs.forEach((deviceConfig: IDeviceConfig) => { + this._hubsByDevice[deviceConfig.Name] = deviceConfig.Hub; + }); + // this._deviceConfigs = props.deviceConfigs; - this.connect(props.hubs); + this.connect(props.hubs); + } + + // public get devicesByHub(): { [hubName: string]: { [deviceName: string]: HarmonyDevice } } { + // return this._devicesByHub; + // } + + public get hubs(): { [hubName: string]: HarmonyHub } { + return this._hubs; + } + + /** + * Power on all devices in an activity. + */ + public powerOn = async (controlUnitName: string, activity: IActivity) => { + //Only power on if not alread on + let currentActivity = this._states[controlUnitName] + ? this._states[controlUnitName]!.currentActivity + : undefined; + if (!currentActivity) { + await this.startActivity(controlUnitName, activity); + } + }; + + /** + * Power off all devices in an activity that aren't being used. + */ + public powerOff = async (controlUnitName: string) => { + if (!this._states[controlUnitName]) { + return; + } + //Build potential list of devices to turn off + let devicesToTurnOff: Array = this._states[ + controlUnitName + ]!.currentActivity.DeviceSetupList.map( + (value: IDeviceSetupItem): HarmonyDevice => { + return this.getDeviceFromName(value.DeviceName); + } + ); + + //Resolve device conflicts with other controlUnits + devicesToTurnOff = this.sanitizeDeviceList( + devicesToTurnOff, + controlUnitName + ); + + //Turn off devices + devicesToTurnOff.forEach(async (device: HarmonyDevice) => { + if (device) { + await device.powerOff(); + } + }); + + this._states[controlUnitName] = undefined; + }; + + /** + * Start an activity + */ + public startActivity = async ( + controlUnitName: string, + activity: IActivity + ) => { + this._log( + `Starting activity ${activity.DisplayName} for controlUnit: ${controlUnitName}` + ); + let lastActivity: IActivity | undefined = undefined; + if (this._states[controlUnitName]) { + lastActivity = this._states[controlUnitName]!.currentActivity; } - // public get devicesByHub(): { [hubName: string]: { [deviceName: string]: HarmonyDevice } } { - // return this._devicesByHub; - // } + //Build potential list of devices to to turn on + let devicesToTurnOn: Array = activity.DeviceSetupList.map( + (value: IDeviceSetupItem): HarmonyDevice => { + return this.getDeviceFromName(value.DeviceName); + } + ); - public get hubs(): { [hubName: string]: HarmonyHub } { - return this._hubs; - } + //Resolve device conflicts with other controlUnits + devicesToTurnOn = this.sanitizeDeviceList(devicesToTurnOn, controlUnitName); - /** - * Power on all devices in an activity. - */ - public powerOn = async (controlUnitName: string, activity: IActivity) => { - //Only power on if not alread on - let currentActivity = this._states[controlUnitName] ? this._states[controlUnitName]!.currentActivity : undefined; - if (!currentActivity) { - await this.startActivity(controlUnitName, activity); + //Turn on devices + await Promise.all( + devicesToTurnOn.map(async (device: HarmonyDevice) => { + if (device && device.name) { + if (!device.on) { + this._log(`Turning on device ${device.name}`); + await device.powerOn(); + } } + }) + ); + + //Assign correct input + await Promise.all( + activity.DeviceSetupList.map(async (value: IDeviceSetupItem) => { + let device: HarmonyDevice = this.getDeviceFromName(value.DeviceName); + + if (device && device.supportsCommand(`Input${value.Input}`)) { + await device.sendCommand(`Input${value.Input}`); + } + }) + ); + + if (activity.UseMatrix) { + //get input and output + let input: IInput = this._matrix.Inputs.filter( + (e) => e.InputDevice === activity.ControlDevice + )[0]; + let output: IOutput = this._matrix.Outputs.filter( + (e) => e.OutputDevice === activity.OutputDevice + )[0]; + + let inputCommandName: string = `In ${input.InputNumber}`; + let outputCommandName: string = `Out ${output.OutputLetter}`; + + let matrixDevice: HarmonyDevice = this.getDeviceFromName( + this._matrix.DeviceName + ); + + //Route hdmi + if ( + matrixDevice.supportsCommand(inputCommandName) && + matrixDevice.supportsCommand(outputCommandName) + ) { + await matrixDevice.sendCommand(outputCommandName); + await matrixDevice.sendCommand(inputCommandName); + await matrixDevice.sendCommand(outputCommandName); + await matrixDevice.sendCommand(inputCommandName); + } } - /** - * Power off all devices in an activity that aren't being used. - */ - public powerOff = async (controlUnitName: string) => { - if (!this._states[controlUnitName]) { - return; + //Build potential list of devices to turn off + if (lastActivity) { + let devicesToTurnOff: Array = lastActivity.DeviceSetupList.map( + (value: IDeviceSetupItem): HarmonyDevice => { + return this.getDeviceFromName(value.DeviceName); } - //Build potential list of devices to turn off - let devicesToTurnOff: Array = this._states[controlUnitName]!.currentActivity.DeviceSetupList - .map((value: IDeviceSetupItem): HarmonyDevice => { - return this.getDeviceFromName(value.DeviceName); - }); + ); - //Resolve device conflicts with other controlUnits - devicesToTurnOff = this.sanitizeDeviceList(devicesToTurnOff, controlUnitName); + //remove devices that will be used for next activity from list + //delete array[index] is stupid because it just nulls out the index. But now i have to deal with nulls + devicesToTurnOff.forEach((device: HarmonyDevice, index: number) => { + if ( + device && + device.name && + activity.DeviceSetupList.some((e) => { + return e && e.DeviceName === device.name; + }) + ) { + delete devicesToTurnOff[index]; + } + }); + //Resolve device conflicts with other controlUnits + devicesToTurnOff = this.sanitizeDeviceList( + devicesToTurnOff, + controlUnitName + ); + + this._log( + `Sanatized devices to turn off: ${JSON.stringify( + devicesToTurnOff.map((e) => (e ? e.name : "")) + )}` + ); + + await Promise.all( //Turn off devices - devicesToTurnOff.forEach(async (device: HarmonyDevice) => { - if (device) { - await device.powerOff(); + devicesToTurnOff.map(async (device: HarmonyDevice) => { + if (device) { + if (device.on) { + this._log(`Turning off device ${device.name}`); + await device.powerOff(); } - }); - - this._states[controlUnitName] = undefined; + } + }) + ); } - /** - * Start an activity - */ - public startActivity = async (controlUnitName: string, activity: IActivity) => { - this._log(`Starting activity ${activity.DisplayName} for controlUnit: ${controlUnitName}`) - let lastActivity: IActivity | undefined = undefined; - if (this._states[controlUnitName]) { - lastActivity = this._states[controlUnitName]!.currentActivity; + //Assign current activity + this._states[controlUnitName] = { currentActivity: activity }; + }; + + /** + * Turn the volume up for the current running activity. + */ + public volumeUp = async (controlUnitName: string) => { + let volumeUpCommand: string = "Volume Up"; + if (this._states[controlUnitName]) { + let volumeDevice: HarmonyDevice = this.getDeviceFromName( + this._states[controlUnitName]!.currentActivity.VolumeDevice + ); + await volumeDevice.sendCommand(volumeUpCommand); + } + }; + + /** + * Volume down for current running activity. + */ + public volumeDown = async (controlUnitName: string) => { + let volumeDownCommand: string = "Volume Down"; + if (this._states[controlUnitName]) { + let volumeDevice: HarmonyDevice = this.getDeviceFromName( + this._states[controlUnitName]!.currentActivity.VolumeDevice + ); + await volumeDevice.sendCommand(volumeDownCommand); + } + }; + + /** + * Send key press for current activity. + * + * @param controlUnitName The name of the control unit to act on. + * @param key The key to send. + */ + public sendKeyPress = async (controlUnitName: string, key: any) => { + if (this._states[controlUnitName]) { + let commandName: string = ""; + + let device: HarmonyDevice = this.getDeviceFromName( + this._states[controlUnitName]!.currentActivity.ControlDevice + ); + switch (key) { + case RemoteKey.ARROW_UP: { + commandName = "Direction Up"; + break; } + case RemoteKey.ARROW_DOWN: { + commandName = "Direction Down"; + break; + } + case RemoteKey.ARROW_LEFT: { + commandName = "Direction Left"; + break; + } + case RemoteKey.ARROW_RIGHT: { + commandName = "Direction Right"; + break; + } + case RemoteKey.SELECT: { + commandName = "Select"; + break; + } + case RemoteKey.PLAY_PAUSE: { + commandName = "Pause"; + break; + } + case RemoteKey.INFORMATION: { + commandName = "Menu"; + break; + } + case RemoteKey.BACK: { + commandName = "Back"; + break; + } + case RemoteKey.EXIT: { + commandName = "Back"; + break; + } + } - //Build potential list of devices to to turn on - let devicesToTurnOn: Array = activity.DeviceSetupList.map((value: IDeviceSetupItem): HarmonyDevice => { - return this.getDeviceFromName(value.DeviceName); - }); + await device.sendCommand(commandName); + } + }; - //Resolve device conflicts with other controlUnits - devicesToTurnOn = this.sanitizeDeviceList(devicesToTurnOn, controlUnitName); + /** + * Return if a control unit is active + * @param controlUnitName + */ + public getIsActive(controlUnitName: string): IActivity | undefined { + return this._states[controlUnitName] + ? this._states[controlUnitName]!.currentActivity + : undefined; + } - //Turn on devices - await Promise.all(devicesToTurnOn.map(async (device: HarmonyDevice) => { - if (device && device.name) { - if (!device.on) { - this._log(`Turning on device ${device.name}`) - await device.powerOn(); - } + /** + * Get the IDevice by name. + * @param deviceName The device to retrieve. + */ + public getDeviceFromName(deviceName: string): HarmonyDevice { + let device: HarmonyDevice | undefined; + try { + device = this._hubs[this._hubsByDevice[deviceName]].getDeviceByName( + deviceName + ); + } catch (err) { + this._log(`Error retrieving device from hub: ${err}`); + } + + return device!; + } + + // /** + // * Gets device button commands + // * @param deviceCommandName The device command name + // * @param deviceName The device name + // */ + // public getCommand(deviceCommandName: string, deviceName: string): ICommand | undefined { + // const device: HarmonyDevice = this.getDeviceFromName(deviceName); + // if (device && device.supportsCommand(deviceCommandName)) { + // return device.getCommand(deviceCommandName); + // } else { + // return undefined; + // } + // } + + private connect = async (hubs: Array) => { + let readyCount = 0; + await Promise.all( + hubs.map( + async (hub: IHub): Promise => { + const newHarmonyHub = new HarmonyHub(hub.Name, hub.Ip, this._log); + this._hubs[hub.Name] = newHarmonyHub; + newHarmonyHub.on("Ready", () => { + readyCount++; + if (readyCount === Object.keys(this._hubs).length) { + this.emit("Ready"); } - })); + }); + await newHarmonyHub.initialize(); + } + ) + ); + }; - //Assign correct input - await Promise.all( - activity.DeviceSetupList.map(async (value: IDeviceSetupItem) => { - let device: HarmonyDevice = this.getDeviceFromName(value.DeviceName); + /** + * Helper function to make sure no control unit depends on device list. + * @param devicesToTurnOn The list of devices to modify. + * @param controlUnitName The name of the control unit in question. + */ + private sanitizeDeviceList( + devicesToTurnOn: Array, + controlUnitName: string + ): Array { + for (let controlUnitKey in this._states) { + //Skip self + if (controlUnitKey === controlUnitName) { + continue; + } + let currentOtherState: IActivityState = this._states[controlUnitKey]!; - if (device && device.supportsCommand(`Input${value.Input}`)) { - await device.sendCommand(`Input${value.Input}`) - } - }) + if (currentOtherState) { + currentOtherState.currentActivity.DeviceSetupList.forEach( + (value: IDeviceSetupItem) => { + //there are devices to remove + if (devicesToTurnOn.some((e) => e && e.name === value.DeviceName)) { + let deviceToRemove: HarmonyDevice = devicesToTurnOn.filter( + (i) => i.name === value.DeviceName + )[0]; + delete devicesToTurnOn[devicesToTurnOn.indexOf(deviceToRemove)]; + } + } ); - - if (activity.UseMatrix) { - //get input and output - let input: IInput = this._matrix.Inputs.filter(e => e.InputDevice === activity.ControlDevice)[0]; - let output: IOutput = this._matrix.Outputs.filter(e => e.OutputDevice === activity.OutputDevice)[0]; - - let inputCommandName: string = `In ${input.InputNumber}`; - let outputCommandName: string = `Out ${output.OutputLetter}`; - - let matrixDevice: HarmonyDevice = this.getDeviceFromName(this._matrix.DeviceName); - - //Route hdmi - if (matrixDevice.supportsCommand(inputCommandName) && matrixDevice.supportsCommand(outputCommandName)) { - await matrixDevice.sendCommand(outputCommandName); - await matrixDevice.sendCommand(inputCommandName); - await matrixDevice.sendCommand(outputCommandName); - await matrixDevice.sendCommand(inputCommandName); - } - } - - //Build potential list of devices to turn off - if (lastActivity) { - let devicesToTurnOff: Array = lastActivity.DeviceSetupList.map((value: IDeviceSetupItem): HarmonyDevice => { - return this.getDeviceFromName(value.DeviceName); - }); - - //remove devices that will be used for next activity from list - //delete array[index] is stupid because it just nulls out the index. But now i have to deal with nulls - devicesToTurnOff.forEach((device: HarmonyDevice, index: number) => { - if (device && device.name && activity.DeviceSetupList.some(e => { - return (e && e.DeviceName === device.name) - })) { - delete devicesToTurnOff[index]; - } - }) - - //Resolve device conflicts with other controlUnits - devicesToTurnOff = this.sanitizeDeviceList(devicesToTurnOff, controlUnitName); - - this._log(`Sanatized devices to turn off: ${JSON.stringify(devicesToTurnOff.map(e => e ? e.name : ""))}`); - - await Promise.all( - //Turn off devices - devicesToTurnOff.map(async (device: HarmonyDevice) => { - if (device) { - if (device.on) { - this._log(`Turning off device ${device.name}`) - await device.powerOff(); - } - } - }) - ); - - } - - //Assign current activity - this._states[controlUnitName] = { currentActivity: activity }; + } } - /** - * Turn the volume up for the current running activity. - */ - public volumeUp = async (controlUnitName: string) => { - let volumeUpCommand: string = "Volume Up" - if (this._states[controlUnitName]) { - let volumeDevice: HarmonyDevice = this.getDeviceFromName(this._states[controlUnitName]!.currentActivity.VolumeDevice); - await volumeDevice.sendCommand(volumeUpCommand); - } - } - - /** - * Volume down for current running activity. - */ - public volumeDown = async (controlUnitName: string) => { - let volumeDownCommand: string = "Volume Down" - if (this._states[controlUnitName]) { - let volumeDevice: HarmonyDevice = this.getDeviceFromName(this._states[controlUnitName]!.currentActivity.VolumeDevice); - await volumeDevice.sendCommand(volumeDownCommand); - } - } - - /** - * Send key press for current activity. - * - * @param controlUnitName The name of the control unit to act on. - * @param key The key to send. - */ - public sendKeyPress = async (controlUnitName: string, key: any) => { - if (this._states[controlUnitName]) { - let commandName: string = ""; - - let device: HarmonyDevice = this.getDeviceFromName(this._states[controlUnitName]!.currentActivity.ControlDevice); - switch (key) { - case RemoteKey.ARROW_UP: { - commandName = "Direction Up"; - break; - } - case RemoteKey.ARROW_DOWN: { - commandName = "Direction Down"; - break; - } - case RemoteKey.ARROW_LEFT: { - commandName = "Direction Left"; - break; - } - case RemoteKey.ARROW_RIGHT: { - commandName = "Direction Right"; - break; - } - case RemoteKey.SELECT: { - commandName = "Select"; - break; - } - case RemoteKey.PLAY_PAUSE: { - commandName = "Pause"; - break; - } - case RemoteKey.INFORMATION: { - commandName = "Menu"; - break; - } - case RemoteKey.BACK: { - commandName = "Back"; - break; - } - case RemoteKey.EXIT: { - commandName = "Back"; - break; - } - } - - await device.sendCommand(commandName); - } - } - - /** - * Return if a control unit is active - * @param controlUnitName - */ - public getIsActive(controlUnitName: string): IActivity | undefined { - return this._states[controlUnitName] ? this._states[controlUnitName]!.currentActivity : undefined; - } - - /** - * Get the IDevice by name. - * @param deviceName The device to retrieve. - */ - public getDeviceFromName(deviceName: string): HarmonyDevice { - let device: HarmonyDevice | undefined; - try { - device = this._hubs[this._hubsByDevice[deviceName]].getDeviceByName(deviceName); - } catch (err) { - this._log(`Error retrieving device from hub: ${err}`); - } - - return device!; - } - - // /** - // * Gets device button commands - // * @param deviceCommandName The device command name - // * @param deviceName The device name - // */ - // public getCommand(deviceCommandName: string, deviceName: string): ICommand | undefined { - // const device: HarmonyDevice = this.getDeviceFromName(deviceName); - // if (device && device.supportsCommand(deviceCommandName)) { - // return device.getCommand(deviceCommandName); - // } else { - // return undefined; - // } - // } - - private connect = async (hubs: Array) => { - let readyCount = 0; - await Promise.all( - hubs.map(async (hub: IHub): Promise => { - const newHarmonyHub = new HarmonyHub(hub.Name, hub.Ip, this._log); - this._hubs[hub.Name] = newHarmonyHub; - newHarmonyHub.on("Ready", () => { - readyCount++; - if (readyCount === Object.keys(this._hubs).length) { - this.emit("Ready"); - } - }) - await newHarmonyHub.initialize(); - }) - ) - } - - /** - * Helper function to make sure no control unit depends on device list. - * @param devicesToTurnOn The list of devices to modify. - * @param controlUnitName The name of the control unit in question. - */ - private sanitizeDeviceList(devicesToTurnOn: Array, controlUnitName: string): Array { - for (let controlUnitKey in this._states) { - //Skip self - if (controlUnitKey === controlUnitName) { - continue; - } - let currentOtherState: IActivityState = this._states[controlUnitKey]!; - - if (currentOtherState) { - currentOtherState.currentActivity.DeviceSetupList.forEach((value: IDeviceSetupItem) => { - //there are devices to remove - if (devicesToTurnOn.some(e => e && e.name === value.DeviceName)) { - let deviceToRemove: HarmonyDevice = devicesToTurnOn.filter(i => i.name === value.DeviceName)[0]; - delete devicesToTurnOn[devicesToTurnOn.indexOf(deviceToRemove)]; - } - }); - } - } - - return devicesToTurnOn; - } + return devicesToTurnOn; + } } -export default HarmonyDataProvider; \ No newline at end of file +export default HarmonyDataProvider; diff --git a/src/Util/Callbackify.ts b/src/Util/Callbackify.ts index 4bf521f..ddf0169 100644 --- a/src/Util/Callbackify.ts +++ b/src/Util/Callbackify.ts @@ -1,30 +1,32 @@ - /** * Helper function to convert callbacks into promises - * @param func + * @param func */ -export default function callbackify(func: (...args: any[]) => Promise): Function { - return (...args: any[]) => { - const onlyArgs: any[] = []; - let maybeCallback: Function | null = null; - for (const arg of args) { - if (typeof arg === 'function') { - maybeCallback = arg; - break; - } +export default function callbackify( + func: (...args: any[]) => Promise +): Function { + return (...args: any[]) => { + const onlyArgs: any[] = []; + let maybeCallback: Function | null = null; - onlyArgs.push(arg); - } + for (const arg of args) { + if (typeof arg === "function") { + maybeCallback = arg; + break; + } - if (!maybeCallback) { - throw new Error("Missing callback parameter!"); - } - - const callback = maybeCallback; - - func(...onlyArgs) - .then((data: any) => callback(null, data)) - .catch((err: any) => callback(err)) + onlyArgs.push(arg); } -} \ No newline at end of file + + if (!maybeCallback) { + throw new Error("Missing callback parameter!"); + } + + const callback = maybeCallback; + + func(...onlyArgs) + .then((data: any) => callback(null, data)) + .catch((err: any) => callback(err)); + }; +} diff --git a/src/Util/index.ts b/src/Util/index.ts new file mode 100644 index 0000000..19f0e10 --- /dev/null +++ b/src/Util/index.ts @@ -0,0 +1,2 @@ +export * from "./Callbackify"; +export * from "./Sleep"; diff --git a/src/index.ts b/src/index.ts index 29cbc95..777c136 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,118 +1,11 @@ -import * as Accessories from "./Accessories"; -import HarmonyDataProvider from "./DataProviders/HarmonyDataProvider"; -import * as Config from "./Models/Config"; -import { HarmonyDevice } from "./Models/HarmonyDevice"; -import { HarmonyHub } from "./Models/HarmonyHub"; -import { API, Logging, StaticPlatformPlugin, AccessoryPlugin } from "homebridge"; +import { API } from "homebridge"; -let Accessory: any; -let Homebridge: API; +import { PLATFORM_NAME } from "./settings"; +import { Platform } from "./platform"; /** - * Main entry. - * @param homebridge + * This method registers the platform with Homebridge */ -export default function (homebridge: API) { - Homebridge = homebridge; - Accessory = homebridge.platformAccessory; - homebridge.registerPlatform( - 'homebridge-harmony-watson', - 'HarmonyHubMatrix', - HarmonyMatrixPlatform, - ); +export = (api: API) => { + api.registerPlatform(PLATFORM_NAME, Platform); }; - -class HarmonyMatrixPlatform implements StaticPlatformPlugin { - log: Logging; - config: Config.IConfig; - api: API; - dataProvider: HarmonyDataProvider | null; - accessoryList: Array = []; - - constructor(logger: Logging, config: any, api: API) { - this.log = logger; - this.config = config; - this.api = api; - this.log.info('INFO - Registering Harmony Matrix Platform'); - this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); - - this.dataProvider = null; - - if (this.config) { - //construct data provider - this.dataProvider = new HarmonyDataProvider({ - hubs: this.config.Hubs, - deviceConfigs: this.config.Devices, - matrix: this.config.Matrix, - log: this.log - }); - - //Emit devices if requested - - this.dataProvider.on("Ready", () => { - this.log("All hubs connected"); - if (this.config.EmitDevicesOnStartup) { - const hubs = this.dataProvider!.hubs; - Object.values(hubs).forEach((hub: HarmonyHub) => { - const deviceDictionary = hub.devices; - this.log(`${hub.hubName}`) - - Object.values(deviceDictionary).forEach((device: HarmonyDevice) => { - this.log(` ${device.name} : ${device.id}`); - Object.keys(device.commands).forEach((command: string) => { - this.log(` ${command}`); - }); - }); - }); - } - }); - - } - } - - /** - * Handler for didFinishLaunching - * Happens after constructor - */ - didFinishLaunching() { - this.log(`Publishing external accessories`); - - //This is required in order to have multiple tv remotes on one platform - this.accessoryList.forEach((accessory: AccessoryPlugin) => { - if (accessory instanceof Accessories.ControlUnit) { - this.api.publishExternalAccessories("HarmonyMatrixPlatform", [accessory.platformAccessory]); - } - }) - } - - /** - * Called by homebridge to gather accessories. - * @param callback - */ - accessories(callback: (accessories: Array) => void) { - - //Add control units - this.config.ControlUnits.forEach((unit: Config.IControlUnit) => { - this.accessoryList.push(new Accessories.ControlUnit({ - dataProvider: this.dataProvider!, - displayName: unit.DisplayName, - api: this.api, - log: this.log, - activities: unit.Activities, - })); - }); - - //Add device buttons - this.config.DeviceButtons.forEach((button: Config.IDeviceButton) => { - this.accessoryList.push(new Accessories.DeviceButton({ - dataProvider: this.dataProvider!, - buttonName: button.ButtonName, - displayName: button.DisplayName, - deviceInfo: button, - api: this.api, - log: this.log - })) - }); - callback(this.accessoryList); - } -} \ No newline at end of file diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 0000000..ef24806 --- /dev/null +++ b/src/platform.ts @@ -0,0 +1,144 @@ +import { + API, + Characteristic, + DynamicPlatformPlugin, + Logger, + PlatformAccessory, + PlatformConfig, + Service, +} from "homebridge"; +import { ControlUnit, DeviceButton } from "./Accessories"; +import HarmonyDataProvider from "./DataProviders/HarmonyDataProvider"; +import { IConfig, IControlUnit, IDeviceButton } from "./Models/Config"; +import { HarmonyDevice } from "./Models/HarmonyDevice"; +import { HarmonyHub } from "./Models/HarmonyHub"; +import { PLATFORM_NAME, PLUGIN_NAME } from "./settings"; + +export class Platform implements DynamicPlatformPlugin { + constructor( + public readonly log: Logger, + config: PlatformConfig, + public readonly api: API + ) { + this.log.debug("Finished initializing platform:", config.name); + + this.config = (config as unknown) as IConfig; + //construct data provider + const dataProvider = new HarmonyDataProvider({ + hubs: this.config.Hubs, + deviceConfigs: this.config.Devices, + matrix: this.config.Matrix, + log: this.log, + }); + + this.api.on("didFinishLaunching", async () => { + log.debug("Executed didFinishLaunching callback"); + this.discoverDevices(dataProvider); + }); + + this.dataProvider = null; + + if (this.config) { + //construct data provider + this.dataProvider = new HarmonyDataProvider({ + hubs: this.config.Hubs, + deviceConfigs: this.config.Devices, + matrix: this.config.Matrix, + log: this.log, + }); + + //Emit devices if requested + + this.dataProvider.on("Ready", () => { + this.log.info("All hubs connected"); + if (this.config.EmitDevicesOnStartup) { + const hubs = this.dataProvider!.hubs; + Object.values(hubs).forEach((hub: HarmonyHub) => { + const deviceDictionary = hub.devices; + this.log.info(`${hub.hubName}`); + + Object.values(deviceDictionary).forEach((device: HarmonyDevice) => { + this.log.info(` ${device.name} : ${device.id}`); + Object.keys(device.commands).forEach((command: string) => { + this.log.info(` ${command}`); + }); + }); + }); + } + }); + } + } + + public readonly Service: typeof Service = this.api.hap.Service; + public readonly Characteristic: typeof Characteristic = this.api.hap + .Characteristic; + + // this is used to track restored cached accessories + public readonly accessories: PlatformAccessory[] = []; + public config: IConfig; + public dataProvider: HarmonyDataProvider | null; + + public discoverDevices(dataProvider: HarmonyDataProvider) { + this.config.ControlUnits.forEach((unit: IControlUnit) => { + const uuid = this.api.hap.uuid.generate(unit.DisplayName); + const existingAccessory = this.accessories.find((e) => e.UUID === uuid); + if (existingAccessory) { + this.log.info( + "Restoring existing accessory from cache: " + + existingAccessory.displayName + ); + + new ControlUnit(this, existingAccessory, dataProvider, unit.Activities); + + this.api.publishExternalAccessories(PLUGIN_NAME, [existingAccessory]); + console.log("Publishing external accessory: " + uuid); + } else { + this.log.info("Adding new accessory: " + unit.DisplayName); + const accessory = new this.api.platformAccessory( + unit.DisplayName, + uuid + ); + accessory.context["DeviceName"] = unit.DisplayName; + + new ControlUnit(this, accessory, dataProvider, unit.Activities); + + this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]); + console.log("Publishing external accessory: " + uuid); + } + }); + + this.config.DeviceButtons.forEach((button: IDeviceButton) => { + const uuid = this.api.hap.uuid.generate(button.DisplayName); + const existingAccessory = this.accessories.find((e) => e.UUID === uuid); + if (existingAccessory) { + this.log.info( + "Restoring existing accessory from cache: " + + existingAccessory.displayName + ); + + new DeviceButton(this, existingAccessory, dataProvider, button); + this.api.updatePlatformAccessories([existingAccessory]); + } else { + this.log.info("Adding new accessory: " + button.DisplayName); + const accessory = new this.api.platformAccessory( + button.DisplayName, + uuid + ); + accessory.context["DeviceName"] = button.DisplayName; + + new DeviceButton(this, accessory, dataProvider, button); + + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ + accessory, + ]); + } + }); + } + + configureAccessory(accessory: PlatformAccessory>): void { + this.log.info("Loading accessory from cache:", accessory.displayName); + + // add the restored accessory to the accessories cache so we can track if it has already been registered + this.accessories.push(accessory); + } +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..5daca0d --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,9 @@ +/** + * This is the name of the platform that users will use to register the plugin in the Homebridge config.json + */ +export const PLATFORM_NAME = "HarmonyHubMatrix"; + +/** + * This must match the name of your plugin as defined the package.json + */ +export const PLUGIN_NAME = "homebridge-harmony-control"; diff --git a/tsconfig.json b/tsconfig.json index 6d72b10..4e9341a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,13 @@ { - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "declaration": true, - "outDir": "./bin", - "strict": true, - "sourceMap": true, - }, - "include": [ - "src" - ], - "exclude": [ - "**/node_modules", - "**/__tests__/*" - ] -} \ No newline at end of file + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "declaration": true, + "outDir": "./bin", + "strict": true, + "sourceMap": true, + "skipLibCheck": true + }, + "include": ["src"], + "exclude": ["**/node_modules", "**/__tests__/*"] +}