Skip to content

Commit

Permalink
feat: plugins improved support (#688)
Browse files Browse the repository at this point in the history
With this PR users will have more power when developing custom plugins. More info in [docs](https://zwave-js.github.io/zwavejs2mqtt/#/guide/plugins)

BREAKING CHANGE: `plugins` are now stored in an array of `strings` on settings `gateway` prop instead of `zwave`
  • Loading branch information
robertsLando authored Feb 24, 2021
1 parent 5a43abd commit a213b25
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 10 deletions.
35 changes: 35 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -868,6 +901,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
Expand Down Expand Up @@ -911,6 +945,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)
}
Expand Down
7 changes: 6 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.10.0-alpine

RUN apk add --no-cache \
libstdc++ \
libstdc++ \
openssl \
libgcc \
libusb \
Expand Down
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [Z2M Migration](guide/migrating.md)
- [Env Vars](guide/env-vars.md)
- [FAQ](guide/faq.md)
- [Plugins](guide/plugins.md)

- Development

Expand Down
52 changes: 52 additions & 0 deletions docs/guide/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Plugins

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

A plugin is imported in zwavejs1mqtt using `require(pluginName)(context)` where context provides access to these 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 <docker image name>:<tag> .
```

## 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
```
9 changes: 0 additions & 9 deletions lib/ZwaveClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,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)
Expand Down
12 changes: 12 additions & 0 deletions src/components/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@
v-model="newGateway.authEnabled"
></v-switch>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-combobox
hint="You can select a plugin from the list or write the path to your custom plugin and press enter"
persistent-hint
label="Plugins"
:items="['@varet/zj2m-prom-exporter']"
multiple
chips
deletable-chips
v-model="newGateway.plugins"
></v-combobox>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-switch
hint="Enable logging"
Expand Down
2 changes: 2 additions & 0 deletions src/store/mutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const state = {
devices: [],
gateway: {
type: 0,
plugins: [],
authEnabled: false,
payloadType: 0,
nodeNames: true,
hassDiscovery: true,
Expand Down

0 comments on commit a213b25

Please sign in to comment.