From 5983bec7522ed083f82ca995b80eb5153eaf4b20 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 22 Feb 2021 14:22:45 +0100 Subject: [PATCH 1/8] feat: plugins improved support With this PR users will have more power when developing custom plugins BREAKING CHANGE: `plugins` are now stored in an array of `strings` on settings `gateway` prop instead of `zwave` --- app.js | 35 +++++++++++++++++++++++++++++++++++ lib/ZwaveClient.js | 9 --------- src/components/Settings.vue | 12 ++++++++++++ src/store/mutations.js | 2 ++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index 675bcc53b96..a944421f023 100644 --- a/app.js +++ b/app.js @@ -89,6 +89,7 @@ socketManager.authMiddleware = function (socket, next) { } let gw // the gateway instance +const plugins = [] // flag used to prevent multiple restarts while one is already in progress let restarting = false @@ -249,9 +250,41 @@ function startGateway (settings) { gw.start() + const pluginsConfig = settings.gateway ? settings.gateway.plugins : null + + // load custom plugins + if (pluginsConfig && Array.isArray(pluginsConfig)) { + for (const plugin of pluginsConfig) { + try { + const pluginName = path.basename(plugin) + const instance = require(plugin)({ + zwave, + mqtt, + app, + logger: loggers.module(pluginName) + }) + instance.name = pluginName + plugins.push(instance) + logger.info(`Successfully loaded plugin ${instance.name}`) + } catch (error) { + logger.error(`Error while loading ${plugin} plugin`, error) + } + } + } + restarting = false } +async function destroyPlugins () { + while (plugins.length > 0) { + const instance = plugins.pop() + if (instance && typeof instance.destroy === 'function') { + logger.info('Closing plugin ' + instance.name) + await instance.destroy() + } + } +} + function setupInterceptor () { // intercept logs and redirect them to socket const interceptor = function (write) { @@ -806,6 +839,7 @@ app.post('/api/settings', apisLimiter, isAuthenticated, async function ( restarting = true await jsonStore.put(store.settings, settings) await gw.close() + await destroyPlugins() // reload loggers settings setupLogging(settings) // restart clients and gateway @@ -849,6 +883,7 @@ async function gracefuShutdown () { logger.warn('Shutdown detected: closing clients...') try { await gw.close() + await destroyPlugins() } catch (error) { logger.error('Error while closing clients', error) } diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index feafc16e8db..d15092019d9 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -114,15 +114,6 @@ function ZwaveClient (config, socket) { this.healTimeout = null this.status = ZWAVE_STATUS.closed - - // load custom plugins - if (config.plugin) { - try { - require(config.plugin)(this) - } catch (error) { - logger.error(`Error while loading ${config.plugin} plugin`, error.message) - } - } } inherits(ZwaveClient, EventEmitter) diff --git a/src/components/Settings.vue b/src/components/Settings.vue index b80914edb8a..88e7e86fed9 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -25,6 +25,18 @@ v-model="newGateway.authEnabled" > + + + Date: Mon, 22 Feb 2021 14:44:55 +0100 Subject: [PATCH 2/8] docs: plugins --- docs/_sidebar.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 5956a273995..c3e7c556b3c 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -20,6 +20,7 @@ - [Z2M Migration](guide/migrating.md) - [Env Vars](guide/env-vars.md) - [FAQ](guide/faq.md) + - [Plugins](guide/plugins.md) - Development From 40916194595363aad7c646d2de00c5d68de6022e Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 22 Feb 2021 14:45:13 +0100 Subject: [PATCH 3/8] docs: plugins --- docs/guide/plugins.md | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/guide/plugins.md diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md new file mode 100644 index 00000000000..ebbce60652f --- /dev/null +++ b/docs/guide/plugins.md @@ -0,0 +1,52 @@ +# Plugins + +Plugins are nodejs packages that can be initegrated in zwavejsmqtt in order to add new awesome features. They have access to all the clients (zwave and mqtt) and express instance. + +## Usage + +A plugin is imported in zwavejs1mqtt using `require(pluginName)(contex)` where context provides access to this elements: + +- `zwave`: Zwave client +- `mqtt`: Mqtt client +- `app`: Express instance +- `logger`: A logger instance to log things in console/file based on logger general settings + +In order to add a plugin you have to specify the absolute/relative path to it or, if it is available as an npm package, you can install it using the command: + +```bash +npm i my-awesome-plugin +``` + +## Plugins with docker + +Building the container is straight forward. Here an example of build command installing plugin `my-awesome-plugin` + +```bash +docker build -f docker/Dockerfile --build-arg plugins='my-awesome-plugin' -t : . +``` + +## Developing custom Plugins + +In order to implement a plugin you need to create a class with a constructor that accepts a single parameter that is the context we spoke in [usage](#usage) section and a `destroy` function that will be called when application is closed or settings updated. + +Here is a minimal example of a custom plugin: + +```js +function MyPlugin(ctx) { + this.zwave = ctx.zwave; + this.mqtt = ctx.mqtt; + this.logger = ctx.logger; + this.express = ctx.app; + + // this.express.get('/my-route', function(req, res) {...}) + // this.mqtt.publish(...) + // this.zwave.on('valueChanged', onValueChanged) + // ... add all the stuff you need here +} + +MyPlugin.prototype.destroy = async function() { + // clean up the state +}; + +module.export = MyPlugin; +``` From 9ce727ffac13574efee6aae1008f01b3a22f0b2d Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 22 Feb 2021 14:47:06 +0100 Subject: [PATCH 4/8] fix: lint issues --- docs/guide/plugins.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md index ebbce60652f..4580e7ceb86 100644 --- a/docs/guide/plugins.md +++ b/docs/guide/plugins.md @@ -32,11 +32,11 @@ In order to implement a plugin you need to create a class with a constructor tha Here is a minimal example of a custom plugin: ```js -function MyPlugin(ctx) { - this.zwave = ctx.zwave; - this.mqtt = ctx.mqtt; - this.logger = ctx.logger; - this.express = ctx.app; +function MyPlugin (ctx) { + this.zwave = ctx.zwave + this.mqtt = ctx.mqtt + this.logger = ctx.logger + this.express = ctx.app // this.express.get('/my-route', function(req, res) {...}) // this.mqtt.publish(...) @@ -44,9 +44,9 @@ function MyPlugin(ctx) { // ... add all the stuff you need here } -MyPlugin.prototype.destroy = async function() { +MyPlugin.prototype.destroy = async function () { // clean up the state -}; +} -module.export = MyPlugin; +module.export = MyPlugin ``` From 4cba34df108e9a45c79c8e59e3d4f53657443c47 Mon Sep 17 00:00:00 2001 From: "V. Aretakis" Date: Mon, 22 Feb 2021 15:21:22 +0100 Subject: [PATCH 5/8] Add docker file support for plugin installation --- docker/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6a1c2991924..cdfae00eb39 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,6 +14,8 @@ COPY package.json package-lock.json ./ RUN npm config set unsafe-perm true RUN npm install --no-optional +# add plugin support, space separated +ARG plugins # when update devices arg is set update config files from zwavejs repo ARG updateDevices ARG zwavejs=https://github.com/zwave-js/node-zwave-js/archive/master.tar.gz @@ -37,11 +39,14 @@ RUN npm run build && \ static \ stylesheets +RUN if [ ! -z "$plugins" ]; \ + then echo "Installing plugins ${plugins}"; npm i ${plugins} ; fi + # STEP: 2 (runtime) FROM node:15.9.0-alpine RUN apk add --no-cache \ - libstdc++ \ + libstdc++ \ openssl \ libgcc \ libusb \ From 487959e43ed0a47284716c82cabb4791f2766d88 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 23 Feb 2021 15:19:08 +0100 Subject: [PATCH 6/8] docs: update docs/guide/plugins.md Co-authored-by: Chris Nesbitt-Smith --- docs/guide/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md index 4580e7ceb86..e2e23ad2890 100644 --- a/docs/guide/plugins.md +++ b/docs/guide/plugins.md @@ -1,6 +1,6 @@ # Plugins -Plugins are nodejs packages that can be initegrated in zwavejsmqtt in order to add new awesome features. They have access to all the clients (zwave and mqtt) and express instance. +Plugins are nodejs packages that can be integrated in zwavejsmqtt in order to add new awesome features. They have access to all the clients (zwave and mqtt) and express instance. ## Usage From b0d3f6f3c8a20d8785ca2f83c0af306358683521 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 23 Feb 2021 15:19:19 +0100 Subject: [PATCH 7/8] docs: update docs/guide/plugins.md Co-authored-by: Chris Nesbitt-Smith --- docs/guide/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md index e2e23ad2890..1eb3d795bdd 100644 --- a/docs/guide/plugins.md +++ b/docs/guide/plugins.md @@ -4,7 +4,7 @@ Plugins are nodejs packages that can be integrated in zwavejsmqtt in order to ad ## Usage -A plugin is imported in zwavejs1mqtt using `require(pluginName)(contex)` where context provides access to this elements: +A plugin is imported in zwavejs1mqtt using `require(pluginName)(contex)` where context provides access to these elements: - `zwave`: Zwave client - `mqtt`: Mqtt client From 7777d77b9bc507f4cd3775344a8a1165bb49e0de Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 23 Feb 2021 15:20:33 +0100 Subject: [PATCH 8/8] docs: update docs/guide/plugins.md --- docs/guide/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md index 1eb3d795bdd..1e6d561c173 100644 --- a/docs/guide/plugins.md +++ b/docs/guide/plugins.md @@ -4,7 +4,7 @@ Plugins are nodejs packages that can be integrated in zwavejsmqtt in order to ad ## Usage -A plugin is imported in zwavejs1mqtt using `require(pluginName)(contex)` where context provides access to these elements: +A plugin is imported in zwavejs1mqtt using `require(pluginName)(context)` where context provides access to these elements: - `zwave`: Zwave client - `mqtt`: Mqtt client