From 513ee2015d3259246ee39c0eb3f05f03ac515597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=B8rch?= Date: Fri, 13 Jul 2018 14:33:17 +0200 Subject: [PATCH] Added more features to decode Decode can now do lookups for sensor types, reported during node presentation, and use that to enrich the emitted mysensors node message Also added more tests on controller class --- package-lock.json | 511 +++++++++++++++++++++++++- package.json | 10 +- src/lib/database-sqlite.ts | 45 ++- src/lib/database.interface.ts | 7 +- src/lib/decoder/auto-decode.ts | 6 +- src/lib/decoder/decoder.interface.ts | 2 +- src/lib/decoder/mysensors-decoder.ts | 18 +- src/lib/decoder/mysensors-mqtt.ts | 4 +- src/lib/decoder/mysensors-serial.ts | 4 +- src/lib/mysensors-controller.spec.ts | 62 ++++ src/lib/mysensors-controller.ts | 12 +- src/lib/mysensors-msg.ts | 3 +- src/migrations/003-update-columns.sql | 34 ++ src/nodes/common.ts | 4 + src/nodes/decode.html | 40 +- src/nodes/decode.ts | 27 +- src/nodes/mysdebug.ts | 4 +- src/nodes/mysensors-db.ts | 4 +- 18 files changed, 740 insertions(+), 57 deletions(-) create mode 100644 src/lib/mysensors-controller.spec.ts create mode 100644 src/migrations/003-update-columns.sql diff --git a/package-lock.json b/package-lock.json index f8799e6..ef24ab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,31 +1,40 @@ { "name": "node-red-contrib-mysensors", - "version": "3.0.0", + "version": "3.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha1-hNt+nrVTHfGKjF4L+25EnlXmVLI=", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, "@types/chai": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.3.tgz", - "integrity": "sha1-uKdDUpd6I7YEwBqnhPW3k0Q/t9w=", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.4.tgz", + "integrity": "sha1-XKBzszDZC0Bm1s4Y9g1X8ghM6Mo=", "dev": true }, "@types/events": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "integrity": "sha1-gaZzHOTfQ2GeXIyUU4Oz5iqJ6oY=", "dev": true }, "@types/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-s8jmnwOINdsaf9wLPYefxQUG4p4=", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.3.tgz", + "integrity": "sha512-C1wVVr7xhKu6c3Mb27dFzNYR05qvHwgtpN+JOYTGc1pKA7dCEDDYpscn7kul+bCUwa3NoGDbzI1pdznSOa397w==", "dev": true }, "@types/moment-timezone": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.6.tgz", - "integrity": "sha512-rMZjLmXs9sly1UbwxckyAEvQkrwrGqR24nFAjFrndRJBBnUooCCD0LPmdRcf9haHXFnckI9E3ko0oC6LEDk7dw==", + "integrity": "sha1-ITQfXL7spudzcCsk//058Gv24gM=", "requires": { "moment": ">=2.14.0" } @@ -39,19 +48,105 @@ "@types/node-red": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@types/node-red/-/node-red-0.17.3.tgz", - "integrity": "sha512-ZBpI+jv/0MSQIn7vhyR2Av4WY2R6viQPTzYUGYLJcm3t0Y/HdrZJAMYXqlPv/ZmOCypcisVjljmxpMfkuNBXLg==", + "integrity": "sha1-FhAluGbPDQcN9x9ykR/3BBZaJLY=", "dev": true, "requires": { "@types/events": "*", "@types/node": "*" } }, + "@types/sinon": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.1.tgz", + "integrity": "sha1-oVs27ELx9TFmYXSR/qvRc0ywPiE=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", "dev": true }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -66,12 +161,70 @@ "type-detect": "^4.0.0" } }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha1-SYgbj7pn3xKpa98/VsCqueeRMUc=", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -81,36 +234,297 @@ "type-detect": "^4.0.0" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha1-6u1lbsg0TxD1J8a/obbiJE3hZ9E=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha1-7G55QQ/5FORyZSq/oOYDwD1g6QU=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lolex": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", + "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, "moment": { "version": "2.22.2", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "moment-timezone": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.17.tgz", - "integrity": "sha512-Y/JpVEWIOA9Gho4vO15MTnW1FCmHi3ypprrkUaxsZ1TKg3uqC8q/qMBjTddkHoiwwZN3qvZSr4zJP7x9V3LpXA==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.20.tgz", + "integrity": "sha512-uJXgstE4ddmJpLFIyihm7VsLJsViDxO88lx+GmIbSnyAAHHbSdpwBhpE2N3KFOmDa/9BxYDykQUzH796CHga2w==", "requires": { "moment": ">= 2.9.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "nan": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", "integrity": "sha1-9WTXX1+PNqbZRWzKemxP5IireGY=" }, + "nise": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.2.tgz", + "integrity": "sha1-qaOADjmUmUr55FIzPVSdYPcrjow=", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "just-extend": "^1.1.27", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha1-gvHsGaQjrB+9CAsLqwa6NuhKeiY=", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha1-jR2TUOJWItow3j5EumkrUiGrfFA=", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=", + "dev": true + }, + "sinon": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.0.0.tgz", + "integrity": "sha512-MatciKXyM5pXMSoqd593MqTsItJNCkSSl53HJYeKR5wfsDdp2yljjUQJLfVwAWLoBNfx1HThteqygGQ0ZEpXpQ==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "diff": "^3.5.0", + "lodash.get": "^4.4.2", + "lolex": "^2.4.2", + "nise": "^1.3.3", + "supports-color": "^5.4.0", + "type-detect": "^4.0.8" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sqlite": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-2.9.2.tgz", @@ -524,11 +938,82 @@ } } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha1-1+TdeSRdhUKMTX5IIqeZF5VMooY=", + "dev": true + }, + "tslint": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", + "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" + } + }, + "tsutils": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", + "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", "dev": true + }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha1-HL9h0F1rliaSROtqO85L2RTg8Aw=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true } } } diff --git a/package.json b/package.json index 09a1924..4bdbddb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "author": { "name": "Thomas Bowman Mørch" }, - "version": "3.1.2", + "version": "3.2.0", "scripts": { "build": "mkdir -p dist/nodes/ && cp -a src/nodes/*.html dist/nodes/ && cp -R src/migrations dist && tsc ", "pretest": "tsc", @@ -44,16 +44,18 @@ }, "dependencies": { "@types/moment-timezone": "^0.5.6", - "moment-timezone": "^0.5.17", + "@types/sinon": "^5.0.1", + "moment-timezone": "^0.5.20", "sqlite": "^2.9.2" }, "devDependencies": { - "@types/chai": "^4.1.3", - "@types/mocha": "^5.2.0", + "@types/chai": "^4.1.4", + "@types/mocha": "^5.2.3", "@types/node": "^9.6.18", "@types/node-red": "^0.17.3", "chai": "^4.1.2", "mocha": "^5.2.0", + "sinon": "^6.0.0", "tslint": "^5.10.0", "typescript": "^2.8.3" } diff --git a/src/lib/database-sqlite.ts b/src/lib/database-sqlite.ts index 8497e99..4ab595e 100644 --- a/src/lib/database-sqlite.ts +++ b/src/lib/database-sqlite.ts @@ -1,8 +1,8 @@ import * as path from 'path'; import { open } from 'sqlite'; -import { IDatabase, INodeData } from './database.interface'; +import { IDatabase, INodeData, ISensorData } from './database.interface'; import { NullCheck } from './nullcheck'; -export class Database implements IDatabase { +export class DatabaseSqlite implements IDatabase { private dbPromise: any; /** @@ -11,27 +11,28 @@ export class Database implements IDatabase { */ constructor(private file: string) { if (NullCheck.isUndefinedNullOrEmpty(this.file)) { - throw new Error('No dbname set'); + // throw new Error('No dbname set'); + } else { + this.dbPromise = Promise.resolve() + .then(() => open(this.file)) + .then((db) => db.migrate({migrationsPath: path.dirname(__dirname) + '/migrations'})); + this.checkDb(); } - this.dbPromise = Promise.resolve() - .then(() => open(this.file)) - .then((db) => db.migrate({migrationsPath: path.dirname(__dirname) + '/migrations'})); - this.checkDb(); } public async nodeHeard(nodeId: number): Promise { const db = await this.dbPromise; - await db.run(`update node set lastHeard=CURRENT_TIMESTAMP, used=1 where id=${nodeId}`); + await db.run(`update node set lastHeard=CURRENT_TIMESTAMP, used=1 where nodeId=${nodeId}`); } public async sketchName(nodeId: number, name: string): Promise { const db = await this.dbPromise; - await db.run(`update node set sketchName='${name}' where id=${nodeId}`); + await db.run(`update node set sketchName='${name}' where nodeId=${nodeId}`); } public async sketchVersion(nodeId: number, version: string): Promise { const db = await this.dbPromise; - await db.run( `update node set sketchVersion='${version}', lastRestart=CURRENT_TIMESTAMP where id=${nodeId}`); + await db.run( `update node set sketchVersion='${version}', lastRestart=CURRENT_TIMESTAMP where nodeId=${nodeId}`); } public async getNodeList(): Promise { @@ -42,7 +43,7 @@ export class Database implements IDatabase { public async getFreeNodeId(): Promise { const db = await this.dbPromise; - const res = await db.get('select min(id) id from node where used=0'); + const res = await db.get('select min(nodeId) id from node where used=0'); return res.id; } @@ -53,16 +54,32 @@ export class Database implements IDatabase { public async setParent(node: string, last: string): Promise { const db = await this.dbPromise; - await db.run(`update node set parentId=${last} where id=${node}`); + await db.run(`update node set parentId=${last} where nodeId=${node}`); + } + + public async child(nodeId: number, childId: number, type: number, description: string): Promise { + const db = await this.dbPromise; + db.run(`insert or replace into child (childId, nodeId, sType, description) values(${childId}, ${nodeId}, ${type}, '${description}')`); + } + + public async childHeard(nodeId: number, childId: number): Promise { + const db = await this.dbPromise; + await db.run(`update child set lastHeard=CURRENT_TIMESTAMP where childId=${childId} and nodeId=${nodeId}`); + } + + public async getChild(nodeId: number, childId: number): Promise { + const db = await this.dbPromise; + const result = (await db.get(`select * from child where nodeId=${nodeId} and childId=${childId}`)) as ISensorData; + return result; } private async checkDb(): Promise { const db = await this.dbPromise; - const x = await db.get('select count(id) cnt from node'); + const x = await db.get('select count(nodeId) cnt from node'); if (x.cnt === 0) { for (let i = 1; i <= 254; i++) { - db.run(`insert into node (id, used) values (${i}, 0)`); + db.run(`insert into node (nodeId, used) values (${i}, 0)`); } } } diff --git a/src/lib/database.interface.ts b/src/lib/database.interface.ts index e4dfb0c..5497636 100644 --- a/src/lib/database.interface.ts +++ b/src/lib/database.interface.ts @@ -13,9 +13,11 @@ export interface INodeData { } export interface ISensorData { - id: number; nodeId: number; + childId: number; + description: string; sType: mysensor_sensor; + lastHeard: Date; } export interface IDatabase { @@ -26,4 +28,7 @@ export interface IDatabase { getFreeNodeId(): Promise; close(): Promise; setParent(node: string, last: string): Promise; + child(nodeId: number, childId: number, type: number, description: string): Promise; + childHeard(nodeId: number, childId: number): Promise; + getChild(nodeId: number, childId: number): Promise; } diff --git a/src/lib/decoder/auto-decode.ts b/src/lib/decoder/auto-decode.ts index 1055c44..8800d30 100644 --- a/src/lib/decoder/auto-decode.ts +++ b/src/lib/decoder/auto-decode.ts @@ -3,13 +3,13 @@ import { NullCheck } from '../nullcheck'; import { MysensorsMqtt } from './mysensors-mqtt'; import { MysensorsSerial } from './mysensors-serial'; -export function AutoDecode(msg: IMysensorsMsg): IMysensorsMsg { +export async function AutoDecode(msg: IMysensorsMsg): Promise { if (NullCheck.isUndefinedOrNull(msg.nodeId)) { let msgTmp: IMysensorsMsg | undefined; if (NullCheck.isUndefinedNullOrEmpty(msg.topic)) { - msgTmp = new MysensorsSerial().decode(msg); + msgTmp = await new MysensorsSerial().decode(msg); } else { - msgTmp = new MysensorsMqtt().decode(msg); + msgTmp = await new MysensorsMqtt().decode(msg); } if (NullCheck.isDefinedOrNonNull(msgTmp)) { msg = msgTmp; diff --git a/src/lib/decoder/decoder.interface.ts b/src/lib/decoder/decoder.interface.ts index 3ceb571..134ba69 100644 --- a/src/lib/decoder/decoder.interface.ts +++ b/src/lib/decoder/decoder.interface.ts @@ -1,6 +1,6 @@ import { IMysensorsMsg, INodeMessage } from '../mysensors-msg'; export interface IDecoder { - decode(msg: INodeMessage): IMysensorsMsg| undefined; + decode(msg: INodeMessage): Promise; encode(msg: IMysensorsMsg): INodeMessage| undefined; } diff --git a/src/lib/decoder/mysensors-decoder.ts b/src/lib/decoder/mysensors-decoder.ts index cdb66c5..1793783 100644 --- a/src/lib/decoder/mysensors-decoder.ts +++ b/src/lib/decoder/mysensors-decoder.ts @@ -1,3 +1,4 @@ +import { IDatabase } from 'node-red-contrib-mysensors/src/lib/database.interface'; import { IMysensorsMsg } from '../mysensors-msg'; import { mysensor_command, @@ -9,7 +10,13 @@ import { import { NullCheck } from '../nullcheck'; export abstract class MysensorsDecoder { - protected enrich(msg: IMysensorsMsg): IMysensorsMsg { + protected enrichWithDb: boolean; + + constructor(enrich?: boolean, private database?: IDatabase) { + this.enrichWithDb = enrich || false; + } + + protected async enrich(msg: IMysensorsMsg): Promise { if (NullCheck.isDefinedOrNonNull(msg.messageType)) { msg.messageTypeStr = mysensor_command[msg.messageType]; } @@ -29,6 +36,15 @@ export abstract class MysensorsDecoder { msg.subTypeStr = mysensor_stream[msg.subType]; } } + if (this.enrichWithDb && + NullCheck.isDefinedOrNonNull(msg.nodeId) && + NullCheck.isDefinedOrNonNull(msg.childSensorId) && + NullCheck.isDefinedOrNonNull(this.database)) { + const res = await this.database.getChild(msg.nodeId, msg.childSensorId); + if (NullCheck.isDefinedOrNonNull(res)) { + msg.sensorTypeStr = mysensor_sensor[res.sType]; + } + } return msg; } } diff --git a/src/lib/decoder/mysensors-mqtt.ts b/src/lib/decoder/mysensors-mqtt.ts index b2eabd9..85c8c6a 100644 --- a/src/lib/decoder/mysensors-mqtt.ts +++ b/src/lib/decoder/mysensors-mqtt.ts @@ -5,7 +5,7 @@ import { MysensorsDecoder } from './mysensors-decoder'; export class MysensorsMqtt extends MysensorsDecoder implements IDecoder { - public decode(msg: INodeMessage): IMysensorsMsg| undefined { + public async decode(msg: INodeMessage): Promise { if (NullCheck.isDefinedNonNullAndNotEmpty(msg.topic)) { const msgOut = msg as IMysensorsMsg; const split = msg.topic.toString().split('/'); @@ -17,7 +17,7 @@ export class MysensorsMqtt extends MysensorsDecoder implements IDecoder { msgOut.ack = (split[split.length - 2] === '1') ? 1 : 0; msgOut.subType = parseInt( split[split.length - 1], 10 ); msgOut.origin = MsgOrigin.mqtt; - return this.enrich(msgOut); + return await this.enrich(msgOut); } } } diff --git a/src/lib/decoder/mysensors-serial.ts b/src/lib/decoder/mysensors-serial.ts index 92e2e3c..6966f4b 100644 --- a/src/lib/decoder/mysensors-serial.ts +++ b/src/lib/decoder/mysensors-serial.ts @@ -4,7 +4,7 @@ import { IDecoder } from './decoder.interface'; import { MysensorsDecoder } from './mysensors-decoder'; export class MysensorsSerial extends MysensorsDecoder implements IDecoder { - public decode(msg: INodeMessage): IMysensorsMsg| undefined { + public async decode(msg: INodeMessage): Promise { let message = msg.payload.toString(); message = message.replace(/(\r\n|\n|\r)/gm, ''); const tokens = message.split(';'); @@ -17,7 +17,7 @@ export class MysensorsSerial extends MysensorsDecoder implements IDecoder { msgOut.subType = parseInt(tokens[4], 10); msgOut.payload = tokens[5]; msgOut.origin = MsgOrigin.serial; - return this.enrich(msgOut); + return await this.enrich(msgOut); } } diff --git a/src/lib/mysensors-controller.spec.ts b/src/lib/mysensors-controller.spec.ts new file mode 100644 index 0000000..d95f33e --- /dev/null +++ b/src/lib/mysensors-controller.spec.ts @@ -0,0 +1,62 @@ +import { expect } from 'chai'; +import { IDatabase } from 'node-red-contrib-mysensors/src/lib/database.interface'; +import * as sinon from 'sinon'; +import { DatabaseSqlite } from './database-sqlite'; +import { MysensorsController } from './mysensors-controller'; +import { IMysensorsMsg } from './mysensors-msg'; +import { mysensor_command, mysensor_internal } from './mysensors-types'; + +describe('Controller test', () => { + let db: IDatabase; + let controller: MysensorsController; + sinon.stub(DatabaseSqlite.prototype, 'getFreeNodeId').callsFake(() => '777'); + db = new DatabaseSqlite('dummy'); + controller = new MysensorsController(db, true, true, 'CET', 'M', 'mys-out'); + + it('MQTT ID Request', async () => { + const input: IMysensorsMsg = { + payload: '', + topic: 'mys-in/255/255/3/0/3', + }; + const expected: IMysensorsMsg = { + payload: '777', + subType: mysensor_internal.I_ID_RESPONSE, + topicRoot: 'mys-out', + }; + expect(await controller.messageHandler(input)) + .to.include(expected); + }); + + it('Serial config request', async () => { + const expected: IMysensorsMsg = { + payload: '255;255;3;0;6;M', + }; + const request: IMysensorsMsg = {payload: '255;255;3;0;6;0'}; + + expect(await controller.messageHandler(request)).to.include(expected); + }); + + it('Decoded time request', async () => { + const request: IMysensorsMsg = { + ack: 0, + childSensorId: 255, + messageType: mysensor_command.C_INTERNAL, + nodeId: 10, + payload: '', + subType: mysensor_internal.I_TIME, + }; + + const expected: IMysensorsMsg = { + payload: '', + subType: mysensor_internal.I_TIME, + }; + + expect(await controller.messageHandler(request)).to.include.keys(expected); + }); + + it('updates database uppon reception of package', async () => { + const spy = sinon.spy(DatabaseSqlite.prototype, 'nodeHeard'); + await controller.messageHandler({payload: '10;255;3;0;6;0'}); + expect(spy.called).to.eq(true); + }); +}); diff --git a/src/lib/mysensors-controller.ts b/src/lib/mysensors-controller.ts index c3c8b92..34af2f0 100644 --- a/src/lib/mysensors-controller.ts +++ b/src/lib/mysensors-controller.ts @@ -20,9 +20,19 @@ export class MysensorsController { ) { } public async messageHandler(msg: IMysensorsMsg): Promise { - msg = AutoDecode(msg); + msg = await AutoDecode(msg); if (NullCheck.isDefinedOrNonNull(msg.nodeId)) { await this.database.nodeHeard(msg.nodeId); + if (NullCheck.isDefinedOrNonNull(msg.childSensorId)) { + await this.database.childHeard(msg.nodeId, msg.childSensorId); + } + } + + if (msg.messageType === mysensor_command.C_PRESENTATION && + NullCheck.isDefinedOrNonNull(msg.childSensorId) && + NullCheck.isDefinedOrNonNull(msg.nodeId) && + NullCheck.isDefinedOrNonNull(msg.subType)) { + await this.database.child(msg.nodeId, msg.childSensorId, msg.subType, msg.payload); } if (msg.messageType === mysensor_command.C_INTERNAL) { diff --git a/src/lib/mysensors-msg.ts b/src/lib/mysensors-msg.ts index 0ca4197..d159345 100644 --- a/src/lib/mysensors-msg.ts +++ b/src/lib/mysensors-msg.ts @@ -9,7 +9,7 @@ export interface INodeMessage { topic?: string; } -export interface IMysensorsMsg extends INodeMessage { +export interface IMysensorsMsg extends INodeMessage { topicRoot?: string; nodeId?: number; childSensorId?: number; @@ -18,6 +18,7 @@ export interface IMysensorsMsg extends INodeMessage { ack?: 0|1; subType?: mysensor_data| mysensor_internal| mysensor_sensor| mysensor_stream; subTypeStr?: string; + sensorTypeStr?: string; origin?: MsgOrigin; } diff --git a/src/migrations/003-update-columns.sql b/src/migrations/003-update-columns.sql new file mode 100644 index 0000000..96b4684 --- /dev/null +++ b/src/migrations/003-update-columns.sql @@ -0,0 +1,34 @@ +-- UP + +ALTER TABLE node RENAME TO tmp_node; + +CREATE TABLE IF NOT EXISTS node ( + nodeId integer PRIMARY KEY AUTOINCREMENT, + label varchar, + sketchName varchar, + sketchVersion varchar, + lastHeard timestamp, + parentId integer, + lastRestart timestamp, + used boolean +); + +INSERT INTO node(nodeId, label, sketchName, sketchVersion, lastHeard, parentId, lastRestart, used) +SELECT id, label, sketchName, sketchVersion, lastHeard, parentId, lastRestart, used +FROM tmp_node; + +drop table tmp_node; + +drop table child; + +CREATE TABLE IF NOT EXISTS child ( + nodeId integer, + childId integer, + sType integer, + description varchar, + lastHeard timestamp, + PRIMARY KEY(nodeId, childId), + FOREIGN KEY(nodeId) REFERENCES node(nodeId)); + + +-- DOWN diff --git a/src/nodes/common.ts b/src/nodes/common.ts index 3c564fc..6f84e0a 100644 --- a/src/nodes/common.ts +++ b/src/nodes/common.ts @@ -15,9 +15,13 @@ export interface IEncodeProperties extends NodeProperties { /* Decode */ export interface IDecodeProperties extends NodeProperties { mqtt: boolean; + enrich: boolean; + database?: NodeId; } export interface IDecodeEncodeConf extends Node { decoder: IDecoder; + database?: IDbConfigNode; + enrich: boolean; } /* DB */ diff --git a/src/nodes/decode.html b/src/nodes/decode.html index a61a1c3..d3b50d3 100644 --- a/src/nodes/decode.html +++ b/src/nodes/decode.html @@ -3,8 +3,10 @@ category: 'mysensors', color: '#a6bbcf', defaults: { + database: {value:"", type: "mysensorsdb", required: false}, name: {value:""}, - mqtt: {value: false, required:true} + mqtt: {value: false, required:true}, + enrich: {value: false} }, inputs:1, outputs:1, @@ -15,7 +17,22 @@ return this.name||type; }, inputLabels: "MQTT topic or mysensors input", - outputLabels: ["decoded out"] + outputLabels: ["decoded out"], + oneditprepare: function() { + if ($('#node-input-enrich').is(':checked')) { + $('.databaseRow').show(); + } else { + $('.databaseRow').hide(); + } + + $('#node-input-enrich').click(function() { + if (this.checked) { + $('.databaseRow').show(); + } else { + $('.databaseRow').hide(); + } + }); + } }); @@ -28,12 +45,23 @@ +
+ + +
+
+ + +
diff --git a/src/nodes/decode.ts b/src/nodes/decode.ts index 3ad96e7..3b9cd8e 100644 --- a/src/nodes/decode.ts +++ b/src/nodes/decode.ts @@ -1,20 +1,33 @@ -import { NodeProperties, Red } from 'node-red'; +import { Red } from 'node-red'; import { MysensorsMqtt } from '../lib/decoder/mysensors-mqtt'; import { MysensorsSerial } from '../lib/decoder/mysensors-serial'; import { IMysensorsMsg } from '../lib/mysensors-msg'; -import { IDecodeEncodeConf, IDecodeProperties } from './common'; +import { NullCheck } from '../lib/nullcheck'; +import { IDbConfigNode, IDecodeEncodeConf, IDecodeProperties } from './common'; export = (RED: Red) => { - RED.nodes.registerType('mysdecode', function(this: IDecodeEncodeConf, props: NodeProperties) { + RED.nodes.registerType('mysdecode', function(this: IDecodeEncodeConf, props: IDecodeProperties) { const config = props as IDecodeProperties; + if (NullCheck.isDefinedNonNullAndNotEmpty(props.database)) { + this.database = RED.nodes.getNode(props.database) as IDbConfigNode; + } + this.enrich = props.enrich; if (config.mqtt) { - this.decoder = new MysensorsMqtt(); + if (this.enrich && NullCheck.isDefinedOrNonNull(this.database)) { + this.decoder = new MysensorsMqtt(props.enrich, this.database.database); + } else { + this.decoder = new MysensorsMqtt(); + } } else { - this.decoder = new MysensorsSerial(); + if (this.enrich && NullCheck.isDefinedOrNonNull(this.database)) { + this.decoder = new MysensorsSerial(props.enrich, this.database.database); + } else { + this.decoder = new MysensorsSerial(); + } } RED.nodes.createNode(this, config); - this.on('input', (msg: IMysensorsMsg) => { - this.send(this.decoder.decode(msg)); + this.on('input', async (msg: IMysensorsMsg) => { + this.send(await this.decoder.decode(msg)); }); }); }; diff --git a/src/nodes/mysdebug.ts b/src/nodes/mysdebug.ts index 0da4aed..b4b35ae 100644 --- a/src/nodes/mysdebug.ts +++ b/src/nodes/mysdebug.ts @@ -17,8 +17,8 @@ export = (RED: Red) => { RED.nodes.createNode(this, config); this.mysDbg = new MysensorsDebugDecode(); - this.on('input', (msg: IMysensorsMsg) => { - msg = AutoDecode(msg); + this.on('input', async (msg: IMysensorsMsg) => { + msg = await AutoDecode(msg); if (NullCheck.isDefinedOrNonNull(msg.nodeId)) { let msgHeader = ''; let msgSubType: string | null = null; diff --git a/src/nodes/mysensors-db.ts b/src/nodes/mysensors-db.ts index 3697e9a..f455c40 100644 --- a/src/nodes/mysensors-db.ts +++ b/src/nodes/mysensors-db.ts @@ -1,6 +1,6 @@ import { NodeProperties, Red } from 'node-red'; import { resolve } from 'path'; -import { Database } from '../lib/database-sqlite'; +import { DatabaseSqlite } from '../lib/database-sqlite'; import { IDbConfigNode, IDBProperties } from './common'; export = (RED: Red) => { @@ -10,7 +10,7 @@ export = (RED: Red) => { this.file = config.file; try { if (this.file) { - this.database = new Database(this.file); + this.database = new DatabaseSqlite(this.file); } } catch (error) { if (error.code === 'SQLITE_CANTOPEN') {