mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 13:34:24 +00:00
Add zigbee2mqtt/bridge/request/options and zigbee2mqtt/bridge/request/restart (#6089)
* Initial * Update schema * Log changed options * Implement zigbee2mqtt/bridge/request/restart * Implement restart required * Updates * Updatos * Set rtscts: false defaults * Updates * Updates
This commit is contained in:
@@ -1,38 +1,59 @@
|
||||
const semver = require('semver');
|
||||
const engines = require('./package.json').engines;
|
||||
const indexJsRestart = 'indexjs.restart';
|
||||
|
||||
const version = engines.node;
|
||||
if (!semver.satisfies(process.version, version)) {
|
||||
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
|
||||
let controller;
|
||||
let stopping = false;
|
||||
|
||||
async function restart() {
|
||||
await stop(indexJsRestart);
|
||||
await start();
|
||||
}
|
||||
|
||||
// Validate settings
|
||||
const settings = require('./lib/util/settings');
|
||||
const errors = settings.validate();
|
||||
if (errors.length > 0) {
|
||||
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
|
||||
console.log(' READ THIS CAREFULLY\n');
|
||||
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
|
||||
for (const error of errors) {
|
||||
console.log(`- ${error}`);
|
||||
async function exit(code, reason) {
|
||||
if (reason !== indexJsRestart) {
|
||||
process.exit(code);
|
||||
}
|
||||
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/information/configuration.html`);
|
||||
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const Controller = require('./lib/controller');
|
||||
const controller = new Controller();
|
||||
controller.start();
|
||||
async function start() {
|
||||
const version = engines.node;
|
||||
if (!semver.satisfies(process.version, version)) {
|
||||
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
|
||||
}
|
||||
|
||||
// Validate settings
|
||||
const settings = require('./lib/util/settings');
|
||||
const errors = settings.validate();
|
||||
if (errors.length > 0) {
|
||||
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
|
||||
console.log(' READ THIS CAREFULLY\n');
|
||||
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
|
||||
for (const error of errors) {
|
||||
console.log(`- ${error}`);
|
||||
}
|
||||
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/information/configuration.html`); // eslint-disable-line
|
||||
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const Controller = require('./lib/controller');
|
||||
controller = new Controller(restart, exit);
|
||||
await controller.start();
|
||||
}
|
||||
|
||||
async function stop(reason=null) {
|
||||
await controller.stop(reason);
|
||||
}
|
||||
|
||||
async function handleQuit() {
|
||||
if (!stopping && controller) {
|
||||
stopping = true;
|
||||
await stop();
|
||||
}
|
||||
}
|
||||
|
||||
process.on('SIGINT', handleQuit);
|
||||
process.on('SIGTERM', handleQuit);
|
||||
|
||||
let stopping = false;
|
||||
|
||||
function handleQuit() {
|
||||
if (!stopping) {
|
||||
stopping = true;
|
||||
controller.stop();
|
||||
}
|
||||
}
|
||||
start();
|
||||
|
||||
+10
-7
@@ -39,11 +39,13 @@ const AllExtensions = [
|
||||
];
|
||||
|
||||
class Controller {
|
||||
constructor() {
|
||||
constructor(restartCallback, exitCallback) {
|
||||
this.zigbee = new Zigbee();
|
||||
this.mqtt = new MQTT();
|
||||
this.eventBus = new EventBus();
|
||||
this.state = new State(this.eventBus);
|
||||
this.restartCallback = restartCallback;
|
||||
this.exitCallback = exitCallback;
|
||||
|
||||
this.publishEntityState = this.publishEntityState.bind(this);
|
||||
this.enableDisableExtension = this.enableDisableExtension.bind(this);
|
||||
@@ -52,7 +54,7 @@ class Controller {
|
||||
// Initialize extensions.
|
||||
const args = [this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus];
|
||||
this.extensions = [
|
||||
new ExtensionBridge(...args, this.enableDisableExtension),
|
||||
new ExtensionBridge(...args, this.enableDisableExtension, this.restartCallback),
|
||||
new ExtensionPublish(...args),
|
||||
new ExtensionReceive(...args),
|
||||
new ExtensionDeviceGroupMembership(...args),
|
||||
@@ -117,7 +119,7 @@ class Controller {
|
||||
logger.error('Failed to start zigbee');
|
||||
logger.error('Exiting...');
|
||||
logger.error(error.stack);
|
||||
process.exit(1);
|
||||
this.exitCallback(1);
|
||||
}
|
||||
|
||||
// Log zigbee clients on startup
|
||||
@@ -185,7 +187,7 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
async stop(reason=null) {
|
||||
// Call extensions
|
||||
await this.callExtensionMethod('stop', []);
|
||||
|
||||
@@ -195,10 +197,11 @@ class Controller {
|
||||
|
||||
try {
|
||||
await this.zigbee.stop();
|
||||
process.exit(0);
|
||||
logger.info('Stopped Zigbee2MQTT');
|
||||
this.exitCallback(0, reason);
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop zigbee');
|
||||
process.exit(1);
|
||||
logger.error('Failed to stop Zigbee2MQTT');
|
||||
this.exitCallback(1, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+65
-7
@@ -6,15 +6,18 @@ const settings = require('../util/settings');
|
||||
const Transport = require('winston-transport');
|
||||
const stringify = require('json-stable-stringify-without-jsonify');
|
||||
const objectAssignDeep = require(`object-assign-deep`);
|
||||
const {updatedDiff, addedDiff} = require('deep-object-diff');
|
||||
|
||||
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
|
||||
|
||||
class Bridge extends Extension {
|
||||
constructor(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension) {
|
||||
constructor(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback) {
|
||||
super(zigbee, mqtt, state, publishEntityState, eventBus);
|
||||
this.enableDisableExtension = enableDisableExtension;
|
||||
this.restartCallback = restartCallback;
|
||||
this.lastJoinedDeviceIeeeAddr = null;
|
||||
this.setupMQTTLogging();
|
||||
this.restartRequired = false;
|
||||
|
||||
this.requestLookup = {
|
||||
'device/options': this.deviceOptions.bind(this),
|
||||
@@ -26,15 +29,17 @@ class Bridge extends Extension {
|
||||
'group/remove': this.groupRemove.bind(this),
|
||||
'group/rename': this.groupRename.bind(this),
|
||||
'permit_join': this.permitJoin.bind(this),
|
||||
'config/last_seen': this.configLastSeen.bind(this),
|
||||
'config/homeassistant': this.configHomeAssistant.bind(this),
|
||||
'config/elapsed': this.configElapsed.bind(this),
|
||||
'config/log_level': this.configLogLevel.bind(this),
|
||||
'restart': this.restart.bind(this),
|
||||
'touchlink/factory_reset': this.touchlinkFactoryReset.bind(this),
|
||||
'touchlink/identify': this.touchlinkIdentify.bind(this),
|
||||
'touchlink/scan': this.touchlinkScan.bind(this),
|
||||
'health_check': this.healthCheck.bind(this),
|
||||
'options': this.bridgeOptions.bind(this),
|
||||
// Below are deprecated
|
||||
'config/last_seen': this.configLastSeen.bind(this),
|
||||
'config/homeassistant': this.configHomeAssistant.bind(this),
|
||||
'config/elapsed': this.configElapsed.bind(this),
|
||||
'config/log_level': this.configLogLevel.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,7 +78,9 @@ class Bridge extends Extension {
|
||||
}
|
||||
|
||||
permitJoinChanged(data) {
|
||||
this.publishInfo();
|
||||
if (!this.zigbee.isStopping()) {
|
||||
this.publishInfo();
|
||||
}
|
||||
}
|
||||
|
||||
async onMQTTMessage(topic, message) {
|
||||
@@ -142,7 +149,46 @@ class Bridge extends Extension {
|
||||
}
|
||||
|
||||
async bridgeOptions(message) {
|
||||
return utils.getResponse(message, {}, 'Saving settings is not yet implemented');
|
||||
if (typeof message !== 'object' || typeof message.options !== 'object') {
|
||||
throw new Error(`Invalid payload`);
|
||||
}
|
||||
|
||||
const diffUpdated = updatedDiff(settings.get(), message.options);
|
||||
const diffAdded = addedDiff(settings.get(), message.options);
|
||||
const newSettings = objectAssignDeep.noMutate(diffUpdated, diffAdded);
|
||||
|
||||
// deep-object-diff converts arrays to objects, set original array back here
|
||||
const convertBackArray = (before, after) => {
|
||||
for (const [key, afterValue] of Object.entries(after)) {
|
||||
const beforeValue = before[key];
|
||||
if (Array.isArray(beforeValue)) {
|
||||
after[key] = beforeValue;
|
||||
} else if (typeof beforeValue === 'object') {
|
||||
convertBackArray(beforeValue, afterValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
convertBackArray(message.options, newSettings);
|
||||
|
||||
const restartRequired = settings.apply(newSettings);
|
||||
if (restartRequired) this.restartRequired = true;
|
||||
|
||||
// Apply some settings on-the-fly.
|
||||
if (newSettings.hasOwnProperty('permit_join')) {
|
||||
await this.zigbee.permitJoin(newSettings.permit_join);
|
||||
}
|
||||
|
||||
if (newSettings.hasOwnProperty('homeassistant')) {
|
||||
this.enableDisableExtension(newSettings.homeassistant, 'HomeAssistant');
|
||||
}
|
||||
|
||||
if (newSettings.hasOwnProperty('advanced') && newSettings.advanced.hasOwnProperty('log_level')) {
|
||||
logger.setLevel(newSettings.advanced.log_level);
|
||||
}
|
||||
|
||||
logger.info('Succesfully changed options');
|
||||
this.publishInfo();
|
||||
return utils.getResponse(message, {restart_required: this.restartRequired}, null);
|
||||
}
|
||||
|
||||
async deviceRemove(message) {
|
||||
@@ -178,6 +224,13 @@ class Bridge extends Extension {
|
||||
return this.renameEntity('group', message);
|
||||
}
|
||||
|
||||
async restart(message) {
|
||||
// Wait 500 ms before restarting so response can be send.
|
||||
setTimeout(this.restartCallback, 500);
|
||||
logger.info('Restarting Zigbee2MQTT');
|
||||
return utils.getResponse(message, {}, null);
|
||||
}
|
||||
|
||||
async permitJoin(message) {
|
||||
if (typeof message === 'object' && !message.hasOwnProperty('value')) {
|
||||
throw new Error('Invalid payload');
|
||||
@@ -210,6 +263,7 @@ class Bridge extends Extension {
|
||||
return utils.getResponse(message, response, null);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
configLastSeen(message) {
|
||||
const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local'];
|
||||
const value = this.getValue(message);
|
||||
@@ -222,6 +276,7 @@ class Bridge extends Extension {
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
configHomeAssistant(message) {
|
||||
const allowed = [true, false];
|
||||
const value = this.getValue(message);
|
||||
@@ -235,6 +290,7 @@ class Bridge extends Extension {
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
configElapsed(message) {
|
||||
const allowed = [true, false];
|
||||
const value = this.getValue(message);
|
||||
@@ -247,6 +303,7 @@ class Bridge extends Extension {
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
configLogLevel(message) {
|
||||
const allowed = ['error', 'warn', 'info', 'debug'];
|
||||
const value = this.getValue(message);
|
||||
@@ -498,6 +555,7 @@ class Bridge extends Extension {
|
||||
network: utils.toSnakeCase(await this.zigbee.getNetworkParameters()),
|
||||
log_level: logger.getLevel(),
|
||||
permit_join: await this.zigbee.getPermitJoin(),
|
||||
restart_required: this.restartRequired,
|
||||
config,
|
||||
config_schema: settings.schema,
|
||||
};
|
||||
|
||||
+24
-5
@@ -6,8 +6,10 @@ const objectAssignDeep = require(`object-assign-deep`);
|
||||
const path = require('path');
|
||||
const yaml = require('./yaml');
|
||||
const Ajv = require('ajv');
|
||||
const ajv = new Ajv({allErrors: true});
|
||||
const schema = require('./settings.schema.json');
|
||||
const ajvSetting = new Ajv({allErrors: true}).compile(schema);
|
||||
const ajvRestartRequired = new Ajv({allErrors: true})
|
||||
.addKeyword('requiresRestart', {validate: (v) => !v}).compile(schema);
|
||||
|
||||
const defaults = {
|
||||
passlist: [],
|
||||
@@ -206,10 +208,8 @@ function validate() {
|
||||
return [error.message];
|
||||
}
|
||||
|
||||
const validate = ajv.compile(schema);
|
||||
validate(_settings);
|
||||
if (validate.errors) {
|
||||
return validate.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`);
|
||||
if (!ajvSetting(_settings)) {
|
||||
return ajvSetting.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
@@ -392,6 +392,24 @@ function set(path, value) {
|
||||
write();
|
||||
}
|
||||
|
||||
function apply(newSettings) {
|
||||
ajvSetting(newSettings);
|
||||
const errors = ajvSetting.errors && ajvSetting.errors.filter((e) => e.keyword !== 'required');
|
||||
if (errors.length) {
|
||||
const error = errors[0];
|
||||
throw new Error(`${error.dataPath.substring(1)} ${error.message}`);
|
||||
}
|
||||
|
||||
get(); // Ensure _settings is intialized.
|
||||
_settings = objectAssignDeep.noMutate(_settings, newSettings);
|
||||
write();
|
||||
|
||||
ajvRestartRequired(newSettings);
|
||||
const restartRequired = ajvRestartRequired.errors &&
|
||||
!!ajvRestartRequired.errors.find((e) => e.keyword === 'requiresRestart');
|
||||
return restartRequired;
|
||||
}
|
||||
|
||||
function getGroup(IDorName) {
|
||||
const settings = getWithDefaults();
|
||||
const byID = settings.groups[IDorName];
|
||||
@@ -637,6 +655,7 @@ module.exports = {
|
||||
validate,
|
||||
get: getWithDefaults,
|
||||
set,
|
||||
apply,
|
||||
getDevice,
|
||||
getGroup,
|
||||
getGroups,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"homeassistant": {
|
||||
"title": "Homeassistant integration",
|
||||
"title": "Home Assistant integration",
|
||||
"type": "boolean",
|
||||
"description": "Home Assistant integration (MQTT discovery)",
|
||||
"default": false
|
||||
@@ -14,11 +14,12 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"title": "Permit join",
|
||||
"description": "Allow new devices to join. WARNING: Disable this after all devices have been paired!"
|
||||
"description": "Allow new devices to join (re-applied at restart)"
|
||||
},
|
||||
"external_converters": {
|
||||
"type": "array",
|
||||
"title": "External converters",
|
||||
"requiresRestart": true,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -31,60 +32,69 @@
|
||||
"base_topic": {
|
||||
"type": "string",
|
||||
"title": "Base topic",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT base topic for Zigbee2MQTT MQTT messages",
|
||||
"examples": ["zigbee2mqtt"]
|
||||
},
|
||||
"server": {
|
||||
"type": "string",
|
||||
"title": "MQTT server",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT server URL (use mqtts:// for SSL/TLS connection)",
|
||||
"examples": ["mqtt://localhost:1883"]
|
||||
},
|
||||
"keepalive": {
|
||||
"type": "number",
|
||||
"title": "Keepalive",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT keepalive in second",
|
||||
"default": 60
|
||||
},
|
||||
"ca": {
|
||||
"type": "string",
|
||||
"title": "Certificate authority",
|
||||
"requiresRestart": true,
|
||||
"description": "Absolute path to SSL/TLS certificate of CA used to sign server and client certificates",
|
||||
"examples": ["/etc/ssl/mqtt-ca.crt"]
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"title": "SSL/TLS key",
|
||||
"requiresRestart": true,
|
||||
"description": "Absolute paths to SSL/TLS key and certificate for client-authentication",
|
||||
"examples": ["/etc/ssl/mqtt-client.key"]
|
||||
|
||||
},
|
||||
"cert": {
|
||||
"type": "string",
|
||||
"title": "SSL/TLS certificate",
|
||||
"requiresRestart": true,
|
||||
"examples": ["/etc/ssl/mqtt-client.crt"]
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"title": "User",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT server authentication user",
|
||||
"examples": ["johnnysilverhand"]
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"title": "Password",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT server authentication password",
|
||||
"examples": ["ILOVEPELMENI"]
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string",
|
||||
"title": "Client ID",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT client ID",
|
||||
"examples": ["MY_CLIENT_ID"]
|
||||
},
|
||||
"reject_unauthorized": {
|
||||
"type": "boolean",
|
||||
"title": "Reject unauthorized",
|
||||
"requiresRestart": true,
|
||||
"description": "Disable self-signed SSL certificate",
|
||||
"default": true
|
||||
},
|
||||
@@ -97,12 +107,14 @@
|
||||
"version": {
|
||||
"type": ["number", "null"],
|
||||
"title": "Version",
|
||||
"requiresRestart": true,
|
||||
"description": "MQTT protocol version",
|
||||
"default": 4
|
||||
},
|
||||
"force_disable_retain": {
|
||||
"type": "boolean",
|
||||
"title": "Force disable retain",
|
||||
"requiresRestart": true,
|
||||
"description": "Disable retain for all send messages. ONLY enable if you MQTT broker doesn't support retained message (e.g. AWS IoT core, Azure IoT Hub, Google Cloud IoT core, IBM Watson IoT Platform). Enabling will break the Home Assistant integration",
|
||||
"default": false
|
||||
}
|
||||
@@ -116,25 +128,29 @@
|
||||
"port": {
|
||||
"type": ["string", "null"],
|
||||
"title": "Port",
|
||||
"requiresRestart": true,
|
||||
"description": "Location of the adapter. To autodetect the port, set null",
|
||||
"examples": ["/dev/ttyACM0"]
|
||||
},
|
||||
"disable_led": {
|
||||
"type": "boolean",
|
||||
"title": "Disable led",
|
||||
"requiresRestart": true,
|
||||
"description": "Disable LED of the adapter if supported",
|
||||
"default": false
|
||||
},
|
||||
"adapter": {
|
||||
"type": "string",
|
||||
"type": ["string", "null"],
|
||||
"enum": ["deconz", "zstack", "zigate"],
|
||||
"title": "Adapter",
|
||||
"requiresRestart": true,
|
||||
"description": "Adapter type, not needed unless you are experiencing problems"
|
||||
}
|
||||
}
|
||||
},
|
||||
"blocklist": {
|
||||
"title": "Blocklist",
|
||||
"requiresRestart": true,
|
||||
"description": "Block devices from the network (by ieeeAddr)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -143,6 +159,7 @@
|
||||
},
|
||||
"passlist": {
|
||||
"title": "Passlist",
|
||||
"requiresRestart": true,
|
||||
"description": "Allow only certain devices to join the network (by ieeeAddr). Note that all devices not on the passlist will be removed from the network!",
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -150,15 +167,19 @@
|
||||
}
|
||||
},
|
||||
"whitelist": {
|
||||
"readOnly": true,
|
||||
"type": "array",
|
||||
"title": "Whitelist (deprecated)",
|
||||
"requiresRestart": true,
|
||||
"title": "Whitelist (deprecated, use passlist)",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ban": {
|
||||
"readOnly": true,
|
||||
"type": "array",
|
||||
"title": "Ban (deprecated)",
|
||||
"requiresRestart": true,
|
||||
"title": "Ban (deprecated, use blocklist)",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -169,7 +190,9 @@
|
||||
"properties": {
|
||||
"transmit_power": {
|
||||
"type": ["number", "null"],
|
||||
"title": "Transmit power"
|
||||
"title": "Transmit power",
|
||||
"requiresRestart": true,
|
||||
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters"
|
||||
},
|
||||
"output": {
|
||||
"type": "string",
|
||||
@@ -186,6 +209,7 @@
|
||||
"legacy_api": {
|
||||
"type": "boolean",
|
||||
"title": "Legacy API",
|
||||
"requiresRestart": true,
|
||||
"description": "Disables the legacy api (false = disable)",
|
||||
"default": true
|
||||
},
|
||||
@@ -200,7 +224,8 @@
|
||||
}
|
||||
],
|
||||
"title": "Pan ID",
|
||||
"description": "ZigBee pan ID"
|
||||
"requiresRestart": true,
|
||||
"description": "ZigBee pan ID, changing requires repairing all devices!"
|
||||
},
|
||||
"ext_pan_id": {
|
||||
"type": "array",
|
||||
@@ -208,7 +233,8 @@
|
||||
"type": "number"
|
||||
},
|
||||
"title": "Ext Pan ID",
|
||||
"description": "Zigbee extended pan ID"
|
||||
"requiresRestart": true,
|
||||
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
|
||||
},
|
||||
"channel": {
|
||||
"type": "number",
|
||||
@@ -216,7 +242,8 @@
|
||||
"maximum": 26,
|
||||
"default": 11,
|
||||
"title": "ZigBee channel",
|
||||
"description": "Changing requires re-pairing of all devices. (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)"
|
||||
"requiresRestart": true,
|
||||
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)"
|
||||
},
|
||||
"cache_state": {
|
||||
"type": "boolean",
|
||||
@@ -227,18 +254,19 @@
|
||||
"cache_state_persistent": {
|
||||
"type": "boolean",
|
||||
"title": "Chache state persistent",
|
||||
"description": "Persist cached state, only used when Cache state(cache_state: true)",
|
||||
"description": "Persist cached state, only used when cache_state: true",
|
||||
"default": true
|
||||
},
|
||||
"cache_state_send_on_startup": {
|
||||
"type": "boolean",
|
||||
"title": "Cache state send on startup",
|
||||
"description": "Cache state send on startup, only used when Cache state(cache_state: true)",
|
||||
"description": "Cache state send on startup, only used when cache_state: true",
|
||||
"default": true
|
||||
},
|
||||
"log_rotation": {
|
||||
"type": "boolean",
|
||||
"title": "Log rotation",
|
||||
"requiresRestart": true,
|
||||
"description": "Log rotation",
|
||||
"default": true
|
||||
},
|
||||
@@ -251,6 +279,7 @@
|
||||
},
|
||||
"log_output": {
|
||||
"type": "array",
|
||||
"requiresRestart": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["console", "file", "syslog"]
|
||||
@@ -261,12 +290,14 @@
|
||||
"log_directory": {
|
||||
"type": "string",
|
||||
"title": "Log directory",
|
||||
"requiresRestart": true,
|
||||
"description": "Location of log directory",
|
||||
"examples": ["data/log/%TIMESTAMP%"]
|
||||
},
|
||||
"log_file": {
|
||||
"type": "string",
|
||||
"title": "Log file",
|
||||
"requiresRestart": true,
|
||||
"description": "Log file name, can also contain timestamp",
|
||||
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
|
||||
"default": "log.txt"
|
||||
@@ -274,18 +305,20 @@
|
||||
"baudrate": {
|
||||
"type": "number",
|
||||
"title": "Baudrate",
|
||||
"requiresRestart": true,
|
||||
"description": "Baudrate for serial port, default: 115200 for Z-Stack, 38400 for Deconz",
|
||||
"examples": [38400, 115200]
|
||||
},
|
||||
"rtscts": {
|
||||
"type": "boolean",
|
||||
"title": "RTS / CTS",
|
||||
"description": "RTS / CTS Hardware Flow Control for serial port",
|
||||
"default": false
|
||||
"requiresRestart": true,
|
||||
"description": "RTS / CTS Hardware Flow Control for serial port"
|
||||
},
|
||||
"soft_reset_timeout": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"requiresRestart": true,
|
||||
"title": "Soft reset timeout (deprecated)",
|
||||
"description": "Soft reset ZNP after timeout",
|
||||
"readOnly": true
|
||||
@@ -304,7 +337,8 @@
|
||||
}
|
||||
],
|
||||
"title": "Network key",
|
||||
"description": "Network encryption key, will improve security (Note: changing requires repairing of all devices)"
|
||||
"requiresRestart": true,
|
||||
"description": "Network encryption key, changing requires repairing all devices!"
|
||||
},
|
||||
"last_seen": {
|
||||
"type": "string",
|
||||
@@ -323,14 +357,16 @@
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"requiresRestart": true,
|
||||
"title": "Availability Timeout",
|
||||
"description": "Availability timeout in seconds When enabled, devices will be checked if they are still online. Only AC powered routers are checked for availability"
|
||||
"description": "Availability timeout in seconds when enabled, devices will be checked if they are still online. Only AC powered routers are checked for availability"
|
||||
},
|
||||
"availability_blocklist": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"requiresRestart": true,
|
||||
"title": "Availability Blocklist",
|
||||
"description": "Prevent devices from being checked for availability"
|
||||
},
|
||||
@@ -339,51 +375,63 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"requiresRestart": true,
|
||||
"title": "Availability passlist",
|
||||
"description": "Only enable availability check for certain devices"
|
||||
},
|
||||
"availability_blacklist": {
|
||||
"type": "array",
|
||||
"title": "Availability blacklist (deprecated)",
|
||||
"readOnly": true,
|
||||
"requiresRestart": true,
|
||||
"title": "Availability blacklist (deprecated, use availability_blocklist)",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"availability_whitelist": {
|
||||
"type": "array",
|
||||
"readOnly": true,
|
||||
"requiresRestart": true,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "Availability whitelist (deprecated)"
|
||||
"title": "Availability whitelist (deprecated, use passlist)"
|
||||
},
|
||||
"report": {
|
||||
"type": "boolean",
|
||||
"title": "Reporting",
|
||||
"description": "Enables report feature"
|
||||
"requiresRestart": true,
|
||||
"readOnly": true,
|
||||
"description": "Enables report feature (deprecated)"
|
||||
},
|
||||
"homeassistant_discovery_topic": {
|
||||
"type": "string",
|
||||
"title": "Homeassistant discovery topic",
|
||||
"requiresRestart": true,
|
||||
"examples": ["homeassistant"]
|
||||
},
|
||||
"homeassistant_status_topic": {
|
||||
"type": "string",
|
||||
"title": "Home Assistant status topic",
|
||||
"requiresRestart": true,
|
||||
"examples": ["homeassistant/status"]
|
||||
},
|
||||
"timestamp_format": {
|
||||
"type": "string",
|
||||
"title": "Timestamp format",
|
||||
"requiresRestart": true,
|
||||
"description": "Log timestamp format",
|
||||
"examples": ["YYYY-MM-DD HH:mm:ss"]
|
||||
},
|
||||
"adapter_concurrent": {
|
||||
"title": "Adapter concurrency",
|
||||
"requiresRestart": true,
|
||||
"type": ["number", "null"],
|
||||
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
|
||||
},
|
||||
"adapter_delay": {
|
||||
"type": ["number", "null"],
|
||||
"requiresRestart": true,
|
||||
"title": "Adapter delay"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,10 @@ class Zigbee extends events.EventEmitter {
|
||||
return this.herdsman.getCoordinatorVersion();
|
||||
}
|
||||
|
||||
isStopping() {
|
||||
return this.herdsman.isStopping();
|
||||
}
|
||||
|
||||
async getNetworkParameters() {
|
||||
return this.herdsman.getNetworkParameters();
|
||||
}
|
||||
|
||||
Generated
+32
-47
@@ -707,6 +707,11 @@
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@serialport/parser-inter-byte-timeout": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-9.0.1.tgz",
|
||||
"integrity": "sha512-lFflcUflcP5SF4vLIixAKs1xUI/wfOzCv1Xq78VbPOBlIjZ6ny9lQ6g7cMPR/sB/M1BHwGcdX7CEr90pe3kkog=="
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz",
|
||||
@@ -1719,6 +1724,11 @@
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||
"dev": true
|
||||
},
|
||||
"deep-object-diff": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.0.tgz",
|
||||
"integrity": "sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw=="
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
@@ -21213,11 +21223,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-9.0.1.tgz",
|
||||
"integrity": "sha512-+oaSl5zEu47OlrRiF5p5tn2qgGqYuhVcE+NI+Pv4E1xsNB/A0fFxxMv/8XUw466CRLEJ5IESIB9qbFvKE6ltaQ=="
|
||||
},
|
||||
"@serialport/parser-inter-byte-timeout": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-9.0.1.tgz",
|
||||
"integrity": "sha512-lFflcUflcP5SF4vLIixAKs1xUI/wfOzCv1Xq78VbPOBlIjZ6ny9lQ6g7cMPR/sB/M1BHwGcdX7CEr90pe3kkog=="
|
||||
},
|
||||
"@serialport/parser-readline": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-9.0.1.tgz",
|
||||
@@ -22222,6 +22227,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -25374,6 +25384,13 @@
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
@@ -26313,11 +26330,6 @@
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||
},
|
||||
"require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
@@ -26566,27 +26578,6 @@
|
||||
"is-fullwidth-code-point": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -26961,17 +26952,6 @@
|
||||
"string-width": "^4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz",
|
||||
"integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
@@ -26982,11 +26962,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
@@ -27522,6 +27497,11 @@
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -27611,6 +27591,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"debounce": "^1.2.0",
|
||||
"deep-object-diff": "^1.1.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"finalhandler": "^1.1.2",
|
||||
"git-last-commit": "^1.0.0",
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('Availability', () => {
|
||||
data.writeEmptyState();
|
||||
jest.useFakeTimers();
|
||||
settings.set(['advanced', 'availability_timeout'], 10);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
@@ -203,7 +203,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_blocklist'], ['bulb_color'])
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -217,7 +217,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_blacklist'], ['bulb_color'])
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -231,7 +231,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_blocklist'], [device.ieeeAddr]);
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -258,7 +258,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_passlist'], ['bulb_color']);
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -272,7 +272,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_whitelist'], ['bulb_color']);
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -286,7 +286,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_passlist'], [device.ieeeAddr]);
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
device.ping.mockClear();
|
||||
@@ -301,7 +301,7 @@ describe('Availability', () => {
|
||||
settings.set(['advanced', 'availability_passlist'], ['0x000b57fffec6a5b3'])
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
jest.advanceTimersByTime(11 * 1000);
|
||||
@@ -387,7 +387,7 @@ describe('Availability', () => {
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
getExtension().state[device.ieeeAddr] = false;
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ describe('Bind', () => {
|
||||
zigbeeHerdsman.devices.bulb_color.getEndpoint(1).bind.mockClear();
|
||||
zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read.mockClear();
|
||||
debounce.mockClear();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
this.coordinatorEndoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
|
||||
|
||||
+125
-13
File diff suppressed because one or more lines are too long
@@ -46,7 +46,7 @@ describe('Configure', () => {
|
||||
data.writeDefaultConfiguration();
|
||||
settings._reRead();
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
await flushPromises();
|
||||
|
||||
@@ -3,14 +3,13 @@ const logger = require('./stub/logger');
|
||||
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
|
||||
const MQTT = require('./stub/mqtt');
|
||||
const path = require('path');
|
||||
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
||||
const settings = require('../lib/util/settings');
|
||||
const Controller = require('../lib/controller');
|
||||
const stringify = require('json-stable-stringify-without-jsonify');
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
const tmp = require('tmp');
|
||||
const mocksClear = [
|
||||
zigbeeHerdsman.permitJoin, mockExit, MQTT.end, zigbeeHerdsman.stop, logger.debug,
|
||||
zigbeeHerdsman.permitJoin, MQTT.end, zigbeeHerdsman.stop, logger.debug,
|
||||
MQTT.publish, MQTT.connect, zigbeeHerdsman.devices.bulb_color.removeFromNetwork,
|
||||
zigbeeHerdsman.devices.bulb.removeFromNetwork, logger.error,
|
||||
];
|
||||
@@ -19,10 +18,12 @@ const fs = require('fs');
|
||||
|
||||
describe('Controller', () => {
|
||||
let controller;
|
||||
let mockExit;
|
||||
|
||||
beforeEach(() => {
|
||||
zigbeeHerdsman.returnDevices.splice(0);
|
||||
controller = new Controller();
|
||||
mockExit = jest.fn();
|
||||
controller = new Controller(jest.fn(), mockExit);
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
data.writeDefaultConfiguration();
|
||||
settings._reRead();
|
||||
@@ -228,7 +229,7 @@ describe('Controller', () => {
|
||||
expect(MQTT.end).toHaveBeenCalledTimes(1);
|
||||
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledWith(0);
|
||||
expect(mockExit).toHaveBeenCalledWith(0, null);
|
||||
});
|
||||
|
||||
it('Start controller and stop', async () => {
|
||||
@@ -238,7 +239,7 @@ describe('Controller', () => {
|
||||
expect(MQTT.end).toHaveBeenCalledTimes(1);
|
||||
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledWith(1);
|
||||
expect(mockExit).toHaveBeenCalledWith(1, null);
|
||||
});
|
||||
|
||||
it('Start controller adapter disconnects', async () => {
|
||||
@@ -249,7 +250,7 @@ describe('Controller', () => {
|
||||
expect(MQTT.end).toHaveBeenCalledTimes(1);
|
||||
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledTimes(1);
|
||||
expect(mockExit).toHaveBeenCalledWith(1);
|
||||
expect(mockExit).toHaveBeenCalledWith(1, null);
|
||||
});
|
||||
|
||||
it('Handle mqtt message', async () => {
|
||||
@@ -606,7 +607,7 @@ describe('Controller', () => {
|
||||
const extensionPath = path.join(data.mockDir, 'extension');
|
||||
fs.mkdirSync(extensionPath);
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), path.join(extensionPath, 'exampleExtension.js'))
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('Loads external converters', () => {
|
||||
|
||||
it('Does not load external converters', async () => {
|
||||
settings.set(['external_converters'], []);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(0);
|
||||
@@ -62,7 +62,7 @@ describe('Loads external converters', () => {
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
|
||||
const devicesCount = zigbeeHerdsman.devices.lenght;
|
||||
settings.set(['external_converters'], ['mock-external-converter.js']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
|
||||
@@ -82,7 +82,7 @@ describe('Loads external converters', () => {
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
|
||||
const devicesCount = zigbeeHerdsman.devices.lenght;
|
||||
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
|
||||
@@ -110,7 +110,7 @@ describe('Loads external converters', () => {
|
||||
|
||||
it('Loads external converters from package', async () => {
|
||||
settings.set(['external_converters'], ['mock-external-converter-module']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
|
||||
@@ -121,7 +121,7 @@ describe('Loads external converters', () => {
|
||||
|
||||
it('Loads multiple external converters from package', async () => {
|
||||
settings.set(['external_converters'], ['mock-multiple-external-converter-module']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('Frontend', () => {
|
||||
});
|
||||
|
||||
it('Start/stop', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
|
||||
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
|
||||
@@ -103,7 +103,7 @@ describe('Frontend', () => {
|
||||
});
|
||||
|
||||
it('Websocket interaction', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
|
||||
// Connect
|
||||
@@ -158,7 +158,7 @@ describe('Frontend', () => {
|
||||
});
|
||||
|
||||
it('onReques/onUpgrade', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
|
||||
const mockSocket = {destroy: jest.fn()};
|
||||
@@ -176,7 +176,7 @@ describe('Frontend', () => {
|
||||
});
|
||||
|
||||
it('Static server', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
|
||||
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
|
||||
@@ -185,7 +185,7 @@ describe('Frontend', () => {
|
||||
it('Authentification', async () => {
|
||||
const authToken = 'sample-secure-token'
|
||||
settings.set(['frontend'], {auth_token: authToken});
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
|
||||
const mockSocket = {destroy: jest.fn()};
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ describe('Groups', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
Object.values(zigbeeHerdsman.groups).forEach((g) => g.members = []);
|
||||
data.writeDefaultConfiguration();
|
||||
settings._reRead();
|
||||
|
||||
@@ -790,7 +790,7 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Shouldnt discover when device leaves', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
|
||||
@@ -804,7 +804,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Should send all status when home assistant comes online (default topic)', async () => {
|
||||
jest.useFakeTimers();
|
||||
data.writeDefaultState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
expect(MQTT.subscribe).toHaveBeenCalledWith('homeassistant/status');
|
||||
await flushPromises();
|
||||
@@ -830,7 +830,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Should send all status when home assistant comes online', async () => {
|
||||
jest.useFakeTimers();
|
||||
data.writeDefaultState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(MQTT.subscribe).toHaveBeenCalledWith('hass/status');
|
||||
@@ -856,7 +856,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Shouldnt send all status when home assistant comes offline', async () => {
|
||||
jest.useFakeTimers();
|
||||
data.writeDefaultState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
@@ -870,7 +870,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Shouldnt send all status when home assistant comes online with different topic', async () => {
|
||||
jest.useFakeTimers();
|
||||
data.writeDefaultState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
@@ -1405,7 +1405,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Load Home Assistant mapping from external converters', async () => {
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
|
||||
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
@@ -1424,7 +1424,7 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should clear outdated configs', async () => {
|
||||
controller = new Controller(false);
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('Bridge legacy', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
this.version = await require('../../lib/util/utils').getZigbee2mqttVersion();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
})
|
||||
|
||||
@@ -251,7 +251,7 @@ describe('Bridge legacy', () => {
|
||||
});
|
||||
|
||||
it('Shouldnt rename when no device has been joined', async () => {
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(settings.getDevice('0x000b57fffec6a5b2').friendlyName).toStrictEqual('bulb');
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('Report', () => {
|
||||
delete device.meta.reporting;
|
||||
}
|
||||
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
await flushPromises();
|
||||
@@ -97,7 +97,7 @@ describe('Report', () => {
|
||||
mockClear(device);
|
||||
delete device.meta.report;
|
||||
settings.set(['advanced', 'report'], false);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(device.meta.reporting).toBe(undefined);
|
||||
@@ -110,7 +110,7 @@ describe('Report', () => {
|
||||
const endpoint = device.getEndpoint(1);
|
||||
settings.set(['advanced', 'report'], false);
|
||||
mockClear(device);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expectOnOffBrightnessColorReportDisabled(endpoint, true);
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('Networkmap', () => {
|
||||
data.writeEmptyState();
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
|
||||
settings.set(['external_converters'], ['mock-external-converter.js']);
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
await flushPromises();
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('On event', () => {
|
||||
data.writeDefaultConfiguration();
|
||||
settings._reRead();
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
zigbeeHerdsmanConverters.onEvent.mockClear();
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('OTA update', () => {
|
||||
settings._reRead();
|
||||
settings.set(['advanced', 'ikea_ota_use_test_url'], true);
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('Publish', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Receive', () => {
|
||||
data.writeDefaultConfiguration();
|
||||
settings._reRead();
|
||||
data.writeEmptyState();
|
||||
controller = new Controller();
|
||||
controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
mocksClear.forEach((m) => m.mockClear());
|
||||
delete zigbeeHerdsman.devices.WXKG11LM.linkquality;
|
||||
|
||||
@@ -193,6 +193,7 @@ const mock = {
|
||||
touchlinkScan: jest.fn(),
|
||||
touchlinkIdentify: jest.fn(),
|
||||
start: jest.fn(),
|
||||
isStopping: jest.fn(),
|
||||
permitJoin: jest.fn(),
|
||||
getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}),
|
||||
getNetworkParameters: jest.fn().mockReturnValue({panID: 0x162a, extendedPanID: [0, 11, 22], channel: 15}),
|
||||
|
||||
Reference in New Issue
Block a user