From 3ccf85cb006b6eae111db9287c2dcb9988062790 Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Fri, 27 Nov 2020 23:03:12 -0500 Subject: [PATCH 1/6] Wip --- .env.defaults | 2 + config.schema.json | 45 ++ homebridge-face-location.code-workspace | 4 +- package-lock.json | 543 ++++++++++++++++++++++++ package.json | 2 + src/config.ts | 27 ++ src/index.ts | 12 +- src/monitor.ts | 39 ++ src/monitorAccessory.ts | 190 +++++++++ src/platform.ts | 307 ++++++++++++++ src/settings.ts | 9 + 11 files changed, 1178 insertions(+), 2 deletions(-) create mode 100644 .env.defaults create mode 100644 config.schema.json create mode 100644 src/config.ts create mode 100644 src/monitor.ts create mode 100644 src/monitorAccessory.ts create mode 100644 src/platform.ts create mode 100644 src/settings.ts diff --git a/.env.defaults b/.env.defaults new file mode 100644 index 0000000..1fcd14b --- /dev/null +++ b/.env.defaults @@ -0,0 +1,2 @@ +TRAINED_MODEL_DIR=./trainedModels +OUT_DIR=./out \ No newline at end of file diff --git a/config.schema.json b/config.schema.json new file mode 100644 index 0000000..12fbe59 --- /dev/null +++ b/config.schema.json @@ -0,0 +1,45 @@ +{ + "pluginAlias": "HomeLocation", + "pluginType": "platform", + "singular": true, + "schema": { + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string", + "required": true, + "default": "Example Dynamic Platform" + }, + "refImageDirectory": { + "title": "RefImageDirectory", + "type": "string", + "required": true + }, + "trainedModelDirectory": { + "title": "TrainedModelDirectory", + "type": "string", + "required": true + }, + "rooms": { + "title": "Rooms", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string", + "required": true + }, + "rtspCameraConnectionString": { + "title": "RtspCameraConnectionString", + "type": "string", + "required": true + } + } + } + } + } + } +} diff --git a/homebridge-face-location.code-workspace b/homebridge-face-location.code-workspace index a9da0aa..29c0eb7 100644 --- a/homebridge-face-location.code-workspace +++ b/homebridge-face-location.code-workspace @@ -4,5 +4,7 @@ "path": "." } ], - "settings": {} + "settings": { + "editor.tabSize": 2 + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b87c5ed..6ab6a37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,6 +209,16 @@ "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==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -222,11 +232,31 @@ "typpy": "2.3.11" } }, + "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" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "bonjour-hap": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.5.11.tgz", + "integrity": "sha512-pSYWWxjx3t2vh3sMkBMxQ0QaCqFgqWFz9he9U1JSQ6Lvwev5W19J+nBQWFj55dzUOcH0rPtZh/A+k/MvI5etQw==", + "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", @@ -241,6 +271,15 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -325,6 +364,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "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", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -441,6 +485,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, "decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -449,11 +498,47 @@ "mimic-response": "^2.0.0" } }, + "deep-equal": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.4.tgz", + "integrity": "sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w==", + "requires": { + "es-abstract": "^1.18.0-next.1", + "es-get-iterator": "^1.1.0", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "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", @@ -474,6 +559,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, + "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" + } + }, "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -495,6 +589,57 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-get-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.1.tgz", + "integrity": "sha512-qorBw8Y7B15DVLaJWy6WdEV/ZkieBcu6QCq/xzWzGOKJqgG1j754vXRfZ3NY7HSShneqU43mPB4OkQBTkvHhFw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.1", + "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" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -513,6 +658,11 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, + "fast-srp-hap": { + "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": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -522,6 +672,11 @@ "path-exists": "^4.0.0" } }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, "form-data": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", @@ -545,6 +700,11 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "function.name": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.13.tgz", @@ -553,6 +713,11 @@ "noop6": "^1.0.1" } }, + "futoin-hkdf": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz", + "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -606,6 +771,16 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -624,16 +799,74 @@ "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.13.0.tgz", "integrity": "sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==" }, + "hap-nodejs": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.7.9.tgz", + "integrity": "sha512-ic0H0iajNKKq8O2a5HJgZPaxwhgt0PjXZ/rZFGdGhtM+ivfi9LykkC1FZufjHljUhMTvWe1GYXQaF06UVWhyHA==", + "requires": { + "bonjour-hap": "~3.5.11", + "debug": "^4.1.1", + "decimal.js": "^10.2.0", + "fast-srp-hap": "2.0.2", + "futoin-hkdf": "~1.3.2", + "node-persist": "^0.0.11", + "tweetnacl": "^1.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" + } + } + } + }, + "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" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "homebridge": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.1.6.tgz", + "integrity": "sha512-fsHEeUGp0t3Ry5Awc2U4LZraEHWmgnokUAa3JOfcTZOY9uxjv4buVMkBCa4CdMUCpMPL25Vit+I8doNqfn4Owg==", + "requires": { + "chalk": "^4.1.0", + "commander": "5.1.0", + "hap-nodejs": "^0.7.9", + "node-persist": "^0.0.11", + "qrcode-terminal": "^0.12.0", + "semver": "^7.3.2", + "source-map-support": "^0.5.19" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, "https-proxy-agent": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", @@ -678,11 +911,123 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "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-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.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "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-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "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", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "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" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "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": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -767,6 +1112,20 @@ "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.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", @@ -787,6 +1146,15 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "node-persist": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.11.tgz", + "integrity": "sha1-1m66Pr72IPB5Uw+nsTB2qQZmWHQ=", + "requires": { + "mkdirp": "~0.5.1", + "q": "~1.1.1" + } + }, "node-pre-gyp": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", @@ -894,6 +1262,36 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -967,6 +1365,16 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, + "q": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", + "integrity": "sha1-Y1fikSBnAdmfGXq4TlforRlvKok=" + }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -997,6 +1405,35 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, + "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" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1076,6 +1513,15 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "side-channel": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", + "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "requires": { + "es-abstract": "^1.18.0-next.0", + "object-inspect": "^1.8.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -1125,6 +1571,24 @@ "strip-ansi": "^6.0.0" } }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1177,6 +1641,11 @@ "xtend": "~4.0.1" } }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", @@ -1189,6 +1658,24 @@ "yn": "3.1.1" } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsyringe": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.4.0.tgz", + "integrity": "sha512-SlMApe1lhIq546CDp7bF+IdF4RB6d+9C5T7B0AS0P/Bm+Qpizj/gEmZzvw9J/KlXPEt4qHTbi1TRvX3rCPSdTg==", + "requires": { + "tslib": "^1.9.3" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, "typescript": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", @@ -1220,11 +1707,67 @@ "isexe": "^2.0.0" } }, + "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-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "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" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/package.json b/package.json index acccbf4..daf8e12 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "canvas": "^2.6.1", "copyfiles": "^2.4.0", "dotenv-extended": "^2.9.0", + "homebridge": "^1.1.6", "mime-types": "^2.1.27", "rtsp-stream": "file:../rtsp-stream", "ts-node": "^9.0.0", + "tsyringe": "^4.4.0", "typescript": "^4.0.5" }, "devDependencies": { diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..5eb15c5 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,27 @@ +import { PlatformConfig } from "homebridge"; + +export interface IConfig extends PlatformConfig { + refImageDirectory: string; + trainedModelDirectory: string; + weightDirectory: string; + trainOnStartup: boolean; + rooms: Array; +} + +export interface IRoom { + name: string; + rtspConnectionStrings: Array; +} + +export const isRoom = (object: any): object is IRoom => { + return "name" in object && "rtspCameraConnectionString" in object; +}; + +export const isConfig = (object: any): object is IConfig => { + return ( + "refImageDirectory" in object && + "trainedModelDirectory" in object && + "rooms" in object && + isRoom(object["rooms"]) + ); +}; diff --git a/src/index.ts b/src/index.ts index accefce..edf4b7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,11 @@ -console.log("Hello World"); +import { API } from "homebridge"; + +import { PLATFORM_NAME } from "./settings"; +import { HomeLocationPlatform } from "./platform"; + +/** + * This method registers the platform with Homebridge + */ +export = (api: API) => { + api.registerPlatform(PLATFORM_NAME, HomeLocationPlatform); +}; diff --git a/src/monitor.ts b/src/monitor.ts new file mode 100644 index 0000000..e63f20c --- /dev/null +++ b/src/monitor.ts @@ -0,0 +1,39 @@ +import { FaceMatcher } from "@vladmandic/face-api"; +import { IRoom } from "./config"; +import { Rtsp } from "rtsp-stream/lib"; + +export interface ILabelState { + label: string; + detected: boolean; +} + +export type IRoomState = { [roomName: string]: Array }; + +export class LocationMonitor { + private _state: IRoomState = {}; + + private _streams: Array = []; + + constructor(private rooms: Array, private matcher: FaceMatcher) { + //Initialize state + for (const room of rooms) { + this._streams.push( + ...room.rtspConnectionStrings.map( + (e) => new Rtsp(e, { rate: 0.5, image: true }) + ) + ); + + this._state[room.name] = matcher.labeledDescriptors.map((e) => { + return { label: e.label, detected: false }; + }); + } + } + + public get state(): IRoomState { + return this._state; + } + + private async startStreams() {} + + private async stopStreams() {} +} diff --git a/src/monitorAccessory.ts b/src/monitorAccessory.ts new file mode 100644 index 0000000..4fa5ad9 --- /dev/null +++ b/src/monitorAccessory.ts @@ -0,0 +1,190 @@ +import { + Service, + PlatformAccessory, + CharacteristicValue, + CharacteristicSetCallback, + CharacteristicGetCallback, +} from "homebridge"; + +import { HomeLocationPlatform } from "./platform"; + +/** + * Platform Accessory + * An instance of this class is created for each accessory your platform registers + * Each accessory may expose multiple services of different service types. + */ +export class MonitorAccessory { + private service: Service; + + /** + * These are just used to create a working example + * You should implement your own code to track the state of your accessory + */ + private exampleStates = { + On: false, + Brightness: 100, + }; + + constructor( + private readonly platform: HomeLocationPlatform, + private readonly accessory: PlatformAccessory + ) { + // set accessory information + this.accessory + .getService(this.platform.Service.AccessoryInformation)! + .setCharacteristic( + this.platform.Characteristic.Manufacturer, + "Default-Manufacturer" + ) + .setCharacteristic(this.platform.Characteristic.Model, "Default-Model") + .setCharacteristic( + this.platform.Characteristic.SerialNumber, + "Default-Serial" + ); + + // get the LightBulb service if it exists, otherwise create a new LightBulb service + // you can create multiple services for each accessory + this.service = + this.accessory.getService(this.platform.Service.Lightbulb) || + this.accessory.addService(this.platform.Service.Lightbulb); + + // set the service name, this is what is displayed as the default name on the Home app + // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. + this.service.setCharacteristic( + this.platform.Characteristic.Name, + accessory.context.device.exampleDisplayName + ); + + // each service must implement at-minimum the "required characteristics" for the given service type + // see https://developers.homebridge.io/#/service/Lightbulb + + // register handlers for the On/Off Characteristic + this.service + .getCharacteristic(this.platform.Characteristic.On) + .on("set", this.setOn.bind(this)) // SET - bind to the `setOn` method below + .on("get", this.getOn.bind(this)); // GET - bind to the `getOn` method below + + // register handlers for the Brightness Characteristic + this.service + .getCharacteristic(this.platform.Characteristic.Brightness) + .on("set", this.setBrightness.bind(this)); // SET - bind to the 'setBrightness` method below + + /** + * Creating multiple services of the same type. + * + * To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, + * when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: + * this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID'); + * + * The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory + * can use the same sub type id.) + */ + + // Example: add two "motion sensor" services to the accessory + const motionSensorOneService = + this.accessory.getService("Motion Sensor One Name") || + this.accessory.addService( + this.platform.Service.MotionSensor, + "Motion Sensor One Name", + "YourUniqueIdentifier-1" + ); + + const motionSensorTwoService = + this.accessory.getService("Motion Sensor Two Name") || + this.accessory.addService( + this.platform.Service.MotionSensor, + "Motion Sensor Two Name", + "YourUniqueIdentifier-2" + ); + + /** + * Updating characteristics values asynchronously. + * + * Example showing how to update the state of a Characteristic asynchronously instead + * of using the `on('get')` handlers. + * Here we change update the motion sensor trigger states on and off every 10 seconds + * the `updateCharacteristic` method. + * + */ + let motionDetected = false; + setInterval(() => { + // EXAMPLE - inverse the trigger + motionDetected = !motionDetected; + + // push the new value to HomeKit + motionSensorOneService.updateCharacteristic( + this.platform.Characteristic.MotionDetected, + motionDetected + ); + motionSensorTwoService.updateCharacteristic( + this.platform.Characteristic.MotionDetected, + !motionDetected + ); + + this.platform.log.debug( + "Triggering motionSensorOneService:", + motionDetected + ); + this.platform.log.debug( + "Triggering motionSensorTwoService:", + !motionDetected + ); + }, 10000); + } + + /** + * Handle "SET" requests from HomeKit + * These are sent when the user changes the state of an accessory, for example, turning on a Light bulb. + */ + setOn(value: CharacteristicValue, callback: CharacteristicSetCallback) { + // implement your own code to turn your device on/off + this.exampleStates.On = value as boolean; + + this.platform.log.debug("Set Characteristic On ->", value); + + // you must call the callback function + callback(null); + } + + /** + * Handle the "GET" requests from HomeKit + * These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on. + * + * GET requests should return as fast as possbile. A long delay here will result in + * HomeKit being unresponsive and a bad user experience in general. + * + * If your device takes time to respond you should update the status of your device + * asynchronously instead using the `updateCharacteristic` method instead. + + * @example + * this.service.updateCharacteristic(this.platform.Characteristic.On, true) + */ + getOn(callback: CharacteristicGetCallback) { + // implement your own code to check if the device is on + const isOn = this.exampleStates.On; + + this.platform.log.debug("Get Characteristic On ->", isOn); + + // you must call the callback function + // the first argument should be null if there were no errors + // the second argument should be the value to return + callback(null, isOn); + } + + /** + * Handle "SET" requests from HomeKit + * These are sent when the user changes the state of an accessory, for example, changing the Brightness + */ + setBrightness( + value: CharacteristicValue, + callback: CharacteristicSetCallback + ) { + // implement your own code to set the brightness + this.exampleStates.Brightness = value as number; + + this.platform.log.debug("Set Characteristic Brightness -> ", value); + + // you must call the callback function + callback(null); + } +} diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 0000000..dd2ecaa --- /dev/null +++ b/src/platform.ts @@ -0,0 +1,307 @@ +import { + API, + DynamicPlatformPlugin, + Logger, + PlatformAccessory, + PlatformConfig, + Service, + Characteristic, +} from "homebridge"; +import { IConfig, isConfig } from "./config"; +import * as faceapi from "@vladmandic/face-api"; +import canvas from "canvas"; +import fs, { lstatSync } from "fs"; +import * as path from "path"; +import { nets } from "@vladmandic/face-api"; +import { + LabeledFaceDescriptors, + TNetInput, + FaceMatcher, +} from "@vladmandic/face-api"; +import * as mime from "mime-types"; +import { getFaceDetectorOptions } from "../src/common"; +require("@tensorflow/tfjs-node"); + +const { Canvas, Image, ImageData } = canvas; +//@ts-ignore +faceapi.env.monkeyPatch({ Canvas, Image, ImageData }); + +import { PLATFORM_NAME, PLUGIN_NAME } from "./settings"; +import { MonitorAccessory } from "./monitorAccessory"; + +/** + * HomebridgePlatform + * This class is the main constructor for your plugin, this is where you should + * parse the user config and discover/register accessories with Homebridge. + */ +export class HomeLocationPlatform implements DynamicPlatformPlugin { + 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; + + constructor( + public readonly log: Logger, + config: PlatformConfig, + public readonly api: API + ) { + this.log.debug("Finished initializing platform:", config.name); + + if (!isConfig(config)) { + this.log.error("Configuration is incorrect or incomplete"); + process.exit(1); + } else { + this.config = config; + } + + this.api.on("didFinishLaunching", async () => { + log.debug("Executed didFinishLaunching callback"); + // run the method to discover / register your devices as accessories + await this.discoverDevices(); + }); + } + + /** + * This function is invoked when homebridge restores cached accessories from disk at startup. + * It should be used to setup event handlers for characteristics and update respective values. + */ + public configureAccessory(accessory: PlatformAccessory) { + 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); + } + + /** + * This is an example method showing how to register discovered accessories. + * Accessories must only be registered once, previously created accessories + * must not be registered again to prevent "duplicate UUID" errors. + */ + public async discoverDevices() { + //Train facial recognition model + let faceMatcher: FaceMatcher; + if (this.config.trainOnStartup) { + faceMatcher = await this.trainModels(); + } else { + const faceDetectionNet = nets.ssdMobilenetv1; + + await faceDetectionNet.loadFromDisk(this.config.weightDirectory); + await nets.faceLandmark68Net.loadFromDisk(this.config.weightDirectory); + await nets.faceRecognitionNet.loadFromDisk(this.config.weightDirectory); + + const raw = fs.readFileSync( + path.join(this.config.trainedModelDirectory, "data.json"), + "utf-8" + ); + faceMatcher = FaceMatcher.fromJSON(JSON.parse(raw)); + } + + const exampleDevices = [ + { + exampleUniqueId: "ABCD", + exampleDisplayName: "Bedroom", + }, + { + exampleUniqueId: "EFGH", + exampleDisplayName: "Kitchen", + }, + ]; + + const labels = faceMatcher.labeledDescriptors.map((e) => e.label); + for (const room of this.config.rooms) { + for (const label of labels) { + const uuid = this.api.hap.uuid.generate(room.name + label); + + const existingAccessory = this.accessories.find((e) => e.UUID === uuid); + if (existingAccessory) { + this.log.info( + "Restoring existing accessory from cache: ", + existingAccessory.displayName + ); + + // this is imported from `platformAccessory.ts` + new MonitorAccessory(this, existingAccessory); + + // update accessory cache with any changes to the accessory details and information + this.api.updatePlatformAccessories([existingAccessory]); + } else { + // the accessory does not yet exist, so we need to create it + this.log.info("Adding new accessory:", `${room}+${label}`); + + // create a new accessory + const accessory = new this.api.platformAccessory( + `${room}+${label}`, + uuid + ); + + // store a copy of the device object in the `accessory.context` + // the `context` property can be used to store any data about the accessory you may need + // accessory.context.device = device; + + // create the accessory handler for the newly create accessory + // this is imported from `platformAccessory.ts` + new MonitorAccessory(this, accessory); + + // link the accessory to your platform + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ + accessory, + ]); + } + } + } + + // loop over the discovered devices and register each one if it has not already been registered + for (const device of exampleDevices) { + // generate a unique id for the accessory this should be generated from + // something globally unique, but constant, for example, the device serial + // number or MAC address + const uuid = this.api.hap.uuid.generate(device.exampleUniqueId); + + // see if an accessory with the same uuid has already been registered and restored from + // the cached devices we stored in the `configureAccessory` method above + const existingAccessory = this.accessories.find( + (accessory) => accessory.UUID === uuid + ); + + if (existingAccessory) { + // the accessory already exists + if (device) { + this.log.info( + "Restoring existing accessory from cache:", + existingAccessory.displayName + ); + + // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: + // existingAccessory.context.device = device; + // this.api.updatePlatformAccessories([existingAccessory]); + + // create the accessory handler for the restored accessory + // this is imported from `platformAccessory.ts` + new MonitorAccessory(this, existingAccessory); + + // update accessory cache with any changes to the accessory details and information + this.api.updatePlatformAccessories([existingAccessory]); + } else if (!device) { + // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.: + // remove platform accessories when no longer present + this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ + existingAccessory, + ]); + this.log.info( + "Removing existing accessory from cache:", + existingAccessory.displayName + ); + } + } else { + // the accessory does not yet exist, so we need to create it + this.log.info("Adding new accessory:", device.exampleDisplayName); + + // create a new accessory + const accessory = new this.api.platformAccessory( + device.exampleDisplayName, + uuid + ); + + // store a copy of the device object in the `accessory.context` + // the `context` property can be used to store any data about the accessory you may need + accessory.context.device = device; + + // create the accessory handler for the newly create accessory + // this is imported from `platformAccessory.ts` + new MonitorAccessory(this, accessory); + + // link the accessory to your platform + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ + accessory, + ]); + } + } + } + + private async trainModels(): Promise { + const faceDetectionNet = faceapi.nets.ssdMobilenetv1; + await faceDetectionNet.loadFromDisk(this.config.weightDirectory); + await faceapi.nets.faceLandmark68Net.loadFromDisk( + this.config.weightDirectory + ); + await faceapi.nets.faceRecognitionNet.loadFromDisk( + this.config.weightDirectory + ); + + const options = getFaceDetectorOptions(faceDetectionNet); + + const dirs = fs.readdirSync(this.config.refImageDirectory); + + const refs: Array = []; + for (const dir of dirs) { + if ( + !lstatSync(path.join(this.config.refImageDirectory, dir)).isDirectory() + ) { + continue; + } + const files = fs.readdirSync( + path.join(this.config.refImageDirectory, dir) + ); + let referenceResults = await Promise.all( + files.map(async (file: string) => { + const mimeType = mime.contentType( + path.extname(path.join(this.config.refImageDirectory, dir, file)) + ); + if (!mimeType || !mimeType.startsWith("image")) { + return; + } + console.log(path.join(this.config.refImageDirectory, dir, file)); + + try { + const referenceImage = (await canvas.loadImage( + path.join(this.config.refImageDirectory, dir, file) + )) as unknown; + + const descriptor = await faceapi + .detectSingleFace(referenceImage as TNetInput, options) + .withFaceLandmarks() + .withFaceDescriptor(); + if (!descriptor || !descriptor.descriptor) { + throw new Error("No face found"); + } + + const faceDescriptors = [descriptor.descriptor]; + return new faceapi.LabeledFaceDescriptors(dir, faceDescriptors); + } catch (err) { + console.log( + "An error occurred loading image at path: " + + path.join(this.config.refImageDirectory, dir, file) + ); + } + return undefined; + }) + ); + + if (referenceResults) { + refs.push( + ...(referenceResults.filter((e) => e) as LabeledFaceDescriptors[]) + ); + } + } + + const faceMatcher = new faceapi.FaceMatcher(refs); + + fs.writeFile( + path.join(this.config.trainedModelDirectory, "data.json"), + JSON.stringify(faceMatcher.toJSON()), + "utf8", + (err) => { + if (err) { + console.log(`An error occurred while writing data model to file`); + } + + console.log(`Successfully wrote data model to file`); + } + ); + + return faceMatcher; + } +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..8f37150 --- /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 = "HomeLocation"; + +/** + * This must match the name of your plugin as defined the package.json + */ +export const PLUGIN_NAME = "homebridge-home-location"; From e047ef6549324d681bc8f688d6c785a7af6af766 Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Mon, 30 Nov 2020 20:55:31 -0500 Subject: [PATCH 2/6] Working debug configuration --- .vscode/launch.json | 23 +++--- .vscode/tasks.json | 11 ++- homebridge-face-location.code-workspace | 3 +- package.json | 20 +++--- src/config.ts | 8 ++- src/{monitor.ts => locationMonitor.ts} | 0 src/monitorAccessory.ts | 13 ++-- src/platform.ts | 96 +++---------------------- src/settings.ts | 2 +- 9 files changed, 56 insertions(+), 120 deletions(-) rename src/{monitor.ts => locationMonitor.ts} (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index a42b64a..4f9c5ce 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,17 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/bin/index.js", - "preLaunchTask": "build", - "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/**/*.js"] - }, { "name": "Current TS File", "type": "node", @@ -24,6 +13,18 @@ "sourceMaps": true, "cwd": "${workspaceRoot}", "protocol": "inspector" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "preLaunchTask": "build", + "internalConsoleOptions": "openOnSessionStart", + "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 08f2a53..ab4213d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,10 +4,15 @@ "version": "2.0.0", "tasks": [ { + "type": "npm", + "script": "build", "label": "build", - "type": "typescript", - "tsconfig": "tsconfig.json", - "problemMatcher": ["$tsc"] + "problemMatcher": [] + }, + { + "type": "shell", + "label": "build and install", + "command": "npm run build&&sudo npm install -g --unsafe-perm ." } ] } diff --git a/homebridge-face-location.code-workspace b/homebridge-face-location.code-workspace index 29c0eb7..ae7f19c 100644 --- a/homebridge-face-location.code-workspace +++ b/homebridge-face-location.code-workspace @@ -5,6 +5,7 @@ } ], "settings": { - "editor.tabSize": 2 + "editor.tabSize": 2, + "debug.javascript.unmapMissingSources": true } } \ No newline at end of file diff --git a/package.json b/package.json index daf8e12..64748c5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "homebridge-face-location", "version": "1.0.0", "description": "", - "main": "index.ts", + "main": "bin/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "npm run copy-files && tsc --build", @@ -15,8 +15,12 @@ }, "keywords": [ "homebridge", - "typescript" + "typescript", + "homebridge-plugin" ], + "engines": { + "homebridge": ">=1.1.6" + }, "author": "Brandon Watson", "license": "ISC", "dependencies": { @@ -25,16 +29,16 @@ "@types/mime-types": "^2.1.0", "@vladmandic/face-api": "^0.8.8", "canvas": "^2.6.1", - "copyfiles": "^2.4.0", "dotenv-extended": "^2.9.0", - "homebridge": "^1.1.6", "mime-types": "^2.1.27", "rtsp-stream": "file:../rtsp-stream", - "ts-node": "^9.0.0", - "tsyringe": "^4.4.0", - "typescript": "^4.0.5" + "tsyringe": "^4.4.0" }, "devDependencies": { - "@types/webgl2": "0.0.5" + "@types/webgl2": "0.0.5", + "typescript": "^4.0.5", + "ts-node": "^9.0.0", + "homebridge": "^1.1.6", + "copyfiles": "^2.4.0" } } diff --git a/src/config.ts b/src/config.ts index 5eb15c5..43559a1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,14 +14,18 @@ export interface IRoom { } export const isRoom = (object: any): object is IRoom => { - return "name" in object && "rtspCameraConnectionString" in object; + return "name" in object && "rtspConnectionStrings" in object; }; export const isConfig = (object: any): object is IConfig => { + const roomsOkay = + object["rooms"].filter((room: any) => isRoom(room)).length === + object["rooms"].length; return ( "refImageDirectory" in object && "trainedModelDirectory" in object && + "weightDirectory" in object && "rooms" in object && - isRoom(object["rooms"]) + roomsOkay ); }; diff --git a/src/monitor.ts b/src/locationMonitor.ts similarity index 100% rename from src/monitor.ts rename to src/locationMonitor.ts diff --git a/src/monitorAccessory.ts b/src/monitorAccessory.ts index 4fa5ad9..8a7ea6f 100644 --- a/src/monitorAccessory.ts +++ b/src/monitorAccessory.ts @@ -5,7 +5,7 @@ import { CharacteristicSetCallback, CharacteristicGetCallback, } from "homebridge"; - +import { LocationMonitor } from "./locationMonitor"; import { HomeLocationPlatform } from "./platform"; /** @@ -27,7 +27,8 @@ export class MonitorAccessory { constructor( private readonly platform: HomeLocationPlatform, - private readonly accessory: PlatformAccessory + private readonly accessory: PlatformAccessory, + private monitor: LocationMonitor ) { // set accessory information this.accessory @@ -42,17 +43,17 @@ export class MonitorAccessory { "Default-Serial" ); - // get the LightBulb service if it exists, otherwise create a new LightBulb service + // get the MotionSensor service if it exists, otherwise create a new MotionSensor service // you can create multiple services for each accessory this.service = - this.accessory.getService(this.platform.Service.Lightbulb) || - this.accessory.addService(this.platform.Service.Lightbulb); + this.accessory.getService(this.platform.Service.MotionSensor) || + this.accessory.addService(this.platform.Service.MotionSensor); // set the service name, this is what is displayed as the default name on the Home app // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. this.service.setCharacteristic( this.platform.Characteristic.Name, - accessory.context.device.exampleDisplayName + accessory.context["DeviceName"] ); // each service must implement at-minimum the "required characteristics" for the given service type diff --git a/src/platform.ts b/src/platform.ts index dd2ecaa..2291199 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -19,7 +19,8 @@ import { FaceMatcher, } from "@vladmandic/face-api"; import * as mime from "mime-types"; -import { getFaceDetectorOptions } from "../src/common"; +import { LocationMonitor } from "./locationMonitor"; +import { getFaceDetectorOptions } from "./common"; require("@tensorflow/tfjs-node"); const { Canvas, Image, ImageData } = canvas; @@ -99,16 +100,7 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { faceMatcher = FaceMatcher.fromJSON(JSON.parse(raw)); } - const exampleDevices = [ - { - exampleUniqueId: "ABCD", - exampleDisplayName: "Bedroom", - }, - { - exampleUniqueId: "EFGH", - exampleDisplayName: "Kitchen", - }, - ]; + const locationMonitor = new LocationMonitor(this.config.rooms, faceMatcher); const labels = faceMatcher.labeledDescriptors.map((e) => e.label); for (const room of this.config.rooms) { @@ -122,28 +114,23 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { existingAccessory.displayName ); - // this is imported from `platformAccessory.ts` - new MonitorAccessory(this, existingAccessory); + new MonitorAccessory(this, existingAccessory, locationMonitor); - // update accessory cache with any changes to the accessory details and information this.api.updatePlatformAccessories([existingAccessory]); } else { - // the accessory does not yet exist, so we need to create it - this.log.info("Adding new accessory:", `${room}+${label}`); + this.log.info("Adding new accessory:", `${room.name}+${label}`); // create a new accessory const accessory = new this.api.platformAccessory( - `${room}+${label}`, + `${room.name} ${label}`, uuid ); - // store a copy of the device object in the `accessory.context` - // the `context` property can be used to store any data about the accessory you may need - // accessory.context.device = device; + accessory.context["DeviceName"] = `${room.name} ${label}`; // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new MonitorAccessory(this, accessory); + new MonitorAccessory(this, accessory, locationMonitor); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ @@ -152,73 +139,6 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { } } } - - // loop over the discovered devices and register each one if it has not already been registered - for (const device of exampleDevices) { - // generate a unique id for the accessory this should be generated from - // something globally unique, but constant, for example, the device serial - // number or MAC address - const uuid = this.api.hap.uuid.generate(device.exampleUniqueId); - - // see if an accessory with the same uuid has already been registered and restored from - // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find( - (accessory) => accessory.UUID === uuid - ); - - if (existingAccessory) { - // the accessory already exists - if (device) { - this.log.info( - "Restoring existing accessory from cache:", - existingAccessory.displayName - ); - - // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - // existingAccessory.context.device = device; - // this.api.updatePlatformAccessories([existingAccessory]); - - // create the accessory handler for the restored accessory - // this is imported from `platformAccessory.ts` - new MonitorAccessory(this, existingAccessory); - - // update accessory cache with any changes to the accessory details and information - this.api.updatePlatformAccessories([existingAccessory]); - } else if (!device) { - // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.: - // remove platform accessories when no longer present - this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ - existingAccessory, - ]); - this.log.info( - "Removing existing accessory from cache:", - existingAccessory.displayName - ); - } - } else { - // the accessory does not yet exist, so we need to create it - this.log.info("Adding new accessory:", device.exampleDisplayName); - - // create a new accessory - const accessory = new this.api.platformAccessory( - device.exampleDisplayName, - uuid - ); - - // store a copy of the device object in the `accessory.context` - // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - - // create the accessory handler for the newly create accessory - // this is imported from `platformAccessory.ts` - new MonitorAccessory(this, accessory); - - // link the accessory to your platform - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ - accessory, - ]); - } - } } private async trainModels(): Promise { diff --git a/src/settings.ts b/src/settings.ts index 8f37150..334161e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -6,4 +6,4 @@ export const PLATFORM_NAME = "HomeLocation"; /** * This must match the name of your plugin as defined the package.json */ -export const PLUGIN_NAME = "homebridge-home-location"; +export const PLUGIN_NAME = "homebridge-face-location"; From 65f11bec099b88c6e3c13bf4f388164bcd1def6a Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Thu, 10 Dec 2020 12:51:35 -0500 Subject: [PATCH 3/6] WIP adding custom event classes --- package-lock.json | 195 +++++++++++++++++++++++++++--------- src/common.ts | 9 +- src/config.ts | 1 + src/events/event.ts | 12 +++ src/events/eventDelegate.ts | 1 + src/locationAccessory.ts | 91 +++++++++++++++++ src/locationMonitor.ts | 39 -------- src/monitor.ts | 151 ++++++++++++++++++++++++++++ src/monitorAccessory.ts | 191 ----------------------------------- src/platform.ts | 59 +++++------ 10 files changed, 435 insertions(+), 314 deletions(-) create mode 100644 src/events/event.ts create mode 100644 src/events/eventDelegate.ts create mode 100644 src/locationAccessory.ts delete mode 100644 src/locationMonitor.ts create mode 100644 src/monitor.ts delete mode 100644 src/monitorAccessory.ts diff --git a/package-lock.json b/package-lock.json index 6ab6a37..80603d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -199,7 +199,8 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true }, "argparse": { "version": "1.0.10", @@ -212,12 +213,14 @@ "array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true }, "asynckit": { "version": "0.4.0", @@ -236,6 +239,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, "requires": { "array-filter": "^1.0.0" } @@ -249,6 +253,7 @@ "version": "3.5.11", "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.5.11.tgz", "integrity": "sha512-pSYWWxjx3t2vh3sMkBMxQ0QaCqFgqWFz9he9U1JSQ6Lvwev5W19J+nBQWFj55dzUOcH0rPtZh/A+k/MvI5etQw==", + "dev": true, "requires": { "array-flatten": "^2.1.2", "deep-equal": "^2.0.2", @@ -269,12 +274,14 @@ "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==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "call-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.0" @@ -367,7 +374,8 @@ "commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true }, "concat-map": { "version": "0.0.1", @@ -383,6 +391,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.0.tgz", "integrity": "sha512-yGjpR3yjQdxccW8EcJ4a7ZCA6wGER6/Q2Y+b7bXbVxGeSHBf93i9d7MzTsx+VV1CpMKQa3v4ThZfXBcltMzl0w==", + "dev": true, "requires": { "glob": "^7.0.5", "minimatch": "^3.0.3", @@ -397,6 +406,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -406,12 +416,14 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -421,12 +433,14 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -445,6 +459,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -483,12 +498,14 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decimal.js": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true }, "decompress-response": { "version": "4.2.1", @@ -502,6 +519,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.4.tgz", "integrity": "sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w==", + "dev": true, "requires": { "es-abstract": "^1.18.0-next.1", "es-get-iterator": "^1.1.0", @@ -522,7 +540,8 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true } } }, @@ -535,6 +554,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -557,12 +577,14 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "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==", + "dev": true, "requires": { "ip": "^1.1.5", "safe-buffer": "^5.1.1" @@ -593,6 +615,7 @@ "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -612,6 +635,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.1.tgz", "integrity": "sha512-qorBw8Y7B15DVLaJWy6WdEV/ZkieBcu6QCq/xzWzGOKJqgG1j754vXRfZ3NY7HSShneqU43mPB4OkQBTkvHhFw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.1", @@ -626,7 +650,8 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true } } }, @@ -634,6 +659,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -661,12 +687,14 @@ "fast-srp-hap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.2.tgz", - "integrity": "sha512-wABhZRrFhlovqJQ1HygOUB4R6WZW2hmlpvVYh2dVCy8BPLabDrB/Tu6XI3B4QfmhtHk8s1OeiFqJHY7FBsphug==" + "integrity": "sha512-wABhZRrFhlovqJQ1HygOUB4R6WZW2hmlpvVYh2dVCy8BPLabDrB/Tu6XI3B4QfmhtHk8s1OeiFqJHY7FBsphug==", + "dev": true }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -675,7 +703,8 @@ "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true }, "form-data": { "version": "3.0.0", @@ -703,7 +732,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "function.name": { "version": "1.0.13", @@ -716,7 +746,8 @@ "futoin-hkdf": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz", - "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==" + "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==", + "dev": true }, "gauge": { "version": "2.7.4", @@ -775,6 +806,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -803,6 +835,7 @@ "version": "0.7.9", "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.7.9.tgz", "integrity": "sha512-ic0H0iajNKKq8O2a5HJgZPaxwhgt0PjXZ/rZFGdGhtM+ivfi9LykkC1FZufjHljUhMTvWe1GYXQaF06UVWhyHA==", + "dev": true, "requires": { "bonjour-hap": "~3.5.11", "debug": "^4.1.1", @@ -817,6 +850,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -827,6 +861,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -839,7 +874,8 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, "has-unicode": { "version": "2.0.1", @@ -850,6 +886,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.1.6.tgz", "integrity": "sha512-fsHEeUGp0t3Ry5Awc2U4LZraEHWmgnokUAa3JOfcTZOY9uxjv4buVMkBCa4CdMUCpMPL25Vit+I8doNqfn4Owg==", + "dev": true, "requires": { "chalk": "^4.1.0", "commander": "5.1.0", @@ -863,7 +900,8 @@ "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true } } }, @@ -914,32 +952,38 @@ "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true }, "is-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==" + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "dev": true }, "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==" + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true }, "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -949,22 +993,26 @@ "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==" + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true }, "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==" + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true }, "is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -972,17 +1020,20 @@ "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==" + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true }, "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==" + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -991,6 +1042,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.0", "es-abstract": "^1.17.4", @@ -1002,6 +1054,7 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -1021,12 +1074,14 @@ "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true }, "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==" + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true }, "isarray": { "version": "1.0.0", @@ -1042,6 +1097,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -1049,7 +1105,8 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "mime-db": { "version": "1.44.0", @@ -1116,6 +1173,7 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.2.tgz", "integrity": "sha512-XqSMeO8EWV/nOXOpPV8ztIpNweVfE1dSpz6SQvDPp71HD74lMXjt4m/mWB1YBMG0kHtOodxRWc5WOb/UNN1A5g==", + "dev": true, "requires": { "dns-packet": "^4.0.0", "thunky": "^1.0.2" @@ -1124,7 +1182,8 @@ "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=" + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true }, "nan": { "version": "2.14.2", @@ -1150,6 +1209,7 @@ "version": "0.0.11", "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.11.tgz", "integrity": "sha1-1m66Pr72IPB5Uw+nsTB2qQZmWHQ=", + "dev": true, "requires": { "mkdirp": "~0.5.1", "q": "~1.1.1" @@ -1176,6 +1236,7 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "~1.0.31" @@ -1184,12 +1245,14 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -1200,7 +1263,8 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true } } }, @@ -1265,12 +1329,14 @@ "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true }, "object-is": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -1279,12 +1345,14 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -1323,6 +1391,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -1331,6 +1400,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -1338,12 +1408,14 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -1368,12 +1440,14 @@ "q": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", - "integrity": "sha1-Y1fikSBnAdmfGXq4TlforRlvKok=" + "integrity": "sha1-Y1fikSBnAdmfGXq4TlforRlvKok=", + "dev": true }, "qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "dev": true }, "rc": { "version": "1.2.8", @@ -1409,6 +1483,7 @@ "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==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" @@ -1418,6 +1493,7 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -1442,7 +1518,8 @@ "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "rimraf": { "version": "2.7.1", @@ -1517,6 +1594,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "dev": true, "requires": { "es-abstract": "^1.18.0-next.0", "object-inspect": "^1.8.0" @@ -1545,12 +1623,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -1575,6 +1655,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -1584,6 +1665,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -1636,6 +1718,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -1644,12 +1727,14 @@ "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, "requires": { "arg": "^4.1.0", "diff": "^4.0.1", @@ -1674,12 +1759,14 @@ "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true }, "typescript": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", - "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==" + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true }, "typpy": { "version": "2.3.11", @@ -1692,7 +1779,8 @@ "untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==" + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -1711,6 +1799,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "dev": true, "requires": { "is-bigint": "^1.0.0", "is-boolean-object": "^1.0.0", @@ -1723,6 +1812,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "requires": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -1733,12 +1823,14 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "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==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.2", "es-abstract": "^1.17.5", @@ -1752,6 +1844,7 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -1823,7 +1916,8 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "5.0.5", @@ -1857,7 +1951,8 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/src/common.ts b/src/common.ts index 50d2541..803decb 100644 --- a/src/common.ts +++ b/src/common.ts @@ -15,13 +15,12 @@ export const getFaceDetectorOptions = (net: faceapi.NeuralNetwork) => { : new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold }); }; -export function saveFile(fileName: string, buf: Buffer) { - const baseDir = process.env.OUT_DIR as string; - if (!fs.existsSync(baseDir)) { - fs.mkdirSync(baseDir); +export function saveFile(basePath: string, fileName: string, buf: Buffer) { + if (!fs.existsSync(basePath)) { + fs.mkdirSync(basePath); } - fs.writeFileSync(path.resolve(baseDir, fileName), buf, "base64"); + fs.writeFileSync(path.resolve(basePath, fileName), buf, "base64"); } export const delay = (ms: number): Promise => { diff --git a/src/config.ts b/src/config.ts index 43559a1..9556ff7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ export interface IConfig extends PlatformConfig { weightDirectory: string; trainOnStartup: boolean; rooms: Array; + detectionTimeout: number; } export interface IRoom { diff --git a/src/events/event.ts b/src/events/event.ts new file mode 100644 index 0000000..2790a48 --- /dev/null +++ b/src/events/event.ts @@ -0,0 +1,12 @@ +import { EventDelegate } from "./eventDelegate"; + +export class Event extends Array> { + constructor() { + super(); + } + public fire = (source: T, args: K) => { + for (const delegate of this) { + delegate(source, args); + } + }; +} diff --git a/src/events/eventDelegate.ts b/src/events/eventDelegate.ts new file mode 100644 index 0000000..5f14e97 --- /dev/null +++ b/src/events/eventDelegate.ts @@ -0,0 +1 @@ +export type EventDelegate = (sender: T, args: K) => void; diff --git a/src/locationAccessory.ts b/src/locationAccessory.ts new file mode 100644 index 0000000..de1e1da --- /dev/null +++ b/src/locationAccessory.ts @@ -0,0 +1,91 @@ +import { + Service, + CharacteristicGetCallback, + PlatformAccessory, +} from "homebridge"; +import { Monitor, IStateChangeEventArgs } from "./monitor"; +import { HomeLocationPlatform } from "./platform"; +import { IRoom } from "./config"; + +/** + * Platform Accessory + * An instance of this class is created for each accessory your platform registers + * Each accessory may expose multiple services of different service types. + */ +export class LocationAccessory { + private _services: Array; + + constructor( + private readonly _platform: HomeLocationPlatform, + private readonly _accessory: PlatformAccessory, + private _monitor: Monitor, + private _room: IRoom + ) { + this._services = []; + // set accessory information + this._accessory + .getService(this._platform.Service.AccessoryInformation)! + .setCharacteristic( + this._platform.Characteristic.Manufacturer, + "Brandon Watson" + ) + .setCharacteristic( + this._platform.Characteristic.Model, + "Person Location Sensor" + ) + .setCharacteristic( + this._platform.Characteristic.SerialNumber, + "123-456-789" + ); + + //Init motion services + for (const label of this._monitor.labels) { + const newService = + this._accessory.getService(label) || + this._accessory.addService( + this._platform.Service.MotionSensor, + label, + this._room + label + ); + + newService + .getCharacteristic(this._platform.Characteristic.MotionDetected) + .on("get", (callback: CharacteristicGetCallback) => + this.onMotionDetectedGet(label, callback) + ); + + this._services.push(newService); + } + + //Register monitor state change events + this._monitor.stateChangedEvent.push(this.onMonitorStateChange.bind(this)); + } + + private onMotionDetectedGet = ( + label: string, + callback: CharacteristicGetCallback + ) => { + this._platform.log.debug("Triggered GET MotionDetected"); + + // set this to a valid value for MotionDetected + const currentValue = + this._monitor.getState(label) === this._room.name ? 1 : 0; + + callback(null, currentValue); + }; + + private onMonitorStateChange = ( + sender: Monitor, + args: IStateChangeEventArgs + ) => { + const service = this._services.find( + (service) => service.displayName == args.label + ); + if (service) { + service.setCharacteristic( + this._platform.Characteristic.MotionDetected, + args.new === this._room.name + ); + } + }; +} diff --git a/src/locationMonitor.ts b/src/locationMonitor.ts deleted file mode 100644 index e63f20c..0000000 --- a/src/locationMonitor.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { FaceMatcher } from "@vladmandic/face-api"; -import { IRoom } from "./config"; -import { Rtsp } from "rtsp-stream/lib"; - -export interface ILabelState { - label: string; - detected: boolean; -} - -export type IRoomState = { [roomName: string]: Array }; - -export class LocationMonitor { - private _state: IRoomState = {}; - - private _streams: Array = []; - - constructor(private rooms: Array, private matcher: FaceMatcher) { - //Initialize state - for (const room of rooms) { - this._streams.push( - ...room.rtspConnectionStrings.map( - (e) => new Rtsp(e, { rate: 0.5, image: true }) - ) - ); - - this._state[room.name] = matcher.labeledDescriptors.map((e) => { - return { label: e.label, detected: false }; - }); - } - } - - public get state(): IRoomState { - return this._state; - } - - private async startStreams() {} - - private async stopStreams() {} -} diff --git a/src/monitor.ts b/src/monitor.ts new file mode 100644 index 0000000..dac9842 --- /dev/null +++ b/src/monitor.ts @@ -0,0 +1,151 @@ +import { FaceMatcher } from "@vladmandic/face-api"; +import { IRoom } from "./config"; +import { Rtsp } from "rtsp-stream/lib"; +import canvas from "canvas"; +import * as faceapi from "@vladmandic/face-api"; +import { getFaceDetectorOptions, saveFile } from "./common"; +import { nets } from "@vladmandic/face-api"; +import { Logger } from "homebridge"; +import { Event } from "./events/event"; +const { Canvas, Image, ImageData } = canvas; + +export type MonitorState = { [label: string]: string | null }; +export interface IStateChangeEventArgs { + label: string; + old: string | null; + new: string; +} + +export class Monitor { + private _state: MonitorState = {}; + private _streamsByRoom: { [roomName: string]: Array } = {}; + private _faceDetectionNet = nets.ssdMobilenetv1; + private _stateChangedEvent: Event; + + constructor( + private _rooms: Array, + private _matcher: FaceMatcher, + private _logger: Logger + ) { + this._stateChangedEvent = new Event(); + + //Initialize state + for (const room of this._rooms) { + this._streamsByRoom[room.name] = [ + ...room.rtspConnectionStrings.map((connectionString) => { + return new Rtsp(connectionString, { + rate: 0.5, + image: true, + }) + .on("data", async (data: Buffer) => this.onData(room.name, data)) + .on("error", async (error: string) => + this.onError(error, connectionString) + ); + }), + ]; + + _matcher.labeledDescriptors.forEach((descriptor) => { + this._state[descriptor.label] = null; + }); + } + } + + /** + * @method getState + * + * @param label The name of the label to retrieve state for + * + * The last known room of the requested label + */ + public getState(label: string): string | null { + return this._state[label]; + } + + /** + * @property labels + * + * Gets the list of labels associated with the monitor + */ + public get labels(): Array { + return this._matcher.labeledDescriptors + .map((descriptor) => descriptor.label) + .filter( + (label: string, index: number, array: Array) => + array.indexOf(label) === index + ); + } + + public get stateChangedEvent(): Event { + return this._stateChangedEvent; + } + + /** + * @method startStreams + * + * Starts monitoring rtsp streams + */ + public startStreams() { + for (const key in this._streamsByRoom) { + for (const stream of this._streamsByRoom[key]) { + stream.start(); + } + } + } + + /** + * @method closeStreams + * + * Stops monitoring rtsp streams + */ + public closeStreams() { + for (const key in this._streamsByRoom) { + for (const stream of this._streamsByRoom[key]) { + stream.close(); + } + } + } + + private onData = async (room: string, data: Buffer) => { + const input = ((await canvas.loadImage(data)) as unknown) as ImageData; + const out = faceapi.createCanvasFromMedia(input); + const resultsQuery = await faceapi + .detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet)) + .withFaceLandmarks() + .withFaceDescriptors(); + switch (room) { + case "Kitchen": { + saveFile( + "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", + "Kitchen.jpg", + data + ); + break; + } + case "LivingRoom": { + saveFile( + "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", + "LivingRoom.jpg", + data + ); + break; + } + } + + for (const res of resultsQuery) { + const bestMatch = this._matcher.matchDescriptor(res.descriptor); + const old = this._state[bestMatch.label]; + this._state[bestMatch.label] = room; + this._stateChangedEvent.fire(this, { + old: old, + new: room, + label: bestMatch.label, + }); + + this._logger.info(`Face Detected: ${bestMatch.label} in room ${room}`); + } + }; + + private onError = async (error: string, streamName: string) => { + this._logger.info(`[${streamName}] ${error}`); + }; +} diff --git a/src/monitorAccessory.ts b/src/monitorAccessory.ts deleted file mode 100644 index 8a7ea6f..0000000 --- a/src/monitorAccessory.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { - Service, - PlatformAccessory, - CharacteristicValue, - CharacteristicSetCallback, - CharacteristicGetCallback, -} from "homebridge"; -import { LocationMonitor } from "./locationMonitor"; -import { HomeLocationPlatform } from "./platform"; - -/** - * Platform Accessory - * An instance of this class is created for each accessory your platform registers - * Each accessory may expose multiple services of different service types. - */ -export class MonitorAccessory { - private service: Service; - - /** - * These are just used to create a working example - * You should implement your own code to track the state of your accessory - */ - private exampleStates = { - On: false, - Brightness: 100, - }; - - constructor( - private readonly platform: HomeLocationPlatform, - private readonly accessory: PlatformAccessory, - private monitor: LocationMonitor - ) { - // set accessory information - this.accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic( - this.platform.Characteristic.Manufacturer, - "Default-Manufacturer" - ) - .setCharacteristic(this.platform.Characteristic.Model, "Default-Model") - .setCharacteristic( - this.platform.Characteristic.SerialNumber, - "Default-Serial" - ); - - // get the MotionSensor service if it exists, otherwise create a new MotionSensor service - // you can create multiple services for each accessory - this.service = - this.accessory.getService(this.platform.Service.MotionSensor) || - this.accessory.addService(this.platform.Service.MotionSensor); - - // set the service name, this is what is displayed as the default name on the Home app - // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic( - this.platform.Characteristic.Name, - accessory.context["DeviceName"] - ); - - // each service must implement at-minimum the "required characteristics" for the given service type - // see https://developers.homebridge.io/#/service/Lightbulb - - // register handlers for the On/Off Characteristic - this.service - .getCharacteristic(this.platform.Characteristic.On) - .on("set", this.setOn.bind(this)) // SET - bind to the `setOn` method below - .on("get", this.getOn.bind(this)); // GET - bind to the `getOn` method below - - // register handlers for the Brightness Characteristic - this.service - .getCharacteristic(this.platform.Characteristic.Brightness) - .on("set", this.setBrightness.bind(this)); // SET - bind to the 'setBrightness` method below - - /** - * Creating multiple services of the same type. - * - * To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, - * when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - * this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID'); - * - * The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory - * can use the same sub type id.) - */ - - // Example: add two "motion sensor" services to the accessory - const motionSensorOneService = - this.accessory.getService("Motion Sensor One Name") || - this.accessory.addService( - this.platform.Service.MotionSensor, - "Motion Sensor One Name", - "YourUniqueIdentifier-1" - ); - - const motionSensorTwoService = - this.accessory.getService("Motion Sensor Two Name") || - this.accessory.addService( - this.platform.Service.MotionSensor, - "Motion Sensor Two Name", - "YourUniqueIdentifier-2" - ); - - /** - * Updating characteristics values asynchronously. - * - * Example showing how to update the state of a Characteristic asynchronously instead - * of using the `on('get')` handlers. - * Here we change update the motion sensor trigger states on and off every 10 seconds - * the `updateCharacteristic` method. - * - */ - let motionDetected = false; - setInterval(() => { - // EXAMPLE - inverse the trigger - motionDetected = !motionDetected; - - // push the new value to HomeKit - motionSensorOneService.updateCharacteristic( - this.platform.Characteristic.MotionDetected, - motionDetected - ); - motionSensorTwoService.updateCharacteristic( - this.platform.Characteristic.MotionDetected, - !motionDetected - ); - - this.platform.log.debug( - "Triggering motionSensorOneService:", - motionDetected - ); - this.platform.log.debug( - "Triggering motionSensorTwoService:", - !motionDetected - ); - }, 10000); - } - - /** - * Handle "SET" requests from HomeKit - * These are sent when the user changes the state of an accessory, for example, turning on a Light bulb. - */ - setOn(value: CharacteristicValue, callback: CharacteristicSetCallback) { - // implement your own code to turn your device on/off - this.exampleStates.On = value as boolean; - - this.platform.log.debug("Set Characteristic On ->", value); - - // you must call the callback function - callback(null); - } - - /** - * Handle the "GET" requests from HomeKit - * These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on. - * - * GET requests should return as fast as possbile. A long delay here will result in - * HomeKit being unresponsive and a bad user experience in general. - * - * If your device takes time to respond you should update the status of your device - * asynchronously instead using the `updateCharacteristic` method instead. - - * @example - * this.service.updateCharacteristic(this.platform.Characteristic.On, true) - */ - getOn(callback: CharacteristicGetCallback) { - // implement your own code to check if the device is on - const isOn = this.exampleStates.On; - - this.platform.log.debug("Get Characteristic On ->", isOn); - - // you must call the callback function - // the first argument should be null if there were no errors - // the second argument should be the value to return - callback(null, isOn); - } - - /** - * Handle "SET" requests from HomeKit - * These are sent when the user changes the state of an accessory, for example, changing the Brightness - */ - setBrightness( - value: CharacteristicValue, - callback: CharacteristicSetCallback - ) { - // implement your own code to set the brightness - this.exampleStates.Brightness = value as number; - - this.platform.log.debug("Set Characteristic Brightness -> ", value); - - // you must call the callback function - callback(null); - } -} diff --git a/src/platform.ts b/src/platform.ts index 2291199..4ed8792 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -19,7 +19,7 @@ import { FaceMatcher, } from "@vladmandic/face-api"; import * as mime from "mime-types"; -import { LocationMonitor } from "./locationMonitor"; +import { Monitor } from "./monitor"; import { getFaceDetectorOptions } from "./common"; require("@tensorflow/tfjs-node"); @@ -28,7 +28,7 @@ const { Canvas, Image, ImageData } = canvas; faceapi.env.monkeyPatch({ Canvas, Image, ImageData }); import { PLATFORM_NAME, PLUGIN_NAME } from "./settings"; -import { MonitorAccessory } from "./monitorAccessory"; +import { LocationAccessory } from "./locationAccessory"; /** * HomebridgePlatform @@ -100,43 +100,44 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { faceMatcher = FaceMatcher.fromJSON(JSON.parse(raw)); } - const locationMonitor = new LocationMonitor(this.config.rooms, faceMatcher); + const locationMonitor = new Monitor( + this.config.rooms, + faceMatcher, + this.log + ); + + locationMonitor.startStreams(); const labels = faceMatcher.labeledDescriptors.map((e) => e.label); for (const room of this.config.rooms) { - for (const label of labels) { - const uuid = this.api.hap.uuid.generate(room.name + label); + const uuid = this.api.hap.uuid.generate(room.name); - const existingAccessory = this.accessories.find((e) => e.UUID === uuid); - if (existingAccessory) { - this.log.info( - "Restoring existing accessory from cache: ", - existingAccessory.displayName - ); + const existingAccessory = this.accessories.find((e) => e.UUID === uuid); + if (existingAccessory) { + this.log.info( + "Restoring existing accessory from cache: ", + existingAccessory.displayName + ); - new MonitorAccessory(this, existingAccessory, locationMonitor); + new LocationAccessory(this, existingAccessory, locationMonitor, room); - this.api.updatePlatformAccessories([existingAccessory]); - } else { - this.log.info("Adding new accessory:", `${room.name}+${label}`); + this.api.updatePlatformAccessories([existingAccessory]); + } else { + this.log.info("Adding new accessory:", `${room.name}`); - // create a new accessory - const accessory = new this.api.platformAccessory( - `${room.name} ${label}`, - uuid - ); + // create a new accessory + const accessory = new this.api.platformAccessory(`${room.name}`, uuid); - accessory.context["DeviceName"] = `${room.name} ${label}`; + accessory.context["DeviceName"] = `${room.name}`; - // create the accessory handler for the newly create accessory - // this is imported from `platformAccessory.ts` - new MonitorAccessory(this, accessory, locationMonitor); + // create the accessory handler for the newly create accessory + // this is imported from `platformAccessory.ts` + new LocationAccessory(this, accessory, locationMonitor, room); - // link the accessory to your platform - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ - accessory, - ]); - } + // link the accessory to your platform + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ + accessory, + ]); } } } From 0792147dc409349823e039480d2c08652f271e49 Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Thu, 10 Dec 2020 13:54:06 -0500 Subject: [PATCH 4/6] Using events from common lib --- package-lock.json | 4 +++ package.json | 1 + src/config.ts | 1 + src/events/event.ts | 12 --------- src/events/eventDelegate.ts | 1 - src/monitor.ts | 53 +++++++++++++++++++++---------------- src/platform.ts | 3 ++- tsconfig.json | 6 ++++- 8 files changed, 43 insertions(+), 38 deletions(-) delete mode 100644 src/events/event.ts delete mode 100644 src/events/eventDelegate.ts diff --git a/package-lock.json b/package-lock.json index 80603d6..9a332e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1200,6 +1200,10 @@ "sax": "^1.2.4" } }, + "node-common": { + "version": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git#3ee1400be94851335e822916861ea2eddb9e344f", + "from": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git" + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", diff --git a/package.json b/package.json index 64748c5..78c654a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dotenv-extended": "^2.9.0", "mime-types": "^2.1.27", "rtsp-stream": "file:../rtsp-stream", + "node-common": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git", "tsyringe": "^4.4.0" }, "devDependencies": { diff --git a/src/config.ts b/src/config.ts index 9556ff7..53a2a8b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,6 +7,7 @@ export interface IConfig extends PlatformConfig { trainOnStartup: boolean; rooms: Array; detectionTimeout: number; + debug: boolean; } export interface IRoom { diff --git a/src/events/event.ts b/src/events/event.ts deleted file mode 100644 index 2790a48..0000000 --- a/src/events/event.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EventDelegate } from "./eventDelegate"; - -export class Event extends Array> { - constructor() { - super(); - } - public fire = (source: T, args: K) => { - for (const delegate of this) { - delegate(source, args); - } - }; -} diff --git a/src/events/eventDelegate.ts b/src/events/eventDelegate.ts deleted file mode 100644 index 5f14e97..0000000 --- a/src/events/eventDelegate.ts +++ /dev/null @@ -1 +0,0 @@ -export type EventDelegate = (sender: T, args: K) => void; diff --git a/src/monitor.ts b/src/monitor.ts index dac9842..24ebabd 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -6,7 +6,7 @@ import * as faceapi from "@vladmandic/face-api"; import { getFaceDetectorOptions, saveFile } from "./common"; import { nets } from "@vladmandic/face-api"; import { Logger } from "homebridge"; -import { Event } from "./events/event"; +import { Event } from "common/events"; const { Canvas, Image, ImageData } = canvas; export type MonitorState = { [label: string]: string | null }; @@ -25,7 +25,8 @@ export class Monitor { constructor( private _rooms: Array, private _matcher: FaceMatcher, - private _logger: Logger + private _logger: Logger, + private _isDebug: boolean ) { this._stateChangedEvent = new Event(); @@ -34,13 +35,14 @@ export class Monitor { this._streamsByRoom[room.name] = [ ...room.rtspConnectionStrings.map((connectionString) => { return new Rtsp(connectionString, { - rate: 0.5, + rate: 1, image: true, }) - .on("data", async (data: Buffer) => this.onData(room.name, data)) - .on("error", async (error: string) => + .on("data", (data: Buffer) => this.onData(room.name, data)) + .on("error", (error: string) => this.onError(error, connectionString) - ); + ) + .on("exit", () => this.onExit(connectionString)); }), ]; @@ -112,22 +114,24 @@ export class Monitor { .detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet)) .withFaceLandmarks() .withFaceDescriptors(); - switch (room) { - case "Kitchen": { - saveFile( - "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", - "Kitchen.jpg", - data - ); - break; - } - case "LivingRoom": { - saveFile( - "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", - "LivingRoom.jpg", - data - ); - break; + if (this._isDebug) { + switch (room) { + case "Kitchen": { + saveFile( + "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", + "Kitchen.jpg", + data + ); + break; + } + case "LivingRoom": { + saveFile( + "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", + "LivingRoom.jpg", + data + ); + break; + } } } @@ -145,7 +149,10 @@ export class Monitor { } }; - private onError = async (error: string, streamName: string) => { + private onError = (error: string, streamName: string) => { this._logger.info(`[${streamName}] ${error}`); }; + private onExit = (streamName: string) => { + this._logger.info(`[${streamName}] Stream has exited`); + }; } diff --git a/src/platform.ts b/src/platform.ts index 4ed8792..b32ef45 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -103,7 +103,8 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { const locationMonitor = new Monitor( this.config.rooms, faceMatcher, - this.log + this.log, + this.config.debug ); locationMonitor.startStreams(); diff --git a/tsconfig.json b/tsconfig.json index b5b8cc8..e82e96a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,7 +39,8 @@ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, "paths": { - "rtsp/*": ["./node_modules/rtsp-stream/lib/*"] + "rtsp/*": ["./node_modules/rtsp-stream/lib/*"], + "common/*": ["./node_modules/node-common/lib/*"] } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ @@ -65,6 +66,9 @@ "references": [ { "path": "./node_modules/rtsp-stream/tsconfig.json" + }, + { + "path": "./node_modules/node-common/tsconfig.json" } ] } From 4e873edc9798d72a6bcf82a1b4d449203a970a7f Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Thu, 10 Dec 2020 19:57:12 -0500 Subject: [PATCH 5/6] Giving up on having nested projects --- package-lock.json | 22 ------ package.json | 2 - src/events/event.ts | 12 +++ src/events/eventDelegate.ts | 1 + src/events/index.ts | 2 + src/monitor.ts | 32 ++++---- src/rtsp/index.ts | 146 ++++++++++++++++++++++++++++++++++++ src/rtsp/options.ts | 7 ++ tsconfig.json | 14 +--- 9 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 src/events/event.ts create mode 100644 src/events/eventDelegate.ts create mode 100644 src/events/index.ts create mode 100644 src/rtsp/index.ts create mode 100644 src/rtsp/options.ts diff --git a/package-lock.json b/package-lock.json index 9a332e5..65b0804 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1200,10 +1200,6 @@ "sax": "^1.2.4" } }, - "node-common": { - "version": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git#3ee1400be94851335e822916861ea2eddb9e344f", - "from": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git" - }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -1533,24 +1529,6 @@ "glob": "^7.1.3" } }, - "rtsp-stream": { - "version": "file:../rtsp-stream", - "requires": { - "child_process": "^1.0.2" - }, - "dependencies": { - "@types/node": { - "version": "14.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", - "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==" - }, - "child_process": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=" - } - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", diff --git a/package.json b/package.json index 78c654a..0bf7424 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,6 @@ "canvas": "^2.6.1", "dotenv-extended": "^2.9.0", "mime-types": "^2.1.27", - "rtsp-stream": "file:../rtsp-stream", - "node-common": "git+ssh://git@thebword.ddns.net:3122/watsonb8/node-common.git", "tsyringe": "^4.4.0" }, "devDependencies": { diff --git a/src/events/event.ts b/src/events/event.ts new file mode 100644 index 0000000..2790a48 --- /dev/null +++ b/src/events/event.ts @@ -0,0 +1,12 @@ +import { EventDelegate } from "./eventDelegate"; + +export class Event extends Array> { + constructor() { + super(); + } + public fire = (source: T, args: K) => { + for (const delegate of this) { + delegate(source, args); + } + }; +} diff --git a/src/events/eventDelegate.ts b/src/events/eventDelegate.ts new file mode 100644 index 0000000..5f14e97 --- /dev/null +++ b/src/events/eventDelegate.ts @@ -0,0 +1 @@ +export type EventDelegate = (sender: T, args: K) => void; diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..0adc040 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,2 @@ +export * from "./event"; +export * from "./eventDelegate"; diff --git a/src/monitor.ts b/src/monitor.ts index 24ebabd..1d751ff 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -1,12 +1,12 @@ import { FaceMatcher } from "@vladmandic/face-api"; import { IRoom } from "./config"; -import { Rtsp } from "rtsp-stream/lib"; +import { Rtsp, IStreamEventArgs, ICloseEventArgs } from "./rtsp"; import canvas from "canvas"; import * as faceapi from "@vladmandic/face-api"; import { getFaceDetectorOptions, saveFile } from "./common"; import { nets } from "@vladmandic/face-api"; import { Logger } from "homebridge"; -import { Event } from "common/events"; +import { Event } from "./events"; const { Canvas, Image, ImageData } = canvas; export type MonitorState = { [label: string]: string | null }; @@ -34,15 +34,17 @@ export class Monitor { for (const room of this._rooms) { this._streamsByRoom[room.name] = [ ...room.rtspConnectionStrings.map((connectionString) => { - return new Rtsp(connectionString, { + const rtsp = new Rtsp(connectionString, { rate: 1, image: true, - }) - .on("data", (data: Buffer) => this.onData(room.name, data)) - .on("error", (error: string) => - this.onError(error, connectionString) - ) - .on("exit", () => this.onExit(connectionString)); + }); + rtsp.dataEvent.push((sender: Rtsp, args: IStreamEventArgs) => + this.onData(room.name, args) + ); + rtsp.closeEvent.push((sender: Rtsp, args: ICloseEventArgs) => + this.onExit(connectionString, args) + ); + return rtsp; }), ]; @@ -107,8 +109,8 @@ export class Monitor { } } - private onData = async (room: string, data: Buffer) => { - const input = ((await canvas.loadImage(data)) as unknown) as ImageData; + private onData = async (room: string, args: IStreamEventArgs) => { + const input = ((await canvas.loadImage(args.data)) as unknown) as ImageData; const out = faceapi.createCanvasFromMedia(input); const resultsQuery = await faceapi .detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet)) @@ -120,7 +122,7 @@ export class Monitor { saveFile( "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", "Kitchen.jpg", - data + args.data ); break; } @@ -128,7 +130,7 @@ export class Monitor { saveFile( "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", "LivingRoom.jpg", - data + args.data ); break; } @@ -152,7 +154,7 @@ export class Monitor { private onError = (error: string, streamName: string) => { this._logger.info(`[${streamName}] ${error}`); }; - private onExit = (streamName: string) => { - this._logger.info(`[${streamName}] Stream has exited`); + private onExit = (streamName: string, args: ICloseEventArgs) => { + this._logger.info(`[${streamName}] Stream has exited: ${args.message}`); }; } diff --git a/src/rtsp/index.ts b/src/rtsp/index.ts new file mode 100644 index 0000000..b547a88 --- /dev/null +++ b/src/rtsp/index.ts @@ -0,0 +1,146 @@ +import { ChildProcess, spawn } from "child_process"; +import { Writable } from "stream"; +import { IOptions } from "./options"; +import { Event } from "../events"; + +const ef1 = "ff"; +const ef2 = "d9"; + +export interface IStreamEventArgs { + data: Buffer; +} + +export interface ICloseEventArgs { + message: string; +} +export interface IErrorEventArgs { + message?: string; + err?: Error; +} + +export class Rtsp { + private _connecteionString: string; + private _childProcess: ChildProcess | undefined; + private _started: boolean; + private _buffer: Buffer; + private _options: IOptions; + private _paused: boolean; + + private _dataEvent: Event; + private _closeEvent: Event; + private _errorEvent: Event; + + constructor(connectionString: string, options: IOptions) { + this._started = false; + this._connecteionString = connectionString; + this._childProcess = undefined; + this._buffer = Buffer.from(""); + this._options = options; + this._paused = false; + this._dataEvent = new Event(); + this._closeEvent = new Event(); + this._errorEvent = new Event(); + + this.onData = this.onData.bind(this); + } + + public get isStarted(): boolean { + return this._started; + } + + public get isPaused(): boolean { + return this._paused; + } + + public get dataEvent(): Event { + return this._dataEvent; + } + + public get closeEvent(): Event { + return this._closeEvent; + } + + public get errorEvent(): Event { + return this._errorEvent; + } + + public start(): void { + const argStrings = [ + `-i ${this._connecteionString}`, + `-r ${this._options.rate ?? 10}`, + this._options.image + ? `-f image2` + : `-codec:v ${this._options.codec ?? "libx264"}`, + `-update 1 -`, + ]; + const args = argStrings.join(" "); + this._childProcess = spawn("ffmpeg", args.split(/\s+/)); + + if (!this._childProcess) { + return; + } + + this._childProcess.stdout?.on("data", this.onData); + this._childProcess.on("exit", (code: number, signal: NodeJS.Signals) => + this._closeEvent.fire(this, { + message: "FFmpeg exited with code: " + code + " and signal: " + signal, + }) + ); + this._childProcess.on("error", (error: Error) => + this._errorEvent.fire(this, { err: error }) + ); + + //Only register this event if there are subscribers + if (this._childProcess.stderr && this._errorEvent.length > 0) { + this._childProcess.stderr.on("data", this.onStdErrorData); + } + } + + public close(): void { + this._childProcess && this._childProcess.kill("SIGKILL"); + this._closeEvent.fire(this, { message: "Process killed by user" }); + } + + public pause(): void { + this._paused = true; + } + + public resume(): void { + this._paused = false; + } + + public getStdin(): Writable | null { + return this._childProcess ? this._childProcess.stdin : null; + } + + private onStdErrorData = (data: any): void => { + if (!this._started) { + this._started = true; + } + let msg = ""; + data + .toString() + .split(/\n/) + .forEach((line: string) => { + msg += `${line}\n`; + }); + + this._errorEvent.fire(this, { message: msg }); + }; + + private onData(data: Buffer): void { + if (!this._paused && data.length > 1) { + this._buffer = this._buffer + ? Buffer.concat([this._buffer, data]) + : (this._buffer = Buffer.from(data)); + //End of image + if ( + data[data.length - 2].toString(16) == ef1 && + data[data.length - 1].toString(16) == ef2 + ) { + this._dataEvent.fire(this, { data: this._buffer }); + this._buffer = Buffer.from(""); + } + } + } +} diff --git a/src/rtsp/options.ts b/src/rtsp/options.ts new file mode 100644 index 0000000..49090db --- /dev/null +++ b/src/rtsp/options.ts @@ -0,0 +1,7 @@ +export interface IOptions { + rate?: number; + quality?: number; + resolution?: string; + codec?: string; + image?: boolean; +} diff --git a/tsconfig.json b/tsconfig.json index e82e96a..54253fd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,8 +39,8 @@ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, "paths": { - "rtsp/*": ["./node_modules/rtsp-stream/lib/*"], - "common/*": ["./node_modules/node-common/lib/*"] + "rtsp/*": ["node_modules/rtsp-stream/lib/*"], + "common/*": ["node_modules/node-common/lib/*"] } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ @@ -62,13 +62,5 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": ["./src"], - "exclude": ["node_modules"], - "references": [ - { - "path": "./node_modules/rtsp-stream/tsconfig.json" - }, - { - "path": "./node_modules/node-common/tsconfig.json" - } - ] + "exclude": ["node_modules"] } From 5cec734a097e0783cbe04fa4b1a761755fb259cc Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Fri, 11 Dec 2020 00:02:10 -0500 Subject: [PATCH 6/6] Successful stream stress test Need to add stream watchdog timer and accessory timeout timer --- scripts/streamAndDetect.ts | 12 ++--- src/common.ts | 33 ++++++++++--- src/config.ts | 6 +++ src/{platform.ts => homeLocationPlatform.ts} | 2 +- src/index.ts | 2 +- src/locationAccessory.ts | 2 +- src/monitor.ts | 49 ++++++++++---------- src/rtsp/{index.ts => rtsp.ts} | 38 ++++++++++++--- 8 files changed, 96 insertions(+), 48 deletions(-) rename src/{platform.ts => homeLocationPlatform.ts} (99%) rename src/rtsp/{index.ts => rtsp.ts} (77%) diff --git a/scripts/streamAndDetect.ts b/scripts/streamAndDetect.ts index e8dedfb..8888a43 100644 --- a/scripts/streamAndDetect.ts +++ b/scripts/streamAndDetect.ts @@ -1,4 +1,4 @@ -import { Rtsp } from "rtsp-stream/lib"; +import { Rtsp, IStreamEventArgs } from "../src/rtsp/rtsp"; import { nets } from "@vladmandic/face-api"; import * as faceapi from "@vladmandic/face-api"; import canvas from "canvas"; @@ -37,10 +37,10 @@ const main = async () => { const content = JSON.parse(raw); const matcher = faceapi.FaceMatcher.fromJSON(content); - rtsp.on("data", async (data: Buffer) => { - const input = ((await canvas.loadImage(data)) as unknown) as ImageData; + rtsp.dataEvent.push(async (sender: Rtsp, args: IStreamEventArgs) => { + const input = ((await canvas.loadImage(args.data)) as unknown) as ImageData; const out = faceapi.createCanvasFromMedia(input); - await saveFile("image.jpg", data); + await saveFile(process.env.OUT_DIR as string, "image.jpg", args.data); const resultsQuery = await faceapi .detectAllFaces(out, getFaceDetectorOptions(faceDetectionNet)) .withFaceLandmarks() @@ -52,10 +52,6 @@ const main = async () => { } }); - rtsp.on("error", (err) => { - // console.log(err); - }); - rtsp.start(); }; diff --git a/src/common.ts b/src/common.ts index 803decb..60da41f 100644 --- a/src/common.ts +++ b/src/common.ts @@ -15,12 +15,33 @@ export const getFaceDetectorOptions = (net: faceapi.NeuralNetwork) => { : new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold }); }; -export function saveFile(basePath: string, fileName: string, buf: Buffer) { - if (!fs.existsSync(basePath)) { - fs.mkdirSync(basePath); - } - - fs.writeFileSync(path.resolve(basePath, fileName), buf, "base64"); +export function saveFile( + basePath: string, + fileName: string, + buf: Buffer +): Promise { + const writeFile = (): Promise => { + return new Promise((resolve, reject) => { + fs.writeFile(path.resolve(basePath, fileName), buf, "base64", (err) => { + if (err) { + return reject(err); + } + resolve(); + }); + }); + }; + return new Promise(async (resolve, reject) => { + if (!fs.existsSync(basePath)) { + fs.mkdir(basePath, async (err) => { + if (err) { + return reject(err); + } + resolve(await writeFile()); + }); + } else { + resolve(await writeFile()); + } + }); } export const delay = (ms: number): Promise => { diff --git a/src/config.ts b/src/config.ts index 53a2a8b..5783546 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,10 +4,12 @@ export interface IConfig extends PlatformConfig { refImageDirectory: string; trainedModelDirectory: string; weightDirectory: string; + outputDirectory: string; trainOnStartup: boolean; rooms: Array; detectionTimeout: number; debug: boolean; + writeOutput: boolean; } export interface IRoom { @@ -27,6 +29,10 @@ export const isConfig = (object: any): object is IConfig => { "refImageDirectory" in object && "trainedModelDirectory" in object && "weightDirectory" in object && + "outputDirectory" in object && + "trainOnStartup" in object && + "detectionTimeout" in object && + "writeOutput" in object && "rooms" in object && roomsOkay ); diff --git a/src/platform.ts b/src/homeLocationPlatform.ts similarity index 99% rename from src/platform.ts rename to src/homeLocationPlatform.ts index b32ef45..417e470 100644 --- a/src/platform.ts +++ b/src/homeLocationPlatform.ts @@ -104,7 +104,7 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin { this.config.rooms, faceMatcher, this.log, - this.config.debug + this.config ); locationMonitor.startStreams(); diff --git a/src/index.ts b/src/index.ts index edf4b7d..b678c9f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { API } from "homebridge"; import { PLATFORM_NAME } from "./settings"; -import { HomeLocationPlatform } from "./platform"; +import { HomeLocationPlatform } from "./homeLocationPlatform"; /** * This method registers the platform with Homebridge diff --git a/src/locationAccessory.ts b/src/locationAccessory.ts index de1e1da..89123ce 100644 --- a/src/locationAccessory.ts +++ b/src/locationAccessory.ts @@ -4,7 +4,7 @@ import { PlatformAccessory, } from "homebridge"; import { Monitor, IStateChangeEventArgs } from "./monitor"; -import { HomeLocationPlatform } from "./platform"; +import { HomeLocationPlatform } from "./homeLocationPlatform"; import { IRoom } from "./config"; /** diff --git a/src/monitor.ts b/src/monitor.ts index 1d751ff..01c4170 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -1,12 +1,19 @@ import { FaceMatcher } from "@vladmandic/face-api"; import { IRoom } from "./config"; -import { Rtsp, IStreamEventArgs, ICloseEventArgs } from "./rtsp"; +import { + Rtsp, + IStreamEventArgs, + ICloseEventArgs, + IErrorEventArgs, + IMessageEventArgs, +} from "./rtsp/rtsp"; import canvas from "canvas"; import * as faceapi from "@vladmandic/face-api"; import { getFaceDetectorOptions, saveFile } from "./common"; import { nets } from "@vladmandic/face-api"; import { Logger } from "homebridge"; import { Event } from "./events"; +import { IConfig } from "./config"; const { Canvas, Image, ImageData } = canvas; export type MonitorState = { [label: string]: string | null }; @@ -26,7 +33,7 @@ export class Monitor { private _rooms: Array, private _matcher: FaceMatcher, private _logger: Logger, - private _isDebug: boolean + private _config: IConfig ) { this._stateChangedEvent = new Event(); @@ -35,7 +42,7 @@ export class Monitor { this._streamsByRoom[room.name] = [ ...room.rtspConnectionStrings.map((connectionString) => { const rtsp = new Rtsp(connectionString, { - rate: 1, + rate: 0.7, image: true, }); rtsp.dataEvent.push((sender: Rtsp, args: IStreamEventArgs) => @@ -44,6 +51,15 @@ export class Monitor { rtsp.closeEvent.push((sender: Rtsp, args: ICloseEventArgs) => this.onExit(connectionString, args) ); + rtsp.errorEvent.push((sender: Rtsp, args: IErrorEventArgs) => + this.onError(args, connectionString) + ); + if (this._config.debug) { + rtsp.messageEvent.push((sender: Rtsp, args: IMessageEventArgs) => { + this._logger.info(`[${connectionString}] ${args.message}`); + }); + } + return rtsp; }), ]; @@ -116,25 +132,10 @@ export class Monitor { .detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet)) .withFaceLandmarks() .withFaceDescriptors(); - if (this._isDebug) { - switch (room) { - case "Kitchen": { - saveFile( - "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", - "Kitchen.jpg", - args.data - ); - break; - } - case "LivingRoom": { - saveFile( - "/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out", - "LivingRoom.jpg", - args.data - ); - break; - } - } + + //Write to output image + if (this._config.writeOutput) { + await saveFile(this._config.outputDirectory, room + ".jpg", args.data); } for (const res of resultsQuery) { @@ -151,8 +152,8 @@ export class Monitor { } }; - private onError = (error: string, streamName: string) => { - this._logger.info(`[${streamName}] ${error}`); + private onError = (args: IErrorEventArgs, streamName: string) => { + this._logger.info(`[${streamName}] ${args.message}`); }; private onExit = (streamName: string, args: ICloseEventArgs) => { this._logger.info(`[${streamName}] Stream has exited: ${args.message}`); diff --git a/src/rtsp/index.ts b/src/rtsp/rtsp.ts similarity index 77% rename from src/rtsp/index.ts rename to src/rtsp/rtsp.ts index b547a88..706675b 100644 --- a/src/rtsp/index.ts +++ b/src/rtsp/rtsp.ts @@ -18,6 +18,10 @@ export interface IErrorEventArgs { err?: Error; } +export interface IMessageEventArgs { + message: string; +} + export class Rtsp { private _connecteionString: string; private _childProcess: ChildProcess | undefined; @@ -29,6 +33,7 @@ export class Rtsp { private _dataEvent: Event; private _closeEvent: Event; private _errorEvent: Event; + private _messageEvent: Event; constructor(connectionString: string, options: IOptions) { this._started = false; @@ -40,6 +45,7 @@ export class Rtsp { this._dataEvent = new Event(); this._closeEvent = new Event(); this._errorEvent = new Event(); + this._messageEvent = new Event(); this.onData = this.onData.bind(this); } @@ -56,6 +62,10 @@ export class Rtsp { return this._dataEvent; } + public get messageEvent(): Event { + return this._messageEvent; + } + public get closeEvent(): Event { return this._closeEvent; } @@ -68,6 +78,7 @@ export class Rtsp { const argStrings = [ `-i ${this._connecteionString}`, `-r ${this._options.rate ?? 10}`, + `-vf mpdecimate,setpts=N/FRAME_RATE/TB`, this._options.image ? `-f image2` : `-codec:v ${this._options.codec ?? "libx264"}`, @@ -81,19 +92,32 @@ export class Rtsp { } this._childProcess.stdout?.on("data", this.onData); + this._childProcess.stdout?.on("error", (err) => + console.log("And error occurred" + err) + ); + this._childProcess.stdout?.on("close", () => console.log("Stream closed")); + this._childProcess.stdout?.on("end", () => console.log("Stream ended")); + + //Only register this event if there are subscribers + if (this._childProcess.stderr && this._messageEvent.length > 0) { + this._childProcess.stderr.on("data", this.onMessage); + } + + this._childProcess.on("close", (code: number, signal: NodeJS.Signals) => + this._closeEvent.fire(this, { + message: "FFmpeg exited with code: " + code + " and signal: " + signal, + }) + ); + this._childProcess.on("exit", (code: number, signal: NodeJS.Signals) => this._closeEvent.fire(this, { message: "FFmpeg exited with code: " + code + " and signal: " + signal, }) ); + this._childProcess.on("error", (error: Error) => this._errorEvent.fire(this, { err: error }) ); - - //Only register this event if there are subscribers - if (this._childProcess.stderr && this._errorEvent.length > 0) { - this._childProcess.stderr.on("data", this.onStdErrorData); - } } public close(): void { @@ -113,7 +137,7 @@ export class Rtsp { return this._childProcess ? this._childProcess.stdin : null; } - private onStdErrorData = (data: any): void => { + private onMessage = (data: any): void => { if (!this._started) { this._started = true; } @@ -125,7 +149,7 @@ export class Rtsp { msg += `${line}\n`; }); - this._errorEvent.fire(this, { message: msg }); + this._messageEvent.fire(this, { message: msg }); }; private onData(data: Buffer): void {