diff --git a/app.js b/app.js index 0b1cb62f17..a3bc25f933 100644 --- a/app.js +++ b/app.js @@ -19,10 +19,9 @@ const { inboundEvents, socketEvents } = reqlib('/lib/SocketManager.js') const utils = reqlib('/lib/utils.js') const fs = require('fs-extra') const path = require('path') -const appConfig = reqlib('config/app.js') +const { storeDir } = reqlib('config/app.js') const renderIndex = reqlib('/lib/renderIndex') const archiver = require('archiver') -const storeDir = utils.joinPath(true, appConfig.storeDir) const socketManager = new SocketManager() diff --git a/config/app.js b/config/app.js index 9a0e84a5b9..1731fd21af 100644 --- a/config/app.js +++ b/config/app.js @@ -1,7 +1,8 @@ +const { joinPath } = require('../lib/utils') // config/app.js module.exports = { title: 'ZWave To MQTT', - storeDir: 'store', + storeDir: process.env.STORE_DIR || joinPath(true, 'store'), base: '/', port: 8091 } diff --git a/docs/guide/env-vars.md b/docs/guide/env-vars.md index 7bde967378..fca233431c 100644 --- a/docs/guide/env-vars.md +++ b/docs/guide/env-vars.md @@ -1,6 +1,8 @@ # Environment variables -> [!NOTE] -> Each one of the following environment variables corresponds to their respective options in the UI settings and options saved in the UI take presence over these environment variables. +This is the list of the actually supported env vars: -- `OZW_NETWORK_KEY` +- `NETWORK_KEY`: Zwave Network key +- `PORT`: The port to listent to for incoming requests. Default is `8091` +- `HOST`: The host address to bind to. Default is `0.0.0.0` +- `STORE_DIR`: The absolute path to the directory where all files will be stored. Default is `/store` diff --git a/docs/guide/mqtt.md b/docs/guide/mqtt.md index 96bfa5372f..2139bac34e 100644 --- a/docs/guide/mqtt.md +++ b/docs/guide/mqtt.md @@ -71,9 +71,9 @@ This are the available apis: - `name`: homeId Hex - `version`: zwave-js version - `uptime`: Seconds from when the app process is started. It's the result of `process.uptime()` - - `lastUpdate`: Timestamp of latest event received from OZW + - `lastUpdate`: Timestamp of latest event received - `status`: Client status. Could be: 'driverReady', 'connected', 'scanDone', 'driverFailed', 'closed' - - `cntStatus`: Controller status received from ozw notifications controller command. If inclusion/exclusion is running it would be `Waiting` + - `cntStatus`: The controller status - `getAssociations(nodeId, groupId)`: get an array of current [associations](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=association-interface) of a specific group - `addAssociations(nodeId, groupId, associations)`: add a node to the array of specified [associations](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=association-interface) - `removeAssociations(nodeId, groupId, associations[])`: the opposite of add associations diff --git a/docs/usage/reverse-proxy.md b/docs/usage/reverse-proxy.md index e772b1f01f..f4951bac8c 100644 --- a/docs/usage/reverse-proxy.md +++ b/docs/usage/reverse-proxy.md @@ -54,7 +54,7 @@ would look like: ```javascript module.exports = { title: 'ZWave to MQTT', - storeDir: 'store', + storeDir: process.env.STORE_DIR || joinPath(true, 'store'), base: '/zwave/', port: 8091 } diff --git a/lib/Gateway.js b/lib/Gateway.js index e47f6f2fe4..85470a5cb4 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -18,12 +18,13 @@ const inherits = require('util').inherits const hassCfg = reqlib('/hass/configurations.js') const hassDevices = reqlib('/hass/devices.js') const version = reqlib('package.json').version +const { storeDir } = reqlib('config/app.js') const NODE_PREFIX = 'nodeID_' // const GW_TYPES = ['valueID', 'named', 'manual'] // const PY_TYPES = ['time_value', 'zwave_value', 'just_value'] -const CUSTOM_DEVICES = reqlib('config/app.js').storeDir + '/customDevices' +const CUSTOM_DEVICES = storeDir + '/customDevices' let allDevices = hassDevices // will contain customDevices + hassDevices // watcher initiates a watch on a file. if this fails (e.g., because the file @@ -57,8 +58,8 @@ const watch = (filename, fn) => { } } -const customDevicesJsPath = utils.joinPath(true, CUSTOM_DEVICES) + '.js' -const customDevicesJsonPath = utils.joinPath(true, CUSTOM_DEVICES) + '.json' +const customDevicesJsPath = CUSTOM_DEVICES + '.js' +const customDevicesJsonPath = CUSTOM_DEVICES + '.json' let lastCustomDevicesLoad = null // loadCustomDevices attempts to load a custom devices file, preferring `.js` diff --git a/lib/MqttClient.js b/lib/MqttClient.js index d279c6308a..755f65d9d2 100644 --- a/lib/MqttClient.js +++ b/lib/MqttClient.js @@ -1,15 +1,15 @@ 'use strict' // eslint-disable-next-line one-var -const reqlib = require('app-root-path').require, - mqtt = require('mqtt'), - utils = reqlib('/lib/utils.js'), - NeDBStore = require('mqtt-nedb-store'), - EventEmitter = require('events'), - storeDir = reqlib('config/app.js').storeDir, - logger = reqlib('/lib/logger.js').module('Mqtt'), - url = require('native-url'), - inherits = require('util').inherits +const reqlib = require('app-root-path').require +const mqtt = require('mqtt') +const { joinPath, sanitizeTopic } = reqlib('/lib/utils.js') +const NeDBStore = require('mqtt-nedb-store') +const EventEmitter = require('events') +const { storeDir } = reqlib('config/app.js') +const logger = reqlib('/lib/logger.js').module('Mqtt') +const url = require('native-url') +const { inherits } = require('util') const appVersion = reqlib('package.json').version @@ -49,7 +49,7 @@ function init (config) { return } - this.clientID = utils.sanitizeTopic(NAME_PREFIX + config.name) + this.clientID = sanitizeTopic(NAME_PREFIX + config.name) const parsed = url.parse(config.host || '') let protocol = 'mqtt' @@ -82,7 +82,7 @@ function init (config) { if (config.store) { const COMPACT = { autocompactionInterval: 30000 } - const manager = NeDBStore(utils.joinPath(true, storeDir, 'mqtt'), { + const manager = NeDBStore(joinPath(storeDir, 'mqtt'), { incoming: COMPACT, outgoing: COMPACT }) diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index b1c7e9c39e..d7fc1d15b5 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -17,7 +17,7 @@ const EventEmitter = require('events') const jsonStore = reqlib('/lib/jsonStore.js') const { socketEvents } = reqlib('/lib/SocketManager.js') const store = reqlib('config/store.js') -const storeDir = utils.joinPath(true, reqlib('config/app.js').storeDir) +const { storeDir } = reqlib('config/app.js') const LogManager = require('./logger.js') const logger = LogManager.module('Zwave') const inherits = require('util').inherits @@ -59,7 +59,7 @@ function ZwaveClient (config, socket) { this.driverReady = false this.scenes = jsonStore.get(store.scenes) - config.networkKey = config.networkKey || process.env.OZW_NETWORK_KEY + config.networkKey = config.networkKey || process.env.NETWORK_KEY this.nodes = [] this.storeNodes = jsonStore.get(store.nodes) diff --git a/lib/jsonStore.js b/lib/jsonStore.js index bdee5a597f..0ba69595fb 100644 --- a/lib/jsonStore.js +++ b/lib/jsonStore.js @@ -1,16 +1,16 @@ 'use strict' // eslint-disable-next-line one-var -const jsonfile = require('jsonfile'), - reqlib = require('app-root-path').require, - storeDir = reqlib('config/app.js').storeDir, - Promise = require('bluebird'), - logger = reqlib('/lib/logger.js').module('Store'), - utils = reqlib('lib/utils.js') +const jsonfile = require('jsonfile') +const reqlib = require('app-root-path').require +const { storeDir } = reqlib('config/app.js') +const Promise = require('bluebird') +const logger = reqlib('/lib/logger.js').module('Store') +const utils = reqlib('lib/utils.js') function getFile (config) { return new Promise((resolve, reject) => { - jsonfile.readFile(utils.joinPath(true, storeDir, config.file), function ( + jsonfile.readFile(utils.joinPath(storeDir, config.file), function ( err, data ) { @@ -62,18 +62,16 @@ StorageHelper.prototype.get = function (model) { StorageHelper.prototype.put = function (model, data) { return new Promise((resolve, reject) => { - jsonfile.writeFile( - utils.joinPath(true, storeDir, model.file), - data, - function (err) { - if (err) { - reject(err) - } else { - storage_helper.store[model.file] = data - resolve(storage_helper.store[model.file]) - } + jsonfile.writeFile(utils.joinPath(storeDir, model.file), data, function ( + err + ) { + if (err) { + reject(err) + } else { + storage_helper.store[model.file] = data + resolve(storage_helper.store[model.file]) } - ) + }) }) } diff --git a/lib/logger.js b/lib/logger.js index 2cd16322a0..d26da7ea8d 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,7 +1,7 @@ const winston = require('winston') const { format, transports, addColors } = winston const { combine, timestamp, label, printf, colorize, splat } = format -const utils = require('./utils') +const { joinPath } = require('./utils') const { storeDir } = require('../config/app.js') const colorizer = colorize() @@ -16,11 +16,7 @@ const defaultLogFile = 'zwavejs2mqtt.log' */ const sanitizedConfig = (module, config) => { config = config || {} - const filePath = utils.joinPath( - true, - storeDir, - config.logFileName || defaultLogFile - ) + const filePath = joinPath(storeDir, config.logFileName || defaultLogFile) return { module: module || '-', diff --git a/lib/utils.js b/lib/utils.js index 54f670cabb..ec4dd3bd06 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,79 +5,155 @@ const { version } = require('../package.json') let VERSION -module.exports = { - getPath (write) { - if (write && process.pkg) return process.cwd() - else return appRoot.toString() - }, - joinPath (...paths) { - if (paths.length > 0 && typeof paths[0] === 'boolean') { - paths[0] = this.getPath(paths[0]) - } - return path.join(...paths) - }, - joinProps (...props) { - props = props || [] - let ret = props[0] || '' - for (let i = 1; i < props.length; i++) { - const p = props[i] - if (p !== null && p !== undefined && p !== '') { - ret += '_' + p - } - } - return ret - }, - num2hex (num) { - const hex = num >= 0 ? num.toString(16) : 'XXXX' - return '0x' + '0'.repeat(4 - hex.length) + hex - }, - getVersion () { - if (!VERSION) { - let revision - try { - revision = require('child_process') - .execSync('git rev-parse --short HEAD') - .toString() - .trim() - } catch (error) { - // git not installed - } - VERSION = `${version}${revision ? '.' + revision : ''}` - } +/** + * Get the base root path to application directory. When we are in a `pkg` environment + * the path of the snapshot is not writable + * + * @param {boolean} write + * @returns {string} + */ +function getPath (write) { + if (write && process.pkg) return process.cwd() + else return appRoot.toString() +} - return VERSION - }, - sanitizeTopic (str, removeSlash) { - if (!isNaN(str) || !str) return str +/** + * path.join wrapper, the first option can be a boolean and it will automatically fetch the root path + * passing the boolean to getPath + * + * @param {boolean | string} write + * @param {string[]} paths + * @returns {string} the result of path join + */ +function joinPath (write, ...paths) { + if (typeof write === 'boolean') { + write = getPath(write) + } + return path.join(write, ...paths) +} - if (removeSlash) { - str = this.removeSlash(str) +/** + * Join props with a `_` and skips undefined props + * + * @param {string[]} props the array of props to join + * @returns {string} The result string with joined props + */ +function joinProps (...props) { + props = props || [] + let ret = props[0] || '' + for (let i = 1; i < props.length; i++) { + const p = props[i] + if (p !== null && p !== undefined && p !== '') { + ret += '_' + p } + } + return ret +} + +/** + * Converts a decimal to an hex number of 4 digits and `0x` as prefix + * + * @param {number} num the number to convert + * @returns {string} the hex number string with `0x` prefix + */ +function num2hex (num) { + const hex = num >= 0 ? num.toString(16) : 'XXXX' + return '0x' + '0'.repeat(4 - hex.length) + hex +} - // replace spaces with '_' - str = str.replace(/\s/g, '_') - // remove special chars - return str.replace(/[+*#\\.'`!?^=(),"%[\]:;{}]+/g, '') - }, - removeSlash (str) { - return !isNaN(str) ? str : str.replace(/\//g, '-') - }, - hasProperty (obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop) - }, - humanSize (bytes) { - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] - - if (bytes === 0) { - return 'n/a' +/** + * Gets the actual package.json version with also the git revision number at the end of it + * + * @returns {string} The version + */ +function getVersion () { + if (!VERSION) { + let revision + try { + revision = require('child_process') + .execSync('git rev-parse --short HEAD') + .toString() + .trim() + } catch (error) { + // git not installed } + VERSION = `${version}${revision ? '.' + revision : ''}` + } - const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))) + return VERSION +} - if (i === 0) { - return bytes + ' ' + sizes[i] - } +/** + * Sanitize chars of a string to use in a topic + * + * @param {string} str The string to sanitize + * @param {boolean} sanitizeSlash Set it to true to remove `/` chars from the string + * @returns {string} the sanitized string + */ +function sanitizeTopic (str, sanitizeSlash) { + if (!isNaN(str) || !str) return str + + if (sanitizeSlash) { + str = removeSlash(str) + } + + // replace spaces with '_' + str = str.replace(/\s/g, '_') + // remove special chars + return str.replace(/[+*#\\.'`!?^=(),"%[\]:;{}]+/g, '') +} + +/** + * Removes `/` chars from strings + * + * @param {string} str The string + * @returns {string} the string without `/` chars + */ +function removeSlash (str) { + return !isNaN(str) ? str : str.replace(/\//g, '-') +} + +/** + * Check if an object has a property + * + * @param {*} obj the object + * @param {string} prop the property + * @returns {boolean} true if the property exists, false otherwise + */ +function hasProperty (obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop) +} + +/** + * Gets the size in a human readable form starting from bytes + * + * @param {number} bytes total bytes + * @returns {string} the human readable size + */ +function humanSize (bytes) { + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] - return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i] + if (bytes === 0) { + return 'n/a' } + + const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))) + + if (i === 0) { + return bytes + ' ' + sizes[i] + } + + return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i] +} + +module.exports = { + getPath, + joinPath, + joinProps, + num2hex, + getVersion, + sanitizeTopic, + removeSlash, + hasProperty, + humanSize } diff --git a/test/lib/logger.test.js b/test/lib/logger.test.js index 6523b25748..d7bf8bf0da 100644 --- a/test/lib/logger.test.js +++ b/test/lib/logger.test.js @@ -7,7 +7,6 @@ const { storeDir } = require('../../config/app.js') function checkConfigDefaults (mod, cfg) { const defaultLogFile = utils.joinPath( - true, storeDir, logContainer.__get__('defaultLogFile') )