Skip to content

Commit

Permalink
fix: start/restart management #27
Browse files Browse the repository at this point in the history
  • Loading branch information
robertsLando committed Nov 27, 2020
1 parent 0f1a97e commit 5275ca2
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 82 deletions.
3 changes: 1 addition & 2 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/build/
/config/
/dist/
/*.js
/dist/
107 changes: 63 additions & 44 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
var express = require('express')
var reqlib = require('app-root-path').require
var logger = require('morgan')
var cookieParser = require('cookie-parser')
var bodyParser = require('body-parser')
var app = express()
var SerialPort = require('serialport')
var jsonStore = reqlib('/lib/jsonStore.js')
var cors = require('cors')
var ZWaveClient = reqlib('/lib/ZwaveClient')
var MqttClient = reqlib('/lib/MqttClient')
var Gateway = reqlib('/lib/Gateway')
var store = reqlib('config/store.js')
var debug = reqlib('/lib/debug')('App')
var history = require('connect-history-api-fallback')
var utils = reqlib('/lib/utils.js')
const express = require('express')
const reqlib = require('app-root-path').require
const logger = require('morgan')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const app = express()
const SerialPort = require('serialport')
const jsonStore = reqlib('/lib/jsonStore.js')
const cors = require('cors')
const ZWaveClient = reqlib('/lib/ZwaveClient')
const MqttClient = reqlib('/lib/MqttClient')
const Gateway = reqlib('/lib/Gateway')
const store = reqlib('config/store.js')
const debug = reqlib('/lib/debug')('App')
const history = require('connect-history-api-fallback')
const utils = reqlib('/lib/utils.js')
const renderIndex = reqlib('/lib/renderIndex')
var gw // the gateway instance

let gw // the gateway instance
let io

let restarting = false

debug('zwavejs2mqtt version: ' + require('./package.json').version)
debug('Application path:' + utils.getPath(true))

Expand All @@ -44,10 +47,15 @@ app.use(cors())

app.use(history())

function hasProperty (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop)
}

function startGateway () {
var settings = jsonStore.get(store.settings)
const settings = jsonStore.get(store.settings)

var mqtt, zwave
let mqtt
let zwave

if (settings.mqtt) {
mqtt = new MqttClient(settings.mqtt)
Expand All @@ -58,6 +66,10 @@ function startGateway () {
}

gw = new Gateway(settings.gateway, zwave, mqtt)

gw.start()

restarting = false
}

app.startSocket = function (server) {
Expand All @@ -82,7 +94,7 @@ app.startSocket = function (server) {
socket.on('ZWAVE_API', async function (data) {
debug('Zwave api call:', data.api, data.args)
if (gw.zwave) {
var result = await gw.zwave.callApi(data.api, ...data.args)
const result = await gw.zwave.callApi(data.api, ...data.args)
result.api = data.api
socket.emit(gw.zwave.socketEvents.api, result)
}
Expand Down Expand Up @@ -133,22 +145,22 @@ app.startSocket = function (server) {
// ----- APIs ------

app.get('/health', async function (req, res) {
var mqtt = false
var zwave = false
let mqtt = false
let zwave = false

if (gw) {
mqtt = gw.mqtt ? gw.mqtt.getStatus().status : false
zwave = gw.zwave ? gw.zwave.getStatus().status : false
}

var status = mqtt && zwave
const status = mqtt && zwave

res.status(status ? 200 : 500).send(status ? 'Ok' : 'Error')
})

app.get('/health/:client', async function (req, res) {
var client = req.params.client
var status
const client = req.params.client
let status

if (client !== 'zwave' && client !== 'mqtt') {
res.status(500).send("Requested client doesn 't exist")
Expand All @@ -161,15 +173,17 @@ app.get('/health/:client', async function (req, res) {

// get settings
app.get('/api/settings', async function (req, res) {
var data = {
const data = {
success: true,
settings: jsonStore.get(store.settings),
devices: gw.zwave ? gw.zwave.devices : {},
serial_ports: []
}

let ports
if (process.platform !== 'sunos') {
try {
var ports = await SerialPort.list()
ports = await SerialPort.list()
} catch (error) {
debug(error)
}
Expand All @@ -190,21 +204,22 @@ app.get('/api/exportConfig', function (req, res) {

// import config
app.post('/api/importConfig', async function (req, res) {
var config = req.body.data
const config = req.body.data
try {
if (!gw.zwave) throw Error('Zwave client not inited')

if (!Array.isArray(config)) throw Error('Configuration not valid')
else {
for (let i = 0; i < config.length; i++) {
const e = config[i]
if (e && (!e.hasOwnProperty('name') || !e.hasOwnProperty('loc'))) {
if (e && (!hasProperty(e, 'name') || !hasProperty(e, 'loc'))) {
throw Error('Configuration not valid')
} else if (e) {
await gw.zwave.callApi('_setNodeName', i, e.name || '')
await gw.zwave.callApi('_setNodeLocation', i, e.loc || '')
if (e.hassDevices)
if (e.hassDevices) {
await gw.zwave.storeDevices(e.hassDevices, i, false)
}
}
}
}
Expand All @@ -217,29 +232,33 @@ app.post('/api/importConfig', async function (req, res) {
})

// update settings
app.post('/api/settings', function (req, res) {
jsonStore
.put(store.settings, req.body)
.then(data => {
res.json({ success: true, message: 'Configuration updated successfully' })
return gw.close()
})
.then(() => startGateway())
.catch(err => {
debug(err)
res.json({ success: false, message: err.message })
})
app.post('/api/settings', async function (req, res) {
try {
if (restarting) {
throw Error(
'Gateway is restarting, wait a moment before doing another request'
)
}
restarting = true
await jsonStore.put(store.settings, req.body)
await gw.close()
startGateway()
res.json({ success: true, message: 'Configuration updated successfully' })
} catch (error) {
debug(error)
res.json({ success: false, message: error.message })
}
})

// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('Not Found')
const err = new Error('Not Found')
err.status = 404
next(err)
})

// error handler
app.use(function (err, req, res, next) {
app.use(function (err, req, res) {
// set locals, only providing error in development
res.locals.message = err.message
res.locals.error = req.app.get('env') === 'development' ? err : {}
Expand Down
50 changes: 25 additions & 25 deletions lib/Gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,19 @@ function Gateway (config, zwave, mqtt) {
if (!(this instanceof Gateway)) {
return new Gateway(config)
}
this.config = config || { type: 1 }
// clients
this.mqtt = mqtt
this.zwave = zwave
EventEmitter.call(this)
init.call(this, config, zwave, mqtt)
}

inherits(Gateway, EventEmitter)

function init (config, zwave, mqtt) {
Gateway.prototype.start = async function () {
// gateway configuration
this.config = config || { type: 1 }
this.config.values = this.config.values || []

// clients
this.mqtt = mqtt
this.zwave = zwave
this.config.values = this.config.values || []

// Object where keys are topic and values can be both zwave valueId object
// or a valueConf if the topic is a broadcast topic
Expand All @@ -138,27 +137,28 @@ function init (config, zwave, mqtt) {
// topic levels for subscribes using wildecards
this.topicLevels = []

if (mqtt) {
mqtt.on('writeRequest', onWriteRequest.bind(this))
mqtt.on('broadcastRequest', onBroadRequest.bind(this))
mqtt.on('apiCall', onApiRequest.bind(this))
mqtt.on('hassStatus', onHassStatus.bind(this))
mqtt.on('brokerStatus', onBrokerStatus.bind(this))
if (this.mqtt) {
this.mqtt.on('writeRequest', onWriteRequest.bind(this))
this.mqtt.on('broadcastRequest', onBroadRequest.bind(this))
this.mqtt.on('apiCall', onApiRequest.bind(this))
this.mqtt.on('hassStatus', onHassStatus.bind(this))
this.mqtt.on('brokerStatus', onBrokerStatus.bind(this))
}

if (zwave) {
zwave.on('valueChanged', onValueChanged.bind(this))
zwave.on('nodeStatus', onNodeStatus.bind(this))
zwave.on('notification', onNotification.bind(this))
zwave.on('scanComplete', onScanComplete.bind(this))
zwave.on('nodeSceneEvent', onNodeSceneEvent.bind(this))
zwave.on('nodeRemoved', onNodeRemoved.bind(this))

if (config.sendEvents) {
zwave.on('event', onEvent.bind(this))
if (this.zwave) {
this.zwave.on('valueChanged', onValueChanged.bind(this))
this.zwave.on('nodeStatus', onNodeStatus.bind(this))
this.zwave.on('notification', onNotification.bind(this))
this.zwave.on('scanComplete', onScanComplete.bind(this))
this.zwave.on('nodeSceneEvent', onNodeSceneEvent.bind(this))
this.zwave.on('nodeRemoved', onNodeRemoved.bind(this))

if (this.config.sendEvents) {
this.zwave.on('event', onEvent.bind(this))
}

zwave.connect()
// this is async but doesn't need to be awaited
this.zwave.connect()
} else {
debug('Zwave settings are not valid')
}
Expand Down Expand Up @@ -691,7 +691,7 @@ Gateway.prototype.close = async function () {
}

if (this.zwave) {
this.zwave.close()
await this.zwave.close()
}
}

Expand Down
27 changes: 16 additions & 11 deletions lib/ZwaveClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,26 @@ function ZwaveClient (config, socket) {
if (!(this instanceof ZwaveClient)) {
return new ZwaveClient(config)
}
EventEmitter.call(this)
init.call(this, config, socket)
}

inherits(ZwaveClient, EventEmitter)
EventEmitter.call(this)

function init (cfg, socket) {
this.cfg = cfg
this.cfg = config
this.socket = socket

this.closed = false
this.driverReady = false
this.scenes = jsonStore.get(store.scenes)

cfg.networkKey = cfg.networkKey || process.env.OZW_NETWORK_KEY
config.networkKey = config.networkKey || process.env.OZW_NETWORK_KEY

// TODO: Handle this by using `logOptions` (PR waiting to be merged)
if (process.env.LOGLEVEL === 'undefined') process.env.LOGLEVEL = null

// https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/log/shared.ts#L13
// https://github.com/winstonjs/triple-beam/blob/master/config/npm.js#L14-L15
process.env.LOGLEVEL = process.env.LOGLEVEL || cfg.logLevel
process.env.LOGLEVEL = process.env.LOGLEVEL || config.logLevel

if (process.env.LOGTOFILE || cfg.logToFile) {
if (process.env.LOGTOFILE || config.logToFile) {
process.env.LOGTOFILE = 'true'
}

Expand All @@ -86,6 +83,8 @@ function init (cfg, socket) {
this.status = ZWAVE_STATUS.closed
}

inherits(ZwaveClient, EventEmitter)

// ---------- DRIVER EVENTS -------------------------------------

function driverReady () {
Expand Down Expand Up @@ -989,6 +988,7 @@ ZwaveClient.prototype.storeDevices = async function (devices, nodeId, remove) {
*/
ZwaveClient.prototype.close = async function () {
this.status = ZWAVE_STATUS.closed
this.closed = true

if (this.commandsTimeout) {
clearTimeout(this.commandsTimeout)
Expand All @@ -1000,8 +1000,6 @@ ZwaveClient.prototype.close = async function () {
this.reconnectTimeout = null
}

this.closed = true

if (this.healTimeout) {
clearTimeout(this.healTimeout)
this.healTimeout = null
Expand Down Expand Up @@ -1250,6 +1248,9 @@ ZwaveClient.prototype.refreshNeighbors = function () {
*/
ZwaveClient.prototype.connect = async function () {
if (!this.driverReady) {
// this could happen when the driver fails the connect and a reconnect timeout triggers
if (this.closed) return

let networkKey

if (this.cfg.networkKey && this.cfg.networkKey.length === 32) {
Expand All @@ -1274,6 +1275,10 @@ ZwaveClient.prototype.connect = async function () {
this.status = ZWAVE_STATUS.connected
this.connected = true
} catch (error) {
// destroy diver instance when it fails
this.driver.destroy().catch(err => {
debug('Error while destroing driver ' + err.message)
})
driverError.call(this, error)
debug('Retry connection in 3 seconds...')
this.reconnectTimeout = setTimeout(this.connect.bind(this), 3000)
Expand Down

0 comments on commit 5275ca2

Please sign in to comment.