diff --git a/package-lock.json b/package-lock.json index 1e6e6ab..85785a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,652 @@ { "name": "homebridge-automation", "version": "1.0.0", - "lockfileVersion": 1 + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime-corejs2": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.5.5.tgz", + "integrity": "sha512-FYATQVR00NSNi7mUfpPDp7E8RYMXDuO8gaix7u/w3GekfUinKgX1AcTxs7SoiEmoEW9mbpjrwqWSW6zCmw5h8A==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + } + }, + "@types/bluebird": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.27.tgz", + "integrity": "sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==" + }, + "@types/bonjour": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.5.tgz", + "integrity": "sha512-4fHAqgL0SYHgDmdBrm201hUl7gIavk8KAtwBuDX9TjVdEZbUENdKmnago4GsmDO8QALMUE651F+nNpaCF+B4+A==", + "requires": { + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/inflection": { + "version": "1.5.28", + "resolved": "https://registry.npmjs.org/@types/inflection/-/inflection-1.5.28.tgz", + "integrity": "sha1-Q9VeDXLPMzot/9nE7AQHRVobCTE=" + }, + "@types/node": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.3.tgz", + "integrity": "sha512-3SiLAIBkDWDg6vFo0+5YJyHPWU9uwu40Qe+v+0MH8wRKYBimHvvAOyk3EzMrD/TrIlLYfXrqDqrg913PynrMJQ==" + }, + "@types/request": { + "version": "2.48.2", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.2.tgz", + "integrity": "sha512-gP+PSFXAXMrd5PcD7SqHeUjdGshAI8vKQ3+AvpQr3ht9iQea+59LOKvKITcQI+Lg+1EIkDP6AFSBUJPWG8GDyA==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/request-promise": { + "version": "4.1.44", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.44.tgz", + "integrity": "sha512-RId7eFsUKxfal1LirDDIcOp9u3MM3NXFDBcC3sqIMcmu7f4U6DsCEMD8RbLZtnPrQlN5Jc79di/WPsIEDO4keg==", + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, + "@types/request-promise-native": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.16.tgz", + "integrity": "sha512-gbLf6cg1XGBU8BObOgs5VkCQo5JFz2GstgZjyE4FRbig/jiCEdiynu2fCzJlw3qYPuoj59spKnvuRLN4PsMvhA==", + "requires": { + "@types/request": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "decamelize": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz", + "integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==", + "requires": { + "xregexp": "^4.2.4" + } + }, + "deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", + "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "ip": { + "version": "1.1.5", + "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-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^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=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "requires": { + "dns-packet": "^1.3.1", + "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=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "psl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "requires": { + "define-properties": "^1.1.2" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "xregexp": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz", + "integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==", + "requires": { + "@babel/runtime-corejs2": "^7.2.0" + } + } + } } diff --git a/package.json b/package.json index 1cc3cc5..7a12386 100644 --- a/package.json +++ b/package.json @@ -22,5 +22,16 @@ ], "author": "Brandon Watson", "license": "ISC", - "dependencies": {} + "dependencies": { + "@types/bonjour": "^3.5.5", + "@types/inflection": "^1.5.28", + "@types/request-promise": "^4.1.44", + "@types/request-promise-native": "^1.0.16", + "bonjour": "^3.5.0", + "decamelize": "^3.2.0", + "inflection": "^1.12.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "source-map-support": "^0.5.13" + } } diff --git a/src/3rdParty/HapClient/eventedHttpClient/httpParser.ts b/src/3rdParty/HapClient/eventedHttpClient/httpParser.ts new file mode 100755 index 0000000..37e7f1a --- /dev/null +++ b/src/3rdParty/HapClient/eventedHttpClient/httpParser.ts @@ -0,0 +1,298 @@ +/** + * The contents in this file were taken from NorthernMan54/Hap-Node-Client + * https://raw.githubusercontent.com/NorthernMan54/Hap-Node-Client/master/lib/httpParser.js + */ + +// Borrowed and heaviliy modifed from https://github.com/miguelmota/http-message-parser + +import { Buffer } from 'buffer'; +import { Dictionary } from '../../../Types/types'; + +interface httpResult { + protocol?: string, + httpVersion?: number, + statusCode?: number, + statusMessage?: string, + method?: string, + url?: string, + headers?: Dictionary, + body?: any, + boundary?: any, + multipart?: any, + additional?: any, + meta?: any +} + +function httpMessageParser(message: any) { + const result: httpResult = {} + + let messageString = ''; + let headerNewlineIndex = 0; + let fullBoundary: any = null; + + if (httpMessageParser._isBuffer(message)) { + messageString = message.toString(); + } else if (typeof message === 'string') { + messageString = message; + message = new Buffer(messageString); + } else { + return result; + } + + /* + * Strip extra return characters + */ + messageString = messageString.replace(/\r\n/gim, '\n'); + + /* + * Trim leading whitespace + */ + (function () { + const firstNonWhitespaceRegex = /[\w-]+/gim; + const firstNonWhitespaceIndex = messageString.search(firstNonWhitespaceRegex); + if (firstNonWhitespaceIndex > 0) { + message = message.slice(firstNonWhitespaceIndex, message.length); + messageString = message.toString(); + } + })(); + + /* Parse request line + */ + (function () { + const possibleRequestLine = messageString.split(/\n|\r\n/)[0]; + const requestLineMatch = possibleRequestLine.match(httpMessageParser._requestLineRegex); + + if (Array.isArray(requestLineMatch) && requestLineMatch.length > 1) { + result.protocol = requestLineMatch[1]; + result.httpVersion = parseFloat(requestLineMatch[2]); + result.statusCode = parseInt(requestLineMatch[3], 10); + result.statusMessage = requestLineMatch[4]; + } else { + const responseLineMath = possibleRequestLine.match(httpMessageParser._responseLineRegex); + if (Array.isArray(responseLineMath) && responseLineMath.length > 1) { + result.method = responseLineMath[1]; + result.url = responseLineMath[2]; + result.httpVersion = parseFloat(responseLineMath[3]); + } + } + })(); + + /* Parse headers + */ + (function () { + headerNewlineIndex = messageString.search(httpMessageParser._headerNewlineRegex); + if (headerNewlineIndex > -1) { + headerNewlineIndex = headerNewlineIndex + 1; // 1 for newline length + } else { + /* There's no line breaks so check if request line exists + * because the message might be all headers and no body + */ + if (result.httpVersion) { + headerNewlineIndex = messageString.length; + } + } + + const headersString = messageString.substr(0, headerNewlineIndex); + const headers = httpMessageParser._parseHeaders(headersString); + + if (Object.keys(headers).length > 0) { + result.headers = headers; + + // TOOD: extract boundary. + } + })(); + + /* Try to get boundary if no boundary header + */ + (function () { + if (!result.boundary) { + const boundaryMatch = messageString.match(httpMessageParser._boundaryRegex); + + if (Array.isArray(boundaryMatch) && boundaryMatch.length) { + fullBoundary = boundaryMatch[0].replace(/[\r\n]+/gi, ''); + const boundary = fullBoundary.replace(/^--/, ''); + result.boundary = boundary; + } + } + })(); + + /* Parse body + */ + (function () { + let start = headerNewlineIndex; + let contentLength: number = result.headers!['Content-Length'] as number; + + let end = (result.headers && result.headers['Content-Length'] && contentLength ? contentLength + start : messageString.length); + + const firstBoundaryIndex = messageString.indexOf(fullBoundary); + + if (firstBoundaryIndex > -1 && result.boundary) { + start = headerNewlineIndex; + end = firstBoundaryIndex; + } + + if (headerNewlineIndex > -1) { + const body = messageString.slice(start, end); + result.additional = messageString.slice(end); // Pass back any unparsed data for running thru again + // console.log("Lengths: total %s -> start %s -> end %s -> final %s", messageString.length, start, end, body.length); + + if (body && body.length) { + if ((result.headers && result.headers['Content-Type'] === 'application/hap+json') || + (result.headers && result.headers['Content-Type'] === 'application/json')) { + // JSON.parse JSON message's + try { + if (result.headers['Content-Length']) { + result.body = body; + } else { + result.body = body.split('\n')[1]; + } + } catch (err) { + + } + } else { + result.body = body; + } + } + } + })(); + + /* Parse multipart sections + */ + (function () { + if (result.boundary) { + const multipartStart = messageString.indexOf(fullBoundary) + fullBoundary.length; + const multipartEnd = messageString.lastIndexOf(fullBoundary); + const multipartBody = messageString.substr(multipartStart, multipartEnd); + const splitRegex = new RegExp('^' + fullBoundary + '.*[\n\r]?$', 'gm'); + const parts = multipartBody.split(splitRegex); + + result.multipart = parts.filter(httpMessageParser._isTruthy).map(function (part, i) { + // tslint:disable-next-line: no-shadowed-variable + const result: httpResult = {}; + + const newlineRegex = /\n\n|\r\n\r\n/gim; + let newlineIndex = 0; + let newlineMatch = newlineRegex.exec(part); + let body = null; + + if (newlineMatch) { + newlineIndex = newlineMatch.index; + if (newlineMatch.index <= 0) { + newlineMatch = newlineRegex.exec(part); + if (newlineMatch) { + newlineIndex = newlineMatch.index; + } + } + } + + const possibleHeadersString = part.substr(0, newlineIndex); + + let startOffset = null; + let endOffset = null; + + if (newlineIndex > -1) { + const headers = httpMessageParser._parseHeaders(possibleHeadersString); + if (Object.keys(headers).length > 0) { + result.headers = headers; + + const boundaryIndexes: Array = []; + for (let j = 0; j >= 0;) { + j = message.indexOf(fullBoundary, j); + + if (j >= 0) { + boundaryIndexes.push(j); + j += fullBoundary.length; + } + } + + const boundaryNewlineIndexes: Array = []; + boundaryIndexes.slice(0, boundaryIndexes.length - 1).forEach(function (m, k) { + const partBody = message.slice(boundaryIndexes[k], boundaryIndexes[k + 1]).toString(); + // tslint:disable-next-line: no-shadowed-variable + let headerNewlineIndex = partBody.search(/\n\n|\r\n\r\n/gim) + 2; + headerNewlineIndex = boundaryIndexes[k] + headerNewlineIndex; + boundaryNewlineIndexes.push(headerNewlineIndex); + }); + + startOffset = boundaryNewlineIndexes[i]; + endOffset = boundaryIndexes[i + 1]; + body = message.slice(startOffset, endOffset); + } else { + body = part; + } + } else { + body = part; + } + + result.body = body; + result.meta.body.byteOffset.start = startOffset; + result.meta.body.byteOffset.end = endOffset; + + return result; + }); + } + })(); + + return result; +} + +httpMessageParser._isTruthy = function _isTruthy(value: any) { + return !!value; +}; + +httpMessageParser._isNumeric = function _isNumeric(value: any) { + if (typeof value === 'number' && !isNaN(value)) { + return true; + } + + value = (value || '').toString().trim(); + + if (!value) { + return false; + } + + return !isNaN(value); +}; + +httpMessageParser._isBuffer = function (item: any) { + return ((httpMessageParser._isNodeBufferSupported() && + typeof global === 'object' && + global.Buffer.isBuffer(item)) || + (item instanceof Object && + item._isBuffer)); +}; + +httpMessageParser._isNodeBufferSupported = function () { + return (typeof global === 'object' && + typeof global.Buffer === 'function' && + typeof global.Buffer.isBuffer === 'function'); +}; + +httpMessageParser._parseHeaders = function _parseHeaders(body: any) { + const headers: Dictionary = {}; + + if (typeof body !== 'string') { + return headers; + } + + body.split(/[\r\n]/).forEach(function (string) { + const match = string.match(/([\w-]+):\s*(.*)/i); + + if (Array.isArray(match) && match.length === 3) { + const key = match[1]; + const value = match[2]; + + headers[key] = httpMessageParser._isNumeric(value) ? Number(value) : value; + } + }); + + return headers; +}; + +httpMessageParser._requestLineRegex = /(HTTP|EVENT)\/(1\.0|1\.1|2\.0)\s+(\d+)\s+([\w\s-_]+)/i; +httpMessageParser._responseLineRegex = /(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|TRACE|CONNECT)\s+(.*)\s+HTTP\/(1\.0|1\.1|2\.0)/i; +// httpMessageParser._headerNewlineRegex = /^[\r\n]+/gim; +httpMessageParser._headerNewlineRegex = /^[\r\n]+/gim; +httpMessageParser._boundaryRegex = /(\n|\r\n)+--[\w-]+(\n|\r\n)+/g; + +export default httpMessageParser; diff --git a/src/3rdParty/HapClient/eventedHttpClient/index.ts b/src/3rdParty/HapClient/eventedHttpClient/index.ts new file mode 100755 index 0000000..008500d --- /dev/null +++ b/src/3rdParty/HapClient/eventedHttpClient/index.ts @@ -0,0 +1,59 @@ +/** + * The contents in this file were taken from NorthernMan54/Hap-Node-Client + * https://github.com/NorthernMan54/Hap-Node-Client/blob/master/lib/eventedHttpClient.js + */ + +import * as net from 'net'; +import * as url from 'url'; +import httpMessageParser from './/httpParser'; +import { Dictionary } from '../../../Types/types'; + +export const parseMessage = httpMessageParser; + +export function createConnection(instance: { ipAddress: string, port: number }, pin: string, body: any) { + const client = net.createConnection({ + host: instance.ipAddress, + port: instance.port, + }); + + client.write(_buildMessage({ + method: 'PUT', + url: 'http://' + instance.ipAddress + ':' + instance.port + '/characteristics', + maxAttempts: 1, // (default) try 5 times + headers: { + 'Content-Type': 'Application/json', + 'authorization': pin, + 'connection': 'keep-alive' + }, + body: JSON.stringify(body) + })); + + return client; +} + +function _headersToString(headers: Dictionary) { + let response = ''; + + for (const header of Object.keys(headers)) { + response = response + header + ': ' + headers[header] + '\r\n'; + } + return (response); +} + +function _buildMessage(request: any) { + const context = url.parse(request.url); + let message; + + message = request.method + ' ' + context.pathname; + if (context.search) { + message = message + context.search; + } + message = message + ' HTTP/1.1\r\nHost: ' + context.host + '\r\n' + _headersToString(request.headers); + if (request.body) { + message = message + 'Content-Length: ' + request.body.length + '\r\n\r\n' + request.body + '\r\n\r\n'; + } else { + message = message + '\r\n\r\n'; + } + // debug("Message ->", message); + return (message); +} diff --git a/src/3rdParty/HapClient/hap-types.ts b/src/3rdParty/HapClient/hap-types.ts new file mode 100755 index 0000000..9f5b2e3 --- /dev/null +++ b/src/3rdParty/HapClient/hap-types.ts @@ -0,0 +1,378 @@ +import { Dictionary } from "../../Types/types"; + +/* This file is automatically generated */ + +export const Services: Dictionary = { + '0000003E-0000-1000-8000-0026BB765291': 'AccessoryInformation', + 'AccessoryInformation': '0000003E-0000-1000-8000-0026BB765291', + '000000BB-0000-1000-8000-0026BB765291': 'AirPurifier', + 'AirPurifier': '000000BB-0000-1000-8000-0026BB765291', + '0000008D-0000-1000-8000-0026BB765291': 'AirQualitySensor', + 'AirQualitySensor': '0000008D-0000-1000-8000-0026BB765291', + '00000096-0000-1000-8000-0026BB765291': 'BatteryService', + 'BatteryService': '00000096-0000-1000-8000-0026BB765291', + '00000110-0000-1000-8000-0026BB765291': 'CameraRTPStreamManagement', + 'CameraRTPStreamManagement': '00000110-0000-1000-8000-0026BB765291', + '00000097-0000-1000-8000-0026BB765291': 'CarbonDioxideSensor', + 'CarbonDioxideSensor': '00000097-0000-1000-8000-0026BB765291', + '0000007F-0000-1000-8000-0026BB765291': 'CarbonMonoxideSensor', + 'CarbonMonoxideSensor': '0000007F-0000-1000-8000-0026BB765291', + '00000080-0000-1000-8000-0026BB765291': 'ContactSensor', + 'ContactSensor': '00000080-0000-1000-8000-0026BB765291', + '00000081-0000-1000-8000-0026BB765291': 'Door', + 'Door': '00000081-0000-1000-8000-0026BB765291', + '00000121-0000-1000-8000-0026BB765291': 'Doorbell', + 'Doorbell': '00000121-0000-1000-8000-0026BB765291', + '00000040-0000-1000-8000-0026BB765291': 'Fan', + 'Fan': '00000040-0000-1000-8000-0026BB765291', + '000000B7-0000-1000-8000-0026BB765291': 'Fanv2', + 'Fanv2': '000000B7-0000-1000-8000-0026BB765291', + '000000BA-0000-1000-8000-0026BB765291': 'FilterMaintenance', + 'FilterMaintenance': '000000BA-0000-1000-8000-0026BB765291', + '000000D7-0000-1000-8000-0026BB765291': 'Faucet', + 'Faucet': '000000D7-0000-1000-8000-0026BB765291', + '00000041-0000-1000-8000-0026BB765291': 'GarageDoorOpener', + 'GarageDoorOpener': '00000041-0000-1000-8000-0026BB765291', + '000000BC-0000-1000-8000-0026BB765291': 'HeaterCooler', + 'HeaterCooler': '000000BC-0000-1000-8000-0026BB765291', + '000000BD-0000-1000-8000-0026BB765291': 'HumidifierDehumidifier', + 'HumidifierDehumidifier': '000000BD-0000-1000-8000-0026BB765291', + '00000082-0000-1000-8000-0026BB765291': 'HumiditySensor', + 'HumiditySensor': '00000082-0000-1000-8000-0026BB765291', + '000000CF-0000-1000-8000-0026BB765291': 'IrrigationSystem', + 'IrrigationSystem': '000000CF-0000-1000-8000-0026BB765291', + '00000083-0000-1000-8000-0026BB765291': 'LeakSensor', + 'LeakSensor': '00000083-0000-1000-8000-0026BB765291', + '00000084-0000-1000-8000-0026BB765291': 'LightSensor', + 'LightSensor': '00000084-0000-1000-8000-0026BB765291', + '00000043-0000-1000-8000-0026BB765291': 'Lightbulb', + 'Lightbulb': '00000043-0000-1000-8000-0026BB765291', + '00000044-0000-1000-8000-0026BB765291': 'LockManagement', + 'LockManagement': '00000044-0000-1000-8000-0026BB765291', + '00000045-0000-1000-8000-0026BB765291': 'LockMechanism', + 'LockMechanism': '00000045-0000-1000-8000-0026BB765291', + '00000112-0000-1000-8000-0026BB765291': 'Microphone', + 'Microphone': '00000112-0000-1000-8000-0026BB765291', + '00000085-0000-1000-8000-0026BB765291': 'MotionSensor', + 'MotionSensor': '00000085-0000-1000-8000-0026BB765291', + '00000086-0000-1000-8000-0026BB765291': 'OccupancySensor', + 'OccupancySensor': '00000086-0000-1000-8000-0026BB765291', + '00000047-0000-1000-8000-0026BB765291': 'Outlet', + 'Outlet': '00000047-0000-1000-8000-0026BB765291', + '0000007E-0000-1000-8000-0026BB765291': 'SecuritySystem', + 'SecuritySystem': '0000007E-0000-1000-8000-0026BB765291', + '000000CC-0000-1000-8000-0026BB765291': 'ServiceLabel', + 'ServiceLabel': '000000CC-0000-1000-8000-0026BB765291', + '000000B9-0000-1000-8000-0026BB765291': 'Slat', + 'Slat': '000000B9-0000-1000-8000-0026BB765291', + '00000087-0000-1000-8000-0026BB765291': 'SmokeSensor', + 'SmokeSensor': '00000087-0000-1000-8000-0026BB765291', + '00000113-0000-1000-8000-0026BB765291': 'Speaker', + 'Speaker': '00000113-0000-1000-8000-0026BB765291', + '00000089-0000-1000-8000-0026BB765291': 'StatelessProgrammableSwitch', + 'StatelessProgrammableSwitch': '00000089-0000-1000-8000-0026BB765291', + '00000049-0000-1000-8000-0026BB765291': 'Switch', + 'Switch': '00000049-0000-1000-8000-0026BB765291', + '0000008A-0000-1000-8000-0026BB765291': 'TemperatureSensor', + 'TemperatureSensor': '0000008A-0000-1000-8000-0026BB765291', + '0000004A-0000-1000-8000-0026BB765291': 'Thermostat', + 'Thermostat': '0000004A-0000-1000-8000-0026BB765291', + '000000D0-0000-1000-8000-0026BB765291': 'Valve', + 'Valve': '000000D0-0000-1000-8000-0026BB765291', + '0000008B-0000-1000-8000-0026BB765291': 'Window', + 'Window': '0000008B-0000-1000-8000-0026BB765291', + '0000008C-0000-1000-8000-0026BB765291': 'WindowCovering', + 'WindowCovering': '0000008C-0000-1000-8000-0026BB765291', + '000000D8-0000-1000-8000-0026BB765291': 'Television', + 'Television': '000000D8-0000-1000-8000-0026BB765291', + '000000D9-0000-1000-8000-0026BB765291': 'InputSource', + 'InputSource': '000000D9-0000-1000-8000-0026BB765291', + 'TelevisionSpeaker': '00000113-0000-1000-8000-0026BB765291', +}; + +export const Characteristics: Dictionary = { + '000000A6-0000-1000-8000-0026BB765291': 'AccessoryFlags', + 'AccessoryFlags': '000000A6-0000-1000-8000-0026BB765291', + '000000B0-0000-1000-8000-0026BB765291': 'Active', + 'Active': '000000B0-0000-1000-8000-0026BB765291', + '00000001-0000-1000-8000-0026BB765291': 'AdministratorOnlyAccess', + 'AdministratorOnlyAccess': '00000001-0000-1000-8000-0026BB765291', + '00000064-0000-1000-8000-0026BB765291': 'AirParticulateDensity', + 'AirParticulateDensity': '00000064-0000-1000-8000-0026BB765291', + '00000065-0000-1000-8000-0026BB765291': 'AirParticulateSize', + 'AirParticulateSize': '00000065-0000-1000-8000-0026BB765291', + '00000095-0000-1000-8000-0026BB765291': 'AirQuality', + 'AirQuality': '00000095-0000-1000-8000-0026BB765291', + '00000005-0000-1000-8000-0026BB765291': 'AudioFeedback', + 'AudioFeedback': '00000005-0000-1000-8000-0026BB765291', + '00000068-0000-1000-8000-0026BB765291': 'BatteryLevel', + 'BatteryLevel': '00000068-0000-1000-8000-0026BB765291', + '00000008-0000-1000-8000-0026BB765291': 'Brightness', + 'Brightness': '00000008-0000-1000-8000-0026BB765291', + '00000092-0000-1000-8000-0026BB765291': 'CarbonDioxideDetected', + 'CarbonDioxideDetected': '00000092-0000-1000-8000-0026BB765291', + '00000093-0000-1000-8000-0026BB765291': 'CarbonDioxideLevel', + 'CarbonDioxideLevel': '00000093-0000-1000-8000-0026BB765291', + '00000094-0000-1000-8000-0026BB765291': 'CarbonDioxidePeakLevel', + 'CarbonDioxidePeakLevel': '00000094-0000-1000-8000-0026BB765291', + '00000069-0000-1000-8000-0026BB765291': 'CarbonMonoxideDetected', + 'CarbonMonoxideDetected': '00000069-0000-1000-8000-0026BB765291', + '00000090-0000-1000-8000-0026BB765291': 'CarbonMonoxideLevel', + 'CarbonMonoxideLevel': '00000090-0000-1000-8000-0026BB765291', + '00000091-0000-1000-8000-0026BB765291': 'CarbonMonoxidePeakLevel', + 'CarbonMonoxidePeakLevel': '00000091-0000-1000-8000-0026BB765291', + '0000008F-0000-1000-8000-0026BB765291': 'ChargingState', + 'ChargingState': '0000008F-0000-1000-8000-0026BB765291', + '000000CE-0000-1000-8000-0026BB765291': 'ColorTemperature', + 'ColorTemperature': '000000CE-0000-1000-8000-0026BB765291', + '0000006A-0000-1000-8000-0026BB765291': 'ContactSensorState', + 'ContactSensorState': '0000006A-0000-1000-8000-0026BB765291', + '0000000D-0000-1000-8000-0026BB765291': 'CoolingThresholdTemperature', + 'CoolingThresholdTemperature': '0000000D-0000-1000-8000-0026BB765291', + '000000A9-0000-1000-8000-0026BB765291': 'CurrentAirPurifierState', + 'CurrentAirPurifierState': '000000A9-0000-1000-8000-0026BB765291', + '0000006B-0000-1000-8000-0026BB765291': 'CurrentAmbientLightLevel', + 'CurrentAmbientLightLevel': '0000006B-0000-1000-8000-0026BB765291', + '0000000E-0000-1000-8000-0026BB765291': 'CurrentDoorState', + 'CurrentDoorState': '0000000E-0000-1000-8000-0026BB765291', + '000000AF-0000-1000-8000-0026BB765291': 'CurrentFanState', + 'CurrentFanState': '000000AF-0000-1000-8000-0026BB765291', + '000000B1-0000-1000-8000-0026BB765291': 'CurrentHeaterCoolerState', + 'CurrentHeaterCoolerState': '000000B1-0000-1000-8000-0026BB765291', + '0000000F-0000-1000-8000-0026BB765291': 'CurrentHeatingCoolingState', + 'CurrentHeatingCoolingState': '0000000F-0000-1000-8000-0026BB765291', + '0000006C-0000-1000-8000-0026BB765291': 'CurrentHorizontalTiltAngle', + 'CurrentHorizontalTiltAngle': '0000006C-0000-1000-8000-0026BB765291', + '000000B3-0000-1000-8000-0026BB765291': 'CurrentHumidifierDehumidifierState', + 'CurrentHumidifierDehumidifierState': '000000B3-0000-1000-8000-0026BB765291', + '0000006D-0000-1000-8000-0026BB765291': 'CurrentPosition', + 'CurrentPosition': '0000006D-0000-1000-8000-0026BB765291', + '00000010-0000-1000-8000-0026BB765291': 'CurrentRelativeHumidity', + 'CurrentRelativeHumidity': '00000010-0000-1000-8000-0026BB765291', + '000000AA-0000-1000-8000-0026BB765291': 'CurrentSlatState', + 'CurrentSlatState': '000000AA-0000-1000-8000-0026BB765291', + '00000011-0000-1000-8000-0026BB765291': 'CurrentTemperature', + 'CurrentTemperature': '00000011-0000-1000-8000-0026BB765291', + '000000C1-0000-1000-8000-0026BB765291': 'CurrentTiltAngle', + 'CurrentTiltAngle': '000000C1-0000-1000-8000-0026BB765291', + '0000006E-0000-1000-8000-0026BB765291': 'CurrentVerticalTiltAngle', + 'CurrentVerticalTiltAngle': '0000006E-0000-1000-8000-0026BB765291', + '0000011D-0000-1000-8000-0026BB765291': 'DigitalZoom', + 'DigitalZoom': '0000011D-0000-1000-8000-0026BB765291', + '000000AC-0000-1000-8000-0026BB765291': 'FilterChangeIndication', + 'FilterChangeIndication': '000000AC-0000-1000-8000-0026BB765291', + '000000AB-0000-1000-8000-0026BB765291': 'FilterLifeLevel', + 'FilterLifeLevel': '000000AB-0000-1000-8000-0026BB765291', + '00000052-0000-1000-8000-0026BB765291': 'FirmwareRevision', + 'FirmwareRevision': '00000052-0000-1000-8000-0026BB765291', + '00000053-0000-1000-8000-0026BB765291': 'HardwareRevision', + 'HardwareRevision': '00000053-0000-1000-8000-0026BB765291', + '00000012-0000-1000-8000-0026BB765291': 'HeatingThresholdTemperature', + 'HeatingThresholdTemperature': '00000012-0000-1000-8000-0026BB765291', + '0000006F-0000-1000-8000-0026BB765291': 'HoldPosition', + 'HoldPosition': '0000006F-0000-1000-8000-0026BB765291', + '00000013-0000-1000-8000-0026BB765291': 'Hue', + 'Hue': '00000013-0000-1000-8000-0026BB765291', + '00000014-0000-1000-8000-0026BB765291': 'Identify', + 'Identify': '00000014-0000-1000-8000-0026BB765291', + '0000011F-0000-1000-8000-0026BB765291': 'ImageMirroring', + 'ImageMirroring': '0000011F-0000-1000-8000-0026BB765291', + '0000011E-0000-1000-8000-0026BB765291': 'ImageRotation', + 'ImageRotation': '0000011E-0000-1000-8000-0026BB765291', + '000000D2-0000-1000-8000-0026BB765291': 'InUse', + 'InUse': '000000D2-0000-1000-8000-0026BB765291', + '000000D6-0000-1000-8000-0026BB765291': 'IsConfigured', + 'IsConfigured': '000000D6-0000-1000-8000-0026BB765291', + '00000070-0000-1000-8000-0026BB765291': 'LeakDetected', + 'LeakDetected': '00000070-0000-1000-8000-0026BB765291', + '00000019-0000-1000-8000-0026BB765291': 'LockControlPoint', + 'LockControlPoint': '00000019-0000-1000-8000-0026BB765291', + '0000001D-0000-1000-8000-0026BB765291': 'LockCurrentState', + 'LockCurrentState': '0000001D-0000-1000-8000-0026BB765291', + '0000001C-0000-1000-8000-0026BB765291': 'LockLastKnownAction', + 'LockLastKnownAction': '0000001C-0000-1000-8000-0026BB765291', + '0000001A-0000-1000-8000-0026BB765291': 'LockManagementAutoSecurityTimeout', + 'LockManagementAutoSecurityTimeout': '0000001A-0000-1000-8000-0026BB765291', + '000000A7-0000-1000-8000-0026BB765291': 'LockPhysicalControls', + 'LockPhysicalControls': '000000A7-0000-1000-8000-0026BB765291', + '0000001E-0000-1000-8000-0026BB765291': 'LockTargetState', + 'LockTargetState': '0000001E-0000-1000-8000-0026BB765291', + '0000001F-0000-1000-8000-0026BB765291': 'Logs', + 'Logs': '0000001F-0000-1000-8000-0026BB765291', + '00000020-0000-1000-8000-0026BB765291': 'Manufacturer', + 'Manufacturer': '00000020-0000-1000-8000-0026BB765291', + '00000021-0000-1000-8000-0026BB765291': 'Model', + 'Model': '00000021-0000-1000-8000-0026BB765291', + '00000022-0000-1000-8000-0026BB765291': 'MotionDetected', + 'MotionDetected': '00000022-0000-1000-8000-0026BB765291', + '0000011A-0000-1000-8000-0026BB765291': 'Mute', + 'Mute': '0000011A-0000-1000-8000-0026BB765291', + '00000023-0000-1000-8000-0026BB765291': 'Name', + 'Name': '00000023-0000-1000-8000-0026BB765291', + '0000011B-0000-1000-8000-0026BB765291': 'NightVision', + 'NightVision': '0000011B-0000-1000-8000-0026BB765291', + '000000C4-0000-1000-8000-0026BB765291': 'NitrogenDioxideDensity', + 'NitrogenDioxideDensity': '000000C4-0000-1000-8000-0026BB765291', + '00000024-0000-1000-8000-0026BB765291': 'ObstructionDetected', + 'ObstructionDetected': '00000024-0000-1000-8000-0026BB765291', + '00000071-0000-1000-8000-0026BB765291': 'OccupancyDetected', + 'OccupancyDetected': '00000071-0000-1000-8000-0026BB765291', + '00000025-0000-1000-8000-0026BB765291': 'On', + 'On': '00000025-0000-1000-8000-0026BB765291', + '0000011C-0000-1000-8000-0026BB765291': 'OpticalZoom', + 'OpticalZoom': '0000011C-0000-1000-8000-0026BB765291', + '00000026-0000-1000-8000-0026BB765291': 'OutletInUse', + 'OutletInUse': '00000026-0000-1000-8000-0026BB765291', + '000000C3-0000-1000-8000-0026BB765291': 'OzoneDensity', + 'OzoneDensity': '000000C3-0000-1000-8000-0026BB765291', + '0000004C-0000-1000-8000-0026BB765291': 'PairSetup', + 'PairSetup': '0000004C-0000-1000-8000-0026BB765291', + '0000004E-0000-1000-8000-0026BB765291': 'PairVerify', + 'PairVerify': '0000004E-0000-1000-8000-0026BB765291', + '0000004F-0000-1000-8000-0026BB765291': 'PairingFeatures', + 'PairingFeatures': '0000004F-0000-1000-8000-0026BB765291', + '00000050-0000-1000-8000-0026BB765291': 'PairingPairings', + 'PairingPairings': '00000050-0000-1000-8000-0026BB765291', + '000000C7-0000-1000-8000-0026BB765291': 'PM10Density', + 'PM10Density': '000000C7-0000-1000-8000-0026BB765291', + '000000C6-0000-1000-8000-0026BB765291': 'PM2_5Density', + 'PM2_5Density': '000000C6-0000-1000-8000-0026BB765291', + '00000072-0000-1000-8000-0026BB765291': 'PositionState', + 'PositionState': '00000072-0000-1000-8000-0026BB765291', + '000000D1-0000-1000-8000-0026BB765291': 'ProgramMode', + 'ProgramMode': '000000D1-0000-1000-8000-0026BB765291', + '00000073-0000-1000-8000-0026BB765291': 'ProgrammableSwitchEvent', + 'ProgrammableSwitchEvent': '00000073-0000-1000-8000-0026BB765291', + '000000C9-0000-1000-8000-0026BB765291': 'RelativeHumidityDehumidifierThreshold', + 'RelativeHumidityDehumidifierThreshold': '000000C9-0000-1000-8000-0026BB765291', + '000000CA-0000-1000-8000-0026BB765291': 'RelativeHumidityHumidifierThreshold', + 'RelativeHumidityHumidifierThreshold': '000000CA-0000-1000-8000-0026BB765291', + '000000D4-0000-1000-8000-0026BB765291': 'RemainingDuration', + 'RemainingDuration': '000000D4-0000-1000-8000-0026BB765291', + '000000AD-0000-1000-8000-0026BB765291': 'ResetFilterIndication', + 'ResetFilterIndication': '000000AD-0000-1000-8000-0026BB765291', + '00000028-0000-1000-8000-0026BB765291': 'RotationDirection', + 'RotationDirection': '00000028-0000-1000-8000-0026BB765291', + '00000029-0000-1000-8000-0026BB765291': 'RotationSpeed', + 'RotationSpeed': '00000029-0000-1000-8000-0026BB765291', + '0000002F-0000-1000-8000-0026BB765291': 'Saturation', + 'Saturation': '0000002F-0000-1000-8000-0026BB765291', + '0000008E-0000-1000-8000-0026BB765291': 'SecuritySystemAlarmType', + 'SecuritySystemAlarmType': '0000008E-0000-1000-8000-0026BB765291', + '00000066-0000-1000-8000-0026BB765291': 'SecuritySystemCurrentState', + 'SecuritySystemCurrentState': '00000066-0000-1000-8000-0026BB765291', + '00000067-0000-1000-8000-0026BB765291': 'SecuritySystemTargetState', + 'SecuritySystemTargetState': '00000067-0000-1000-8000-0026BB765291', + '00000117-0000-1000-8000-0026BB765291': 'SelectedRTPStreamConfiguration', + 'SelectedRTPStreamConfiguration': '00000117-0000-1000-8000-0026BB765291', + '00000030-0000-1000-8000-0026BB765291': 'SerialNumber', + 'SerialNumber': '00000030-0000-1000-8000-0026BB765291', + '000000CB-0000-1000-8000-0026BB765291': 'ServiceLabelIndex', + 'ServiceLabelIndex': '000000CB-0000-1000-8000-0026BB765291', + '000000CD-0000-1000-8000-0026BB765291': 'ServiceLabelNamespace', + 'ServiceLabelNamespace': '000000CD-0000-1000-8000-0026BB765291', + '000000D3-0000-1000-8000-0026BB765291': 'SetDuration', + 'SetDuration': '000000D3-0000-1000-8000-0026BB765291', + '00000118-0000-1000-8000-0026BB765291': 'SetupEndpoints', + 'SetupEndpoints': '00000118-0000-1000-8000-0026BB765291', + '000000C0-0000-1000-8000-0026BB765291': 'SlatType', + 'SlatType': '000000C0-0000-1000-8000-0026BB765291', + '00000076-0000-1000-8000-0026BB765291': 'SmokeDetected', + 'SmokeDetected': '00000076-0000-1000-8000-0026BB765291', + '00000075-0000-1000-8000-0026BB765291': 'StatusActive', + 'StatusActive': '00000075-0000-1000-8000-0026BB765291', + '00000077-0000-1000-8000-0026BB765291': 'StatusFault', + 'StatusFault': '00000077-0000-1000-8000-0026BB765291', + '00000078-0000-1000-8000-0026BB765291': 'StatusJammed', + 'StatusJammed': '00000078-0000-1000-8000-0026BB765291', + '00000079-0000-1000-8000-0026BB765291': 'StatusLowBattery', + 'StatusLowBattery': '00000079-0000-1000-8000-0026BB765291', + '0000007A-0000-1000-8000-0026BB765291': 'StatusTampered', + 'StatusTampered': '0000007A-0000-1000-8000-0026BB765291', + '00000120-0000-1000-8000-0026BB765291': 'StreamingStatus', + 'StreamingStatus': '00000120-0000-1000-8000-0026BB765291', + '000000C5-0000-1000-8000-0026BB765291': 'SulphurDioxideDensity', + 'SulphurDioxideDensity': '000000C5-0000-1000-8000-0026BB765291', + '00000115-0000-1000-8000-0026BB765291': 'SupportedAudioStreamConfiguration', + 'SupportedAudioStreamConfiguration': '00000115-0000-1000-8000-0026BB765291', + '00000116-0000-1000-8000-0026BB765291': 'SupportedRTPConfiguration', + 'SupportedRTPConfiguration': '00000116-0000-1000-8000-0026BB765291', + '00000114-0000-1000-8000-0026BB765291': 'SupportedVideoStreamConfiguration', + 'SupportedVideoStreamConfiguration': '00000114-0000-1000-8000-0026BB765291', + '000000B6-0000-1000-8000-0026BB765291': 'SwingMode', + 'SwingMode': '000000B6-0000-1000-8000-0026BB765291', + '000000A8-0000-1000-8000-0026BB765291': 'TargetAirPurifierState', + 'TargetAirPurifierState': '000000A8-0000-1000-8000-0026BB765291', + '000000AE-0000-1000-8000-0026BB765291': 'TargetAirQuality', + 'TargetAirQuality': '000000AE-0000-1000-8000-0026BB765291', + '00000032-0000-1000-8000-0026BB765291': 'TargetDoorState', + 'TargetDoorState': '00000032-0000-1000-8000-0026BB765291', + '000000BF-0000-1000-8000-0026BB765291': 'TargetFanState', + 'TargetFanState': '000000BF-0000-1000-8000-0026BB765291', + '000000B2-0000-1000-8000-0026BB765291': 'TargetHeaterCoolerState', + 'TargetHeaterCoolerState': '000000B2-0000-1000-8000-0026BB765291', + '00000033-0000-1000-8000-0026BB765291': 'TargetHeatingCoolingState', + 'TargetHeatingCoolingState': '00000033-0000-1000-8000-0026BB765291', + '0000007B-0000-1000-8000-0026BB765291': 'TargetHorizontalTiltAngle', + 'TargetHorizontalTiltAngle': '0000007B-0000-1000-8000-0026BB765291', + '000000B4-0000-1000-8000-0026BB765291': 'TargetHumidifierDehumidifierState', + 'TargetHumidifierDehumidifierState': '000000B4-0000-1000-8000-0026BB765291', + '0000007C-0000-1000-8000-0026BB765291': 'TargetPosition', + 'TargetPosition': '0000007C-0000-1000-8000-0026BB765291', + '00000034-0000-1000-8000-0026BB765291': 'TargetRelativeHumidity', + 'TargetRelativeHumidity': '00000034-0000-1000-8000-0026BB765291', + '000000BE-0000-1000-8000-0026BB765291': 'TargetSlatState', + 'TargetSlatState': '000000BE-0000-1000-8000-0026BB765291', + '00000035-0000-1000-8000-0026BB765291': 'TargetTemperature', + 'TargetTemperature': '00000035-0000-1000-8000-0026BB765291', + '000000C2-0000-1000-8000-0026BB765291': 'TargetTiltAngle', + 'TargetTiltAngle': '000000C2-0000-1000-8000-0026BB765291', + '0000007D-0000-1000-8000-0026BB765291': 'TargetVerticalTiltAngle', + 'TargetVerticalTiltAngle': '0000007D-0000-1000-8000-0026BB765291', + '00000036-0000-1000-8000-0026BB765291': 'TemperatureDisplayUnits', + 'TemperatureDisplayUnits': '00000036-0000-1000-8000-0026BB765291', + '000000D5-0000-1000-8000-0026BB765291': 'ValveType', + 'ValveType': '000000D5-0000-1000-8000-0026BB765291', + '00000037-0000-1000-8000-0026BB765291': 'Version', + 'Version': '00000037-0000-1000-8000-0026BB765291', + '000000C8-0000-1000-8000-0026BB765291': 'VOCDensity', + 'VOCDensity': '000000C8-0000-1000-8000-0026BB765291', + '00000119-0000-1000-8000-0026BB765291': 'Volume', + 'Volume': '00000119-0000-1000-8000-0026BB765291', + '000000B5-0000-1000-8000-0026BB765291': 'WaterLevel', + 'WaterLevel': '000000B5-0000-1000-8000-0026BB765291', + '000000E7-0000-1000-8000-0026BB765291': 'ActiveIdentifier', + 'ActiveIdentifier': '000000E7-0000-1000-8000-0026BB765291', + '000000E3-0000-1000-8000-0026BB765291': 'ConfiguredName', + 'ConfiguredName': '000000E3-0000-1000-8000-0026BB765291', + '000000E8-0000-1000-8000-0026BB765291': 'SleepDiscoveryMode', + 'SleepDiscoveryMode': '000000E8-0000-1000-8000-0026BB765291', + '000000DD-0000-1000-8000-0026BB765291': 'ClosedCaptions', + 'ClosedCaptions': '000000DD-0000-1000-8000-0026BB765291', + '00000136-0000-1000-8000-0026BB765291': 'DisplayOrder', + 'DisplayOrder': '00000136-0000-1000-8000-0026BB765291', + '000000E0-0000-1000-8000-0026BB765291': 'CurrentMediaState', + 'CurrentMediaState': '000000E0-0000-1000-8000-0026BB765291', + '00000137-0000-1000-8000-0026BB765291': 'TargetMediaState', + 'TargetMediaState': '00000137-0000-1000-8000-0026BB765291', + '000000E2-0000-1000-8000-0026BB765291': 'PictureMode', + 'PictureMode': '000000E2-0000-1000-8000-0026BB765291', + '000000DF-0000-1000-8000-0026BB765291': 'PowerModeSelection', + 'PowerModeSelection': '000000DF-0000-1000-8000-0026BB765291', + '000000E1-0000-1000-8000-0026BB765291': 'RemoteKey', + 'RemoteKey': '000000E1-0000-1000-8000-0026BB765291', + '000000DB-0000-1000-8000-0026BB765291': 'InputSourceType', + 'InputSourceType': '000000DB-0000-1000-8000-0026BB765291', + '000000DC-0000-1000-8000-0026BB765291': 'InputDeviceType', + 'InputDeviceType': '000000DC-0000-1000-8000-0026BB765291', + '000000E6-0000-1000-8000-0026BB765291': 'Identifier', + 'Identifier': '000000E6-0000-1000-8000-0026BB765291', + '00000135-0000-1000-8000-0026BB765291': 'CurrentVisibilityState', + 'CurrentVisibilityState': '00000135-0000-1000-8000-0026BB765291', + '00000134-0000-1000-8000-0026BB765291': 'TargetVisibilityState', + 'TargetVisibilityState': '00000134-0000-1000-8000-0026BB765291', + '000000E9-0000-1000-8000-0026BB765291': 'VolumeControlType', + 'VolumeControlType': '000000E9-0000-1000-8000-0026BB765291', + '000000EA-0000-1000-8000-0026BB765291': 'VolumeSelector', + 'VolumeSelector': '000000EA-0000-1000-8000-0026BB765291', +}; diff --git a/src/3rdParty/HapClient/index.ts b/src/3rdParty/HapClient/index.ts new file mode 100755 index 0000000..d22b107 --- /dev/null +++ b/src/3rdParty/HapClient/index.ts @@ -0,0 +1,425 @@ +import 'source-map-support/register'; +import * as crypto from 'crypto'; +import decamelize from 'decamelize'; +import * as inflection from 'inflection'; +import Bonjour, { Service } from 'bonjour'; +import { EventEmitter } from 'events'; +import { get, put } from 'request-promise-native'; + +import { Services, Characteristics } from './hap-types'; +import { HapMonitor } from './monitor'; +import { IHapAccessoriesRespType, IServiceType, ICharacteristicType, IHapInstance, createDefaultCharacteristicType } from './interfaces'; + +export type HapAccessoriesRespType = IHapAccessoriesRespType; +export type ServiceType = IServiceType; +export type CharacteristicType = ICharacteristicType; + +export interface IDevice { + addresses: Array + fqdn: string, + host: string, + name: string, + port: number, + protocol: "tcp" | "udp", + rawTxt: Buffer, + referer: { + address: string, + family: "IPv4" | "IPv6", + port: number, + size: number, + }, + subtypes: Array, + txt: any, + type: string +} + +export class HapClient extends EventEmitter { + private bonjour = Bonjour(); + private browser?: Bonjour.Browser; + private discoveryInProgress = false; + + private log: (msg: string) => void; + private pin: string; + private debugEnabled: boolean; + private config: { + debug?: boolean; + instanceBlacklist?: string[]; + }; + + private instances: IHapInstance[] = []; + + private hiddenServices = [ + Services.AccessoryInformation + ]; + + private hiddenCharacteristics = [ + Characteristics.Name + ]; + + constructor(opts: { + pin: string; + logger?: any; + config: any; + }) { + super(); + + this.pin = opts.pin; + this.log = opts.logger; + this.debugEnabled = true; + this.config = opts.config; + this.startDiscovery(); + } + + private debug(msg: any) { + if (this.debugEnabled) { + this.log(msg); + } + } + + private debugErr(msg: string, err: Error) { + if (this.debugEnabled) { + this.log(`${msg}: ${err.message}\n ${err.stack}`); + } + } + + public refreshInstances() { + if (!this.discoveryInProgress) { + this.startDiscovery(); + } else { + try { + this.debug(`[HapClient] Discovery :: Re-broadcasting discovery query`); + if (this.browser) { + this.browser.update(); + } + + } catch (e) { } + } + } + + private async startDiscovery() { + this.discoveryInProgress = true; + + this.browser = this.bonjour.find({ + type: 'hap' + }); + + // start matching services + this.browser.start(); + this.debug(`[HapClient] Discovery :: Started`); + + // stop discovery after 20 seconds + setTimeout(() => { + if (this.browser) { + this.browser.stop(); + } + this.debug(`[HapClient] Discovery :: Ended`); + this.discoveryInProgress = false; + }, 60000); + + // service found + this.browser.on('up', async (service: any) => { + let device = service as IDevice; + if (!device || !device.txt) { + this.debug(`[HapClient] Discovery :: Ignoring device that contains no txt records. ${JSON.stringify(device)}`); + return; + } + + const instance: IHapInstance = { + displayName: device.name, + ipAddress: device.addresses[0], + name: device.txt.md, + username: device.txt.id, + port: device.port, + } + + this.debug(`[HapClient] Discovery :: Found HAP device ${instance.displayName} with username ${instance.username}`); + + // update an existing instance + const existingInstanceIndex = this.instances.findIndex(x => x.username === instance.username); + if (existingInstanceIndex > -1) { + + if ( + this.instances[existingInstanceIndex].port !== instance.port || + this.instances[existingInstanceIndex].name !== instance.name + ) { + this.instances[existingInstanceIndex].port = instance.port; + this.instances[existingInstanceIndex].name = instance.name; + this.debug(`[HapClient] Discovery :: [${this.instances[existingInstanceIndex].ipAddress}:${instance.port} ` + + `(${instance.username})] Instance Updated`); + this.emit('instance-discovered', instance); + } + + return; + } + + //Comenting out because of lack of config + + // check instance is not on the blacklist + // if (this.config.instanceBlacklist && this.config.instanceBlacklist.find(x => instance.username.toLowerCase() === x.toLowerCase())) { + // this.debug(`[HapClient] Discovery :: Instance with username ${instance.username} found in blacklist. Disregarding.`); + // return; + // } + + for (const ip of device.addresses) { + if (ip.match(/^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\.(?!$)|$)){4}$/)) { + try { + this.debug(`[HapClient] Discovery :: Testing ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`); + const test = await get(`http://${ip}:${device.port}/accessories`, { + json: true, + timeout: 1000, + }); + if (test.accessories) { + this.debug(`[HapClient] Discovery :: Success ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`); + instance.ipAddress = ip; + } + break; + } catch (e) { + this.debug(`[HapClient] Discovery :: ***Failed*** ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`); + } + } + } + + // store instance record + if (instance.ipAddress) { + this.instances.push(instance); + this.debug(`[HapClient] Discovery :: [${instance.displayName} - ${instance.ipAddress}:${instance.port} (${instance.username})] Instance Registered`); + this.emit('instance-discovered', instance); + } else { + this.debug(`[HapClient] Discovery :: Could not register to device ${instance.displayName} with username ${instance.username}`); + } + }); + + } + + private async getAccessories() { + if (!this.instances.length) { + this.debug('[HapClient] Cannot load accessories. No Homebridge instances have been discovered.'); + } + + const accessories = []; + for (const instance of this.instances) { + try { + const resp: IHapAccessoriesRespType = await get(`http://${instance.ipAddress}:${instance.port}/accessories`, { json: true }); + for (const accessory of resp.accessories) { + accessory.instance = instance; + accessories.push(accessory); + } + } catch (e) { + if (this.log) { + this.debugErr(`[HapClient] [${instance.displayName} - ${instance.ipAddress}:${instance.port} (${instance.username})] Failed to connect`, e); + } + } + } + return accessories; + } + + public async monitorCharacteristics(): Promise { + const services = await this.getAllServices(); + return new HapMonitor(this.log, this.debug.bind(this), this.pin, services); + } + + public async getAllServices(): Promise> { + /* Get Accessories from HAP */ + const accessories = await this.getAccessories(); + + const services: Array = []; + + /* Parse All Accessories */ + accessories.forEach(accessory => { + + /* Parse Accessory Information */ + const accessoryInformationService = accessory.services.find(x => x.type === Services.AccessoryInformation); + const accessoryInformation: { [key: string]: any } = {}; + + if (accessoryInformationService && accessoryInformationService.characteristics) { + accessoryInformationService.characteristics.forEach((c) => { + if (c.value) { + accessoryInformation[c.description] = c.value; + } + }); + } + + /* Parse All Services */ + accessory.services + .filter((s) => this.hiddenServices.indexOf(s.type) < 0 && Services[s.type]) + .map((s) => { + let serviceName = s.characteristics.find(x => x.type === Characteristics.Name); + + /* Set default name characteristic if none defined */ + serviceName = serviceName ? serviceName : { + iid: 0, + type: Characteristics.Name, + description: 'Name', + format: 'string', + value: this.humanizeString(Services[s.type]), + perms: ['pr'] + }; + + /* Parse Service Characteristics */ + const serviceCharacteristics: Array = s.characteristics + .filter((c) => this.hiddenCharacteristics.indexOf(c.type) < 0 && Characteristics[c.type]) + .map((c) => { + return { + aid: accessory.aid, + iid: c.iid, + uuid: c.type, + type: Characteristics[c.type], + serviceType: Services[s.type], + serviceName: serviceName!.value.toString(), + description: c.description, + value: c.value, + format: c.format, + perms: c.perms, + unit: c.unit, + maxValue: c.maxValue, + minValue: c.minValue, + minStep: c.minStep, + canRead: c.perms.includes('pr'), + canWrite: c.perms.includes('pw'), + ev: c.perms.includes('ev'), + }; + }); + + const service: IServiceType = { + aid: accessory.aid, + iid: s.iid, + uuid: s.type, + type: Services[s.type], + humanType: this.humanizeString(Services[s.type]), + serviceName: serviceName.value.toString(), + serviceCharacteristics: serviceCharacteristics, + accessoryInformation: accessoryInformation, + values: {}, + linked: s.linked, + instance: accessory.instance, + }; + + // generate unique id for service + service.uniqueId = crypto.createHash('sha256') + .update(`${service.instance.username}${service.aid}${service.iid}${service.type}`) + .digest('hex'); + + /* Helper function to trigger a call to the accessory to get all the characteristic values */ + service.refreshCharacteristics = () => { + return this.refreshServiceCharacteristics.bind(this)(service); + }; + + /* Helper function to set the value of a characteristic */ + service.setCharacteristic = (iid: number, value: number | string | boolean): Promise => { + return this.setCharacteristic.bind(this)(service, iid, value); + }; + + /* Helper function to returns a characteristic by it's type name */ + service.getCharacteristic = (type: string): ICharacteristicType => { + let characteristic = service.serviceCharacteristics.find(c => c.type === type); + return characteristic ? characteristic : createDefaultCharacteristicType(); + }; + + service.serviceCharacteristics.forEach((cType: ICharacteristicType) => { + /* Helper function to set the value of a characteristic */ + cType.setValue = async (value: number | string | boolean): Promise => { + return await this.setCharacteristic.bind(this)(service, cType.iid, value); + }; + + /* Helper function to get the value of a characteristic from the accessory */ + cType.getValue = async (): Promise => { + return await this.getCharacteristic.bind(this)(service, cType.iid); + }; + + /* set the values for each characteristic type in an easy-to-access object */ + service.values[cType.type] = cType.value; + }); + + services.push(service); + }); + }); + + return services; + } + + async getService(iid: number) { + const services = await this.getAllServices(); + return services.find(x => x.iid === iid); + } + + async getServiceByName(serviceName: string) { + const services = await this.getAllServices(); + return services.find(x => x.serviceName === serviceName); + } + + async refreshServiceCharacteristics(service: IServiceType): Promise { + const iids: number[] = service.serviceCharacteristics.map(c => c.iid); + + const resp = await get(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, { + qs: { + id: iids.map(iid => `${service.aid}.${iid}`).join(',') + }, + json: true + }); + + resp.characteristics.forEach((c: ICharacteristicType) => { + const characteristic = service.serviceCharacteristics.find(x => x.iid === c.iid && x.aid === service.aid); + characteristic!.value = c.value; + }); + + return service; + } + + async getCharacteristic(service: IServiceType, iid: number): Promise { + const resp = await get(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, { + qs: { + id: `${service.aid}.${iid}` + }, + json: true + }); + + const characteristic = service.serviceCharacteristics.find(x => x.iid === resp.characteristics[0].iid && x.aid === service.aid); + if (characteristic) { + characteristic.value = resp.characteristics[0].value; + } + + return characteristic ? characteristic : createDefaultCharacteristicType(); + } + + async setCharacteristic(service: IServiceType, iid: number, value: number | string | boolean): Promise { + let characteristic = createDefaultCharacteristicType(); + try { + await put(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, { + headers: { + Authorization: this.pin + }, + json: { + characteristics: [ + { + aid: service.aid, + iid: iid, + value: value + } + ] + } + }); + + characteristic = await this.getCharacteristic(service, iid); + } catch (e) { + if (this.log) { + this.debugErr(`[HapClient] [${service.instance.ipAddress}:${service.instance.port} (${service.instance.username})] ` + + `Failed to set value for ${service.serviceName}.`, e); + if (e.statusCode === 401) { + this.debug(`[HapClient] [${service.instance.ipAddress}:${service.instance.port} (${service.instance.username})] ` + + `Make sure Homebridge pin for this instance is set to ${this.pin}.`); + } else { + this.debug(e.message); + } + } else { + console.log(e); + } + } + + return characteristic + } + + private humanizeString(string: string) { + return inflection.titleize(decamelize(string)); + } + +} diff --git a/src/3rdParty/HapClient/interfaces.ts b/src/3rdParty/HapClient/interfaces.ts new file mode 100755 index 0000000..04b937b --- /dev/null +++ b/src/3rdParty/HapClient/interfaces.ts @@ -0,0 +1,116 @@ +import { Socket } from 'net'; + +export interface IHapInstance { + displayName: string + name: string; + ipAddress: string; + port: number; + username: string; +} + +export interface IHapEvInstance { + name: string; + ipAddress: string; + port: number; + username: string; + evCharacteristics?: { aid: number, iid: number, ev: boolean }[]; + socket?: Socket; +} + +export interface IHapAccessoriesRespType { + accessories: Array<{ + instance: { + ipAddress: string; + port: number; + username: string; + name: string; + }; + aid: number; + services: Array<{ + iid: number; + type: string; + primary: boolean; + hidden: boolean; + linked?: Array; + characteristics: Array<{ + iid: number; + type: string; + description: string; + value: number | string | boolean; + format: 'bool' | 'int' | 'float' | 'string' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'data' | 'tlv8' | 'array' | 'dictionary'; + perms: Array<'pr' | 'pw' | 'ev' | 'aa' | 'tw' | 'hd'>; + unit?: 'unit' | 'percentage' | 'celsius' | 'arcdegrees' | 'lux' | 'seconds'; + maxValue?: number; + minValue?: number; + minStep?: number; + }>; + }>; + }>; +} + +export interface IServiceType { + aid: number; + iid: number; + uuid: string; + type: string; + linked?: Array; + linkedServices?: { + [iid: number]: IServiceType; + }; + hidden?: boolean; + humanType: string; + serviceName: string; + serviceCharacteristics: ICharacteristicType[]; + accessoryInformation: any; + refreshCharacteristics?: () => Promise; + setCharacteristic?: (iid: number, value: number | string | boolean) => Promise; + getCharacteristic?: (type: string) => ICharacteristicType; + values: any; + instance: { + ipAddress: string; + port: number; + username: string; + name: string; + }; + uniqueId?: string; +} + +export interface ICharacteristicType { + aid: number; + iid: number; + uuid: string; + type: string; + serviceType: string; + serviceName: string; + description: string; + value: number | string | boolean; + format: 'bool' | 'int' | 'float' | 'string' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'data' | 'tlv8' | 'array' | 'dictionary'; + perms: Array<'pr' | 'pw' | 'ev' | 'aa' | 'tw' | 'hd'>; + unit?: 'unit' | 'percentage' | 'celsius' | 'arcdegrees' | 'lux' | 'seconds'; + maxValue?: number; + minValue?: number; + minStep?: number; + canRead: boolean; + canWrite: boolean; + ev: boolean; + setValue?: (value: number | string | boolean) => Promise; + getValue?: () => Promise; +} + +export const createDefaultCharacteristicType = (): ICharacteristicType => { + return { + aid: -1, + iid: -1, + uuid: "", + type: "", + serviceType: "", + serviceName: "", + description: "", + value: "", + format: 'bool', + perms: [], + canRead: false, + canWrite: false, + ev: false, + }; +} diff --git a/src/3rdParty/HapClient/monitor.ts b/src/3rdParty/HapClient/monitor.ts new file mode 100755 index 0000000..b8de152 --- /dev/null +++ b/src/3rdParty/HapClient/monitor.ts @@ -0,0 +1,117 @@ +import { EventEmitter } from 'events'; +import { IServiceType, IHapEvInstance } from './interfaces'; +import { createConnection, parseMessage } from './eventedHttpClient'; +import { CharacteristicType } from '.'; + +export class HapMonitor extends EventEmitter { + private pin: string; + private evInstances: IHapEvInstance[]; + private services: IServiceType[]; + private logger: any; + private debug: any; + + constructor(logger: any, debug: any, pin: string, services: IServiceType[]) { + super(); + this.logger = logger; + this.debug = debug; + this.pin = pin; + this.services = services; + this.evInstances = []; + + // get a list of characteristics we can watch for each instance + this.parseServices(); + + // start watching + this.start(); + } + + start() { + for (const instance of this.evInstances) { + instance.socket = createConnection(instance, this.pin, { characteristics: instance.evCharacteristics }); + + this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] Connected`); + + instance.socket.on('data', (data) => { + const message = parseMessage(data); + + + if (message.statusCode === 401) { + if (this.logger) { + this.logger.warn(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` + + `${message.statusCode} ${message.statusMessage} - make sure Homebridge pin for this instance is set to ${this.pin}.`); + } + } + + if (message.protocol === 'EVENT') { + try { + const body = JSON.parse(message.body); + if (body.characteristics && body.characteristics.length) { + this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` + + `Got Event: ${JSON.stringify(body.characteristics)}`); + + const response = body.characteristics.map((c: CharacteristicType): IServiceType | undefined => { + // find the matching service for each characteristics + const service = this.services.find(x => x.aid === c.aid && x.instance.username === instance.username); + + if (service) { + // find the correct characteristic and update it + const characteristic = service.serviceCharacteristics.find(x => x.iid === c.iid); + if (characteristic) { + characteristic.value = c.value; + service.values[characteristic.type] = c.value; + return service; + } + } + + return undefined; + + }); + + // push update to listeners + this.emit('service-update', response.filter((x: any) => x)); + } + } catch (e) { + // do nothing + } + } + }); + } + } + + finish() { + for (const instance of this.evInstances) { + if (instance.socket) { + try { + instance.socket.destroy(); + this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] Disconnected`); + } catch (e) { + // do nothing + } + } + } + } + + parseServices() { + // get a list of characteristics we can watch for each instance + for (const service of this.services) { + const evCharacteristics = service.serviceCharacteristics.filter(x => x.perms.includes('ev')); + + if (evCharacteristics.length) { + // register the instance if it's not already there + if (!this.evInstances.find(x => x.username === service.instance.username)) { + const newInstance = Object.assign({}, service.instance) as IHapEvInstance; + newInstance.evCharacteristics = []; + this.evInstances.push(newInstance); + } + + const instance = this.evInstances.find(x => x.username === service.instance.username); + + for (const evCharacteristic of evCharacteristics) { + if (!instance!.evCharacteristics!.find(x => x.aid === service.aid && x.iid === evCharacteristic.iid)) { + instance!.evCharacteristics!.push({ aid: service.aid, iid: evCharacteristic.iid, ev: true }); + } + } + } + } + } +} diff --git a/src/Types/types.ts b/src/Types/types.ts new file mode 100644 index 0000000..f226c24 --- /dev/null +++ b/src/Types/types.ts @@ -0,0 +1 @@ +export type Dictionary = { [key: string]: T }; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0626c53..4db7dac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,6 @@ +import { HapClient } from "./3rdParty/HapClient"; +import { HapMonitor } from "./3rdParty/HapClient/monitor"; + let Accessory: any; let Homebridge: any; @@ -20,6 +23,7 @@ class AutomationPlatform { log: any = {}; config: any = {}; api: any; + client: HapClient; constructor(log: any, config: any, api: any) { this.log = log; @@ -28,8 +32,16 @@ class AutomationPlatform { this.log('INFO - Registering automation platform'); this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); - // let asdf: any = api.getAllServices(); - // let asdff = "asdf"; + this.client = new HapClient({ + pin: "031-45-154", + logger: log, + config: config + }); + + this.client.on('instance-discovered', async (instance: any) => { + let asdf = instance; + + }) }