Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugins improved support #688

Merged
merged 12 commits into from
Feb 24, 2021
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 @@ -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
Expand Down Expand Up @@ -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)
}
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 initegrated in zwavejsmqtt in order to add new awesome features. They have access to all the clients (zwave and mqtt) and express instance.
robertsLando marked this conversation as resolved.
Show resolved Hide resolved

## Usage

A plugin is imported in zwavejs1mqtt using `require(pluginName)(contex)` where context provides access to this elements:
robertsLando marked this conversation as resolved.
Show resolved Hide resolved
robertsLando marked this conversation as resolved.
Show resolved Hide resolved

- `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 @@ -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)
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