diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..4cf8cff54 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/docker/Dockerfile b/docker/Dockerfile index 1ee043e87..55c738792 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12-alpine3.12 as base +FROM node:14-alpine3.12 as base WORKDIR /app RUN apk add --no-cache tzdata eudev @@ -19,7 +19,7 @@ FROM base as release COPY --from=dependencies /app/node_modules ./node_modules COPY lib ./lib -COPY index.js data/configuration.yaml ./ +COPY LICENSE index.js data/configuration.yaml ./ COPY docker/docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh diff --git a/lib/controller.js b/lib/controller.js index b7d835dc0..8dbd865d2 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -26,7 +26,7 @@ const ExtensionBridge = require('./extension/bridge'); const ExtensionGroups = require('./extension/groups'); const ExtensionAvailability = require('./extension/availability'); const ExtensionBind = require('./extension/bind'); -const ExtensionReport = require('./extension/report'); +const ExtensionReport = require('./extension/legacy/report'); const ExtensionOnEvent = require('./extension/onEvent'); const ExtensionOTAUpdate = require('./extension/otaUpdate'); const ExtensionExternalConverters = require('./extension/externalConverters'); @@ -35,7 +35,7 @@ const AllExtensions = [ ExtensionPublish, ExtensionReceive, ExtensionNetworkMap, ExtensionSoftReset, ExtensionHomeAssistant, ExtensionConfigure, ExtensionDeviceGroupMembership, ExtensionBridgeLegacy, ExtensionBridge, ExtensionGroups, ExtensionAvailability, ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate, - ExtensionExternalConverters, + ExtensionExternalConverters, ExtensionFrontend, ]; class Controller { @@ -73,6 +73,10 @@ class Controller { this.extensions.push(new ExtensionBridgeLegacy(...args)); } + if (settings.get().external_converters.length) { + this.extensions.push(new ExtensionExternalConverters(...args)); + } + if (settings.get().homeassistant) { this.extensions.push(new ExtensionHomeAssistant(...args)); } @@ -85,9 +89,6 @@ class Controller { if (settings.get().advanced.availability_timeout) { this.extensions.push(new ExtensionAvailability(...args)); } - if (settings.get().external_converters.length) { - this.extensions.push(new ExtensionExternalConverters(...args)); - } const extensionPath = data.joinPath('extension'); if (fs.existsSync(extensionPath)) { @@ -344,7 +345,7 @@ class Controller { } } - this.eventBus.emit('publishEntityState', {payload: messagePayload, entity: resolvedEntity, stateChangeReason}); + this.eventBus.emit('publishEntityState', {messagePayload, entity: resolvedEntity, stateChangeReason, payload}); } async iteratePayloadAttributeOutput(topicRoot, payload, options) { diff --git a/lib/extension/availability.js b/lib/extension/availability.js index cdcbec77a..b7ac94784 100644 --- a/lib/extension/availability.js +++ b/lib/extension/availability.js @@ -9,6 +9,7 @@ const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/(.*)/availabil // e.g. E11-G13 https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-453683846 const pingableEndDevices = [ zigbeeHerdsmanConverters.devices.find((d) => d.model === 'E11-G13'), + zigbeeHerdsmanConverters.devices.find((d) => d.model === 'E11-N1EA'), zigbeeHerdsmanConverters.devices.find((d) => d.model === '53170161'), ]; diff --git a/lib/extension/bind.js b/lib/extension/bind.js index 5225d3919..cb2f5bbde 100644 --- a/lib/extension/bind.js +++ b/lib/extension/bind.js @@ -5,16 +5,112 @@ const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/( const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`); const Extension = require('./extension'); const stringify = require('json-stable-stringify-without-jsonify'); +const debounce = require('debounce'); +const zigbeeHersdman = require('zigbee-herdsman'); const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering']; // See zigbee-herdsman-converters devices.js const defaultBindGroup = {type: 'group_number', ID: 901}; +const defaultReportConfiguration = { + minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1, +}; + +const getColorCapabilities = async (endpoint) => { + if (endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') == null) { + await endpoint.read('lightingColorCtrl', ['colorCapabilities']); + } + + const value = endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities'); + return { + colorTemperature: (value & 1<<4) > 0, + colorXY: (value & 1<<3) > 0, + }; +}; + +const reportClusters = { + 'genOnOff': [ + {attribute: 'onOff', ...defaultReportConfiguration, minimumReportInterval: 0, reportableChange: 0}, + ], + 'genLevelCtrl': [ + {attribute: 'currentLevel', ...defaultReportConfiguration}, + ], + 'lightingColorCtrl': [ + { + attribute: 'colorTemperature', ...defaultReportConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorTemperature, + }, + { + attribute: 'currentX', ...defaultReportConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY, + }, + { + attribute: 'currentY', ...defaultReportConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY, + }, + ], + 'closuresWindowCovering': [ + {attribute: 'currentPositionLiftPercentage', ...defaultReportConfiguration}, + {attribute: 'currentPositionTiltPercentage', ...defaultReportConfiguration}, + ], +}; + +const pollOnMessage = [ + { + // On messages that have the cluster and type of below + cluster: { + manuSpecificPhilips: [ + {type: 'commandHueNotification', data: {button: 2}}, + {type: 'commandHueNotification', data: {button: 3}}, + ], + genLevelCtrl: [ + {type: 'commandStep', data: {}}, + {type: 'commandStepWithOnOff', data: {}}, + {type: 'commandStop', data: {}}, + {type: 'commandMoveWithOnOff', data: {}}, + {type: 'commandStopWithOnOff', data: {}}, + {type: 'commandMove', data: {}}, + ], + }, + // Read the following attributes + read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']}, + // When the bound devices/members of group have the following manufacturerIDs + manufacturerIDs: [ + zigbeeHersdman.Zcl.ManufacturerCode.Philips, + zigbeeHersdman.Zcl.ManufacturerCode.ATMEL, + zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD, + zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT, + ], + }, + { + cluster: { + genOnOff: [ + {type: 'commandOn', data: {}}, + {type: 'commandOff', data: {}}, + {type: 'commandOffWithEffect', data: {}}, + ], + manuSpecificPhilips: [ + {type: 'commandHueNotification', data: {button: 1}}, + {type: 'commandHueNotification', data: {button: 4}}, + ], + }, + read: {cluster: 'genOnOff', attributes: ['onOff']}, + manufacturerIDs: [ + zigbeeHersdman.Zcl.ManufacturerCode.Philips, + zigbeeHersdman.Zcl.ManufacturerCode.ATMEL, + zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD, + zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT, + ], + }, +]; + class Bind extends Extension { constructor(zigbee, mqtt, state, publishEntityState, eventBus) { super(zigbee, mqtt, state, publishEntityState, eventBus); this.legacyApi = settings.get().advanced.legacy_api; + this.eventBus.on(`groupMembersChanged`, (d) => this.groupMembersChanged(d)); + this.pollDebouncers = {}; } parseMQTTMessage(topic, message) { @@ -59,6 +155,11 @@ class Bind extends Extension { const failedClusters = []; const attemptedClusters = []; + let bindTarget = null; + if (target.type === 'group') bindTarget = target.group; + else if (target.type === 'group_number') bindTarget = target.ID; + else bindTarget = target.endpoint; + // Find which clusters are supported by both the source and target. // Groups are assumed to support all clusters. for (const cluster of clusterCandidates) { @@ -72,11 +173,6 @@ class Bind extends Extension { attemptedClusters.push(cluster); try { - let bindTarget = null; - if (target.type === 'group') bindTarget = target.group; - else if (target.type === 'group_number') bindTarget = target.ID; - else bindTarget = target.endpoint; - if (type === 'bind') { await source.endpoint.bind(cluster, bindTarget); } else { @@ -133,6 +229,15 @@ class Bind extends Extension { responseData[`clusters`] = successfulClusters; responseData[`failed`] = failedClusters; + + if (successfulClusters.length !== 0) { + if (type === 'bind') { + await this.setupReporting(source.endpoint.binds.filter((b) => + successfulClusters.includes(b.cluster.name) && b.target === bindTarget)); + } else if (target.type !== 'group_number') { + await this.disableUnnecessaryReportings(bindTarget); + } + } } const triggeredViaLegacyApi = topic.match(legacyTopicRegex); @@ -147,6 +252,149 @@ class Bind extends Extension { this.eventBus.emit(`devicesChanged`); } } + + async groupMembersChanged(data) { + const bindsToGroup = this.zigbee.getClients().map((c) => c.endpoints).flat().map((e) => e.binds) + .flat().filter((b) => b.target === data.group.group); + + if (data.action === 'add') { + await this.setupReporting(bindsToGroup); + } else { // action === remove/remove_all + await this.disableUnnecessaryReportings(data.endpoint); + } + } + + getSetupReportingEndpoints(bind, coordinatorEp) { + const endpoints = bind.target.constructor.name === 'Group' ? bind.target.members : [bind.target]; + return endpoints.filter((e) => { + const supportsInputCluster = e.supportsInputCluster(bind.cluster.name); + const hasConfiguredReporting = !!e.configuredReportings.find((c) => c.cluster.name === bind.cluster.name); + const hasBind = !!e.binds.find((b) => b.cluster.name === bind.cluster.name && b.target === coordinatorEp); + return supportsInputCluster && !(hasBind && hasConfiguredReporting); + }); + } + + async setupReporting(binds) { + const coordinator = this.zigbee.getDevicesByType('Coordinator')[0]; + const coordinatorEndpoint = coordinator.getEndpoint(1); + for (const bind of binds.filter((b) => b.cluster.name in reportClusters)) { + for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) { + const entity = `${this.zigbee.resolveEntity(endpoint.getDevice()).name}/${endpoint.ID}`; + try { + await endpoint.bind(bind.cluster.name, coordinatorEndpoint); + + const items = await reportClusters[bind.cluster.name] + .filter(async (a) => !a.condition || await a.condition(endpoint)) + .map((a) => { + const result = {...a}; + delete result.condition; + return result; + }); + await endpoint.configureReporting(bind.cluster.name, items); + logger.info(`Succesfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`); + } catch (error) { + logger.warn(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}'`); + } + } + } + + this.eventBus.emit(`devicesChanged`); + } + + async disableUnnecessaryReportings(target) { + const coordinator = this.zigbee.getDevicesByType('Coordinator')[0]; + const coordinatorEndpoint = coordinator.getEndpoint(1); + const endpoints = target.constructor.name === 'Group' ? target.members : [target]; + for (const endpoint of endpoints) { + const entity = `${this.zigbee.resolveEntity(endpoint.getDevice()).name}/${endpoint.ID}`; + const boundClusters = endpoint.binds.filter((b) => b.target === coordinatorEndpoint) + .map((b) => b.cluster.name); + const requiredClusters = this.zigbee.getClients().map((c) => c.endpoints).flat().map((e) => e.binds) + .flat().filter((bind) => { + if (bind.target.constructor.name === 'Group') { + return bind.target.members.includes(endpoint); + } else { + return bind.target === endpoint; + } + }).map((b) => b.cluster.name).filter((v, i, a) => a.indexOf(v) === i); + + for (const cluster of boundClusters.filter((c) => !requiredClusters.includes(c) && c in reportClusters)) { + try { + await endpoint.unbind(cluster, coordinatorEndpoint); + const items = await reportClusters[cluster] + .filter(async (a) => !a.condition || await a.condition(endpoint)) + .map((a) => { + const result = {...a, maximumReportInterval: 0xFFFF}; + delete result.condition; + return result; + }); + await endpoint.configureReporting(cluster, items); + logger.info(`Succesfully disabled reporting for '${entity}' cluster '${cluster}'`); + } catch (error) { + logger.warn(`Failed to disable reporting for '${entity}' cluster '${cluster}'`); + } + } + + this.eventBus.emit('reportingDisabled', {device: endpoint.getDevice()}); + } + } + + async onZigbeeEvent(type, data, resolvedEntity) { + if (type === 'message') { + this.poll(data); + } + } + + poll(message) { + /** + * This method poll bound endpoints and group members for state changes. + * + * A use case is e.g. a Hue Dimmer switch bound to a Hue bulb. + * Hue bulbs only report their on/off state. + * When dimming the bulb via the dimmer switch the state is therefore not reported. + * When we receive a message from a Hue dimmer we read the brightness from the bulb (if bound). + */ + const polls = pollOnMessage.filter((p) => + p.cluster[message.cluster] && p.cluster[message.cluster].find((c) => c.type === message.type && + utils.equalsPartial(message.data, c.data)), + ); + + if (polls.length) { + let toPoll = []; + + // Add bound devices + toPoll = toPoll.concat([].concat(...message.device.endpoints.map((e) => + e.binds.map((e) => e).filter((e) => e.target)))); + toPoll = toPoll.filter((e) => e.target.constructor.name === 'Endpoint'); + toPoll = toPoll.filter((e) => e.target.getDevice().type !== 'Coordinator'); + toPoll = toPoll.map((e) => e.target); + + // If message is published to a group, add members of the group + const group = message.groupID !== 0 ? this.zigbee.getGroupByID(message.groupID) : null; + if (group) { + toPoll = toPoll.concat(group.members); + } + + toPoll = new Set(toPoll); + + for (const endpoint of toPoll) { + for (const poll of polls) { + if (!poll.manufacturerIDs.includes(endpoint.getDevice().manufacturerID)) { + continue; + } + + const key = `${endpoint.deviceIeeeAddress}_${endpoint.ID}_${pollOnMessage.indexOf(poll)}`; + if (!this.pollDebouncers[key]) { + this.pollDebouncers[key] = debounce(async () => { + await endpoint.read(poll.read.cluster, poll.read.attributes); + }, 1000); + } + + this.pollDebouncers[key](); + } + } + } + } } module.exports = Bind; diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 70b5e9037..340d86157 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -34,6 +34,7 @@ class Bridge extends Extension { 'touchlink/identify': this.touchlinkIdentify.bind(this), 'touchlink/scan': this.touchlinkScan.bind(this), 'health_check': this.healthCheck.bind(this), + 'options': this.bridgeOptions.bind(this), }; } @@ -42,7 +43,17 @@ class Bridge extends Extension { this.coordinatorVersion = await this.zigbee.getCoordinatorVersion(); this.eventBus.on(`groupMembersChanged`, () => this.publishGroups()); - this.eventBus.on(`devicesChanged`, () => this.publishDevices()); + this.eventBus.on(`devicesChanged`, () => { + this.publishDevices(); + this.publishInfo(); + }); + this.eventBus.on(`deviceRenamed`, () => { + this.publishInfo(); + }); + this.eventBus.on(`groupRenamed`, () => { + this.publishInfo(); + }); + this.zigbee.on('permitJoinChanged', (data) => this.permitJoinChanged(data)); await this.publishInfo(); await this.publishDevices(); await this.publishGroups(); @@ -58,7 +69,11 @@ class Bridge extends Extension { } } - logger.add(new EventTransport({level: logger.getLevel()})); + logger.addTransport(new EventTransport()); + } + + permitJoinChanged(data) { + this.publishInfo(); } async onMQTTMessage(topic, message) { @@ -126,6 +141,10 @@ class Bridge extends Extension { return this.changeEntityOptions('group', message); } + async bridgeOptions(message) { + return utils.getResponse(message, {}, 'Saving settings is not yet implemented'); + } + async deviceRemove(message) { return this.removeEntity('device', message); } @@ -165,9 +184,11 @@ class Bridge extends Extension { } let value; + let time; let resolvedEntity; if (typeof message === 'object') { value = message.value; + time = message.time; if (message.device) { resolvedEntity = this.zigbee.resolveEntity(message.device); if (!resolvedEntity || resolvedEntity.type !== 'device') { @@ -178,11 +199,15 @@ class Bridge extends Extension { value = message; } - await this.zigbee.permitJoin(value, resolvedEntity); - await this.publishInfo(); - return utils.getResponse( - message, resolvedEntity ? {value: value, device: message.device} : {value: value}, null, - ); + if (typeof value === 'string') { + value = value.toLowerCase() === 'true'; + } + + await this.zigbee.permitJoin(value, resolvedEntity, time); + const response = {value}; + if (resolvedEntity) response.device = message.device; + if (time) response.time = message.time; + return utils.getResponse(message, response, null); } configLastSeen(message) { @@ -192,7 +217,7 @@ class Bridge extends Extension { throw new Error(`'${value}' is not an allowed value, allowed: ${allowed}`); } - settings.set(['advanced', 'last_seen'], message); + settings.set(['advanced', 'last_seen'], value); this.publishInfo(); return utils.getResponse(message, {value}, null); } @@ -294,7 +319,7 @@ class Bridge extends Extension { } } - changeEntityOptions(entityType, message) { + async changeEntityOptions(entityType, message) { if (typeof message !== 'object' || !message.hasOwnProperty('id') || !message.hasOwnProperty('options')) { throw new Error(`Invalid payload`); } @@ -308,6 +333,7 @@ class Bridge extends Extension { }; const oldOptions = cleanup(entity.settings); const newOptions = cleanup(settings.getEntity(ID)); + await this.publishInfo(); return utils.getResponse(message, {from: oldOptions, to: newOptions, id: ID}, null); } @@ -475,6 +501,7 @@ class Bridge extends Extension { log_level: logger.getLevel(), permit_join: await this.zigbee.getPermitJoin(), config, + config_schema: settings.schema, }; await this.mqtt.publish('bridge/info', stringify(payload), {retain: true, qos: 0}); @@ -567,6 +594,7 @@ class Bridge extends Extension { vendor: definition.vendor, description: definition.description, exposes: definition.exposes, + supports_ota: !!definition.ota, }; } else { return null; diff --git a/lib/extension/groups.js b/lib/extension/groups.js index 2106d230d..fbc4b99bc 100644 --- a/lib/extension/groups.js +++ b/lib/extension/groups.js @@ -109,6 +109,9 @@ class Groups extends Extension { } } } else { + // Invalidate the last optimistic group state when group state is changed directly. + delete this.lastOptimisticState[resolvedEntity.group.groupID]; + const groupIDsToPublish = new Set(); for (const member of resolvedEntity.group.members) { await this.publishEntityState(member.getDevice().ieeeAddr, payload, reason); @@ -318,7 +321,8 @@ class Groups extends Extension { if (error) { logger.error(error); } else { - this.eventBus.emit('groupMembersChanged', {group: resolvedEntityGroup}); + this.eventBus.emit('groupMembersChanged', + {group: resolvedEntityGroup, action: type, endpoint: resolvedEntityDevice.endpoint}); } } } diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 2ec6050e7..b9b42862c 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -53,351 +53,346 @@ class HomeAssistant extends Extension { populateMapping() { for (const def of zigbeeHerdsmanConverters.definitions) { - if (def.hasOwnProperty('exposes')) { - this.mapping[def.model] = []; + this.mapping[def.model] = []; - if (['WXKG01LM', 'HS1EB/HS1EB-E', 'ICZB-KPD14S', 'TERNCY-SD01', 'TERNCY-PP01', 'ICZB-KPD18S', - 'E1766', 'ZWallRemote0', 'ptvo.switch', '2AJZ4KPKEY', 'ZGRC-KEY-013', 'HGZB-02S', 'HGZB-045', - 'HGZB-1S', 'AV2010/34', 'IM6001-BTP01', 'WXKG11LM', 'WXKG03LM', 'WXKG02LM_rev1', 'WXKG02LM_rev2', - 'QBKG04LM', 'QBKG03LM', 'QBKG11LM', 'QBKG21LM', 'QBKG22LM', 'WXKG12LM', 'QBKG12LM', - 'E1743'].includes(def.model)) { - // deprecated - this.mapping[def.model].push(sensorClick); - } + if (['WXKG01LM', 'HS1EB/HS1EB-E', 'ICZB-KPD14S', 'TERNCY-SD01', 'TERNCY-PP01', 'ICZB-KPD18S', + 'E1766', 'ZWallRemote0', 'ptvo.switch', '2AJZ4KPKEY', 'ZGRC-KEY-013', 'HGZB-02S', 'HGZB-045', + 'HGZB-1S', 'AV2010/34', 'IM6001-BTP01', 'WXKG11LM', 'WXKG03LM', 'WXKG02LM_rev1', 'WXKG02LM_rev2', + 'QBKG04LM', 'QBKG03LM', 'QBKG11LM', 'QBKG21LM', 'QBKG22LM', 'WXKG12LM', 'QBKG12LM', + 'E1743'].includes(def.model)) { + // deprecated + this.mapping[def.model].push(sensorClick); + } - if (['ICTC-G-1'].includes(def.model)) { - // deprecated - this.mapping[def.model].push({ - type: 'sensor', - object_id: 'brightness', + if (['ICTC-G-1'].includes(def.model)) { + // deprecated + this.mapping[def.model].push({ + type: 'sensor', + object_id: 'brightness', + discovery_payload: { + unit_of_measurement: 'brightness', + icon: 'mdi:brightness-5', + value_template: '{{ value_json.brightness }}', + }, + }); + } + + for (const expose of def.exposes) { + let discoveryEntry = null; + /* istanbul ignore else */ + if (expose.type === 'light') { + const supportsXY = !!expose.features.find((e) => e.name === 'color_xy'); + const supportsHS = !!expose.features.find((e) => e.name === 'color_hs'); + discoveryEntry = { + type: 'light', + object_id: expose.endpoint ? `light_${expose.endpoint}` : 'light', discovery_payload: { - unit_of_measurement: 'brightness', - icon: 'mdi:brightness-5', - value_template: '{{ value_json.brightness }}', + brightness: !!expose.features.find((e) => e.name === 'brightness'), + color_temp: !!expose.features.find((e) => e.name === 'color_temp'), + xy: supportsXY, + hs: !supportsXY && supportsHS, + schema: 'json', + command_topic: true, + brightness_scale: 254, + command_topic_prefix: expose.endpoint ? expose.endpoint : undefined, + state_topic_postfix: expose.endpoint ? expose.endpoint : undefined, }, - }); - } + }; - for (const expose of def.exposes) { - let discoveryEntry = null; - /* istanbul ignore else */ - if (expose.type === 'light') { - discoveryEntry = { - type: 'light', - object_id: expose.endpoint ? `light_${expose.endpoint}` : 'light', - discovery_payload: { - brightness: !!expose.features.find((e) => e.name === 'brightness'), - color_temp: !!expose.features.find((e) => e.name === 'color_temp'), - xy: !!expose.features.find((e) => e.name === 'color_xy'), - hs: !!expose.features.find((e) => e.name === 'color_hs'), - schema: 'json', - command_topic: true, - brightness_scale: 254, - command_topic_prefix: expose.endpoint ? expose.endpoint : undefined, - state_topic_postfix: expose.endpoint ? expose.endpoint : undefined, - }, + const effect = def.exposes.find((e) => e.type === 'enum' && e.name === 'effect'); + if (effect) { + discoveryEntry.discovery_payload.effect = true; + discoveryEntry.discovery_payload.effect_list = effect.values; + } + } else if (expose.type === 'switch') { + const state = expose.features.find((f) => f.name === 'state'); + discoveryEntry = { + type: 'switch', + object_id: expose.endpoint ? `switch_${expose.endpoint}` : 'switch', + discovery_payload: { + payload_off: state.value_off, + payload_on: state.value_on, + value_template: `{{ value_json.${state.property} }}`, + command_topic: true, + command_topic_prefix: expose.endpoint ? expose.endpoint : undefined, + }, + }; + + const different = ['valve_detection', 'window_detection', 'auto_lock', 'away_mode']; + if (different.includes(state.property)) { + discoveryEntry.discovery_payload.command_topic_postfix = state.property; + discoveryEntry.discovery_payload.state_off = state.value_off; + discoveryEntry.discovery_payload.state_on = state.value_on; + discoveryEntry.discovery_payload.state_topic = true; + discoveryEntry.object_id = state.property; + + if (state.property === 'window_detection') { + discoveryEntry.discovery_payload.icon = 'mdi:window-open-variant'; + } + } + } else if (expose.type === 'climate') { + const setpointProperties = ['occupied_heating_setpoint', 'current_heating_setpoint']; + const setpoint = expose.features.find((f) => setpointProperties.includes(f.name)); + assert(setpoint, 'No setpoint found'); + const temperature = expose.features.find((f) => f.name === 'local_temperature'); + assert(temperature, 'No temperature found'); + + discoveryEntry = { + type: 'climate', + object_id: expose.endpoint ? `climate_${expose.endpoint}` : 'climate', + discovery_payload: { + // Static + state_topic: false, + temperature_unit: 'C', + // Setpoint + temp_step: setpoint.value_step, + min_temp: setpoint.value_min.toString(), + max_temp: setpoint.value_max.toString(), + // Temperature + current_temperature_topic: true, + current_temperature_template: `{{ value_json.${temperature.property} }}`, + }, + }; + + const mode = expose.features.find((f) => f.name === 'system_mode'); + if (mode) { + discoveryEntry.discovery_payload.mode_state_topic = true; + discoveryEntry.discovery_payload.mode_state_template = `{{ value_json.${mode.property} }}`; + discoveryEntry.discovery_payload.modes = mode.values; + discoveryEntry.discovery_payload.mode_command_topic = true; + } + + const state = expose.features.find((f) => f.name === 'running_state'); + if (state) { + discoveryEntry.discovery_payload.action_topic = true; + discoveryEntry.discovery_payload.action_template = `{% set values = ` + + `{'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'}` + + ` %}{{ values[value_json.${state.property}] }}`; + } + + const coolingSetpoint = expose.features.find((f) => f.name === 'occupied_cooling_setpoint'); + if (coolingSetpoint) { + discoveryEntry.discovery_payload.temperature_low_command_topic = setpoint.name; + discoveryEntry.discovery_payload.temperature_low_state_template = + `{{ value_json.${setpoint.property} }}`; + discoveryEntry.discovery_payload.temperature_low_state_topic = true; + discoveryEntry.discovery_payload.temperature_high_command_topic = coolingSetpoint.name; + discoveryEntry.discovery_payload.temperature_high_state_template = + `{{ value_json.${coolingSetpoint.property} }}`; + discoveryEntry.discovery_payload.temperature_high_state_topic = true; + } else { + discoveryEntry.discovery_payload.temperature_command_topic = setpoint.name; + discoveryEntry.discovery_payload.temperature_state_template = + `{{ value_json.${setpoint.property} }}`; + discoveryEntry.discovery_payload.temperature_state_topic = true; + } + + const fanMode = expose.features.find((f) => f.name === 'fan_mode'); + if (fanMode) { + discoveryEntry.discovery_payload.fan_modes = fanMode.values; + discoveryEntry.discovery_payload.fan_mode_command_topic = true; + discoveryEntry.discovery_payload.fan_mode_state_template = + `{{ value_json.${fanMode.property} }}`; + discoveryEntry.discovery_payload.fan_mode_state_topic = true; + } + + const preset = expose.features.find((f) => f.name === 'preset'); + if (preset) { + discoveryEntry.discovery_payload.hold_modes = preset.values; + discoveryEntry.discovery_payload.hold_command_topic = true; + discoveryEntry.discovery_payload.hold_state_template = + `{{ value_json.${preset.property} }}`; + discoveryEntry.discovery_payload.hold_state_topic = true; + } + + const awayMode = expose.features.find((f) => f.name === 'away_mode'); + if (awayMode) { + discoveryEntry.discovery_payload.away_mode_command_topic = true; + discoveryEntry.discovery_payload.away_mode_state_topic = true; + discoveryEntry.discovery_payload.away_mode_state_template = + `{{ value_json.${awayMode.property} }}`; + } + + if (expose.endpoint) { + discoveryEntry.discovery_payload.state_topic_postfix = expose.endpoint; + } + } else if (expose.type === 'lock') { + assert(!expose.endpoint, `Endpoint not supported for lock type`); + const state = expose.features.find((f) => f.name === 'state'); + assert(state, 'No state found'); + discoveryEntry = { + type: 'lock', + object_id: 'lock', + discovery_payload: { + command_topic: true, + value_template: `{{ value_json.${state.property} }}`, + }, + }; + + if (state.property === 'keypad_lockout') { + // deprecated: keypad_lockout is messy, but changing is breaking + discoveryEntry.discovery_payload.payload_lock = state.value_on; + discoveryEntry.discovery_payload.payload_unlock = state.value_off; + discoveryEntry.discovery_payload.state_topic = true; + discoveryEntry.object_id = 'keypad_lock'; + } else if (state.property === 'child_lock') { + // deprecated: child_lock is messy, but changing is breaking + discoveryEntry.discovery_payload.payload_lock = state.value_on; + discoveryEntry.discovery_payload.payload_unlock = state.value_off; + discoveryEntry.discovery_payload.state_locked = 'LOCKED'; + discoveryEntry.discovery_payload.state_unlocked = 'UNLOCKED'; + discoveryEntry.discovery_payload.state_topic = true; + discoveryEntry.object_id = 'child_lock'; + } else { + discoveryEntry.discovery_payload.state_locked = state.value_on; + discoveryEntry.discovery_payload.state_unlocked = state.value_off; + } + + if (state.property !== 'state') { + discoveryEntry.discovery_payload.command_topic_postfix = state.property; + } + } else if (expose.type === 'cover') { + assert(!expose.endpoint, `Endpoint not supported for cover type`); + const hasPosition = expose.features.find((e) => e.name === 'position'); + const hasTilt = expose.features.find((e) => e.name === 'tilt'); + + discoveryEntry = { + type: 'cover', + object_id: 'cover', + discovery_payload: { + command_topic: true, + state_topic: !hasPosition, + }, + }; + + if (!hasPosition && !hasTilt) { + discoveryEntry.discovery_payload.optimistic = true; + } + + if (hasPosition) { + discoveryEntry.discovery_payload = {...discoveryEntry.discovery_payload, + value_template: '{{ value_json.position }}', + set_position_template: '{ "position": {{ position }} }', + set_position_topic: true, + position_topic: true, }; + } - const effect = def.exposes.find((e) => e.type === 'enum' && e.name === 'effect'); - if (effect) { - discoveryEntry.discovery_payload.effect = true; - discoveryEntry.discovery_payload.effect_list = effect.values; - } - } else if (expose.type === 'switch') { - const state = expose.features.find((f) => f.name === 'state'); - discoveryEntry = { - type: 'switch', - object_id: expose.endpoint ? `switch_${expose.endpoint}` : 'switch', - discovery_payload: { - payload_off: 'OFF', - payload_on: 'ON', - value_template: `{{ value_json.${state.property} }}`, - command_topic: true, - command_topic_prefix: expose.endpoint ? expose.endpoint : undefined, - }, + if (hasTilt) { + discoveryEntry.discovery_payload = {...discoveryEntry.discovery_payload, + tilt_command_topic: true, + tilt_status_topic: true, + tilt_status_template: '{{ value_json.tilt }}', }; + } + } else if (expose.type === 'fan') { + assert(!expose.endpoint, `Endpoint not supported for fan type`); + discoveryEntry = { + type: 'fan', + object_id: 'fan', + discovery_payload: { + state_topic: true, + state_value_template: '{{ value_json.fan_state }}', + command_topic: true, + command_topic_postfix: 'fan_state', + }, + }; - if (state.property === 'valve_detection' || state.property === 'window_detection') { - discoveryEntry.discovery_payload.command_topic_postfix = state.property; - discoveryEntry.discovery_payload.state_off = 'OFF'; - discoveryEntry.discovery_payload.state_on = 'ON'; - discoveryEntry.discovery_payload.state_topic = true; - discoveryEntry.object_id = state.property; + const speed = expose.features.find((e) => e.name === 'mode'); + if (speed) { + discoveryEntry.discovery_payload.speed_state_topic = true; + discoveryEntry.discovery_payload.speed_command_topic = true; + discoveryEntry.discovery_payload.speed_value_template = '{{ value_json.fan_mode }}'; + discoveryEntry.discovery_payload.speeds = speed.values; + } + } else if (expose.type === 'binary') { + const lookup = { + occupancy: {device_class: 'motion'}, + battery_low: {device_class: 'battery'}, + water_leak: {device_class: 'moisture'}, + vibration: {device_class: 'vibration'}, + contact: {device_class: 'door'}, + smoke: {device_class: 'smoke'}, + gas: {device_class: 'gas'}, + carbon_monoxide: {device_class: 'safety'}, + presence: {device_class: 'presence'}, + }; - if (state.property === 'window_detection') { - discoveryEntry.discovery_payload.icon = 'mdi:window-open-variant'; - } - } - } else if (expose.type === 'climate') { - const setpointProperties = ['occupied_heating_setpoint', 'current_heating_setpoint']; - const setpoint = expose.features.find((f) => setpointProperties.includes(f.name)); - assert(setpoint, 'No setpoint found'); - const temperature = expose.features.find((f) => f.name === 'local_temperature'); - assert(temperature, 'No temperature found'); + discoveryEntry = { + type: 'binary_sensor', + object_id: expose.endpoint ? `${expose.name}_${expose.endpoint}` : `${expose.name}`, + discovery_payload: { + value_template: `{{ value_json.${expose.property} }}`, + payload_on: expose.value_on, + payload_off: expose.value_off, + ...(lookup[expose.name] || {}), + }, + }; + } else if (expose.type === 'numeric') { + const lookup = { + battery: {device_class: 'battery'}, + temperature: {device_class: 'temperature'}, + humidity: {device_class: 'humidity'}, + illuminance_lux: {device_class: 'illuminance'}, + illuminance: {device_class: 'illuminance'}, + soil_moisture: {icon: 'mdi:water-percent'}, + position: {icon: 'mdi:valve'}, + pressure: {device_class: 'pressure'}, + power: {device_class: 'power'}, + linkquality: {icon: 'mdi:signal'}, + current: {device_class: 'current'}, + voltage: {device_class: 'voltage'}, + current_phase_b: {device_class: 'current'}, + voltage_phase_b: {device_class: 'voltage'}, + current_phase_c: {device_class: 'current'}, + voltage_phase_c: {device_class: 'voltage'}, + energy: {device_class: 'energy'}, + smoke_density: {icon: 'mdi:google-circles-communities'}, + gas_density: {icon: 'mdi:google-circles-communities'}, + pm25: {icon: 'mdi:air-filter'}, + pm10: {icon: 'mdi:air-filter'}, + voc: {icon: 'mdi:air-filter'}, + aqi: {icon: 'mdi:air-filter'}, + hcho: {icon: 'mdi:air-filter'}, + requested_brightness_level: {icon: 'mdi:brightness-5'}, + requested_brightness_percent: {icon: 'mdi:brightness-5'}, + eco2: {icon: 'mdi:molecule-co2'}, + co2: {icon: 'mdi:molecule-co2'}, + local_temperature: {device_class: 'temperature'}, + }; - discoveryEntry = { - type: 'climate', - object_id: expose.endpoint ? `climate_${expose.endpoint}` : 'climate', - discovery_payload: { - // Static - state_topic: false, - temperature_unit: 'C', - // Setpoint - temp_step: setpoint.value_step, - min_temp: setpoint.value_min.toString(), - max_temp: setpoint.value_max.toString(), - // Temperature - current_temperature_topic: true, - current_temperature_template: `{{ value_json.${temperature.property} }}`, - }, - }; - - const mode = expose.features.find((f) => f.name === 'system_mode'); - if (mode) { - discoveryEntry.discovery_payload.mode_state_topic = true; - discoveryEntry.discovery_payload.mode_state_template = `{{ value_json.${mode.property} }}`; - discoveryEntry.discovery_payload.modes = mode.values; - discoveryEntry.discovery_payload.mode_command_topic = true; - } - - const state = expose.features.find((f) => f.name === 'running_state'); - if (state) { - discoveryEntry.discovery_payload.action_topic = true; - discoveryEntry.discovery_payload.action_template = `{% set values = ` + - `{'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'}` + - ` %}{{ values[value_json.${state.property}] }}`; - } - - const coolingSetpoint = expose.features.find((f) => f.name === 'occupied_cooling_setpoint'); - if (coolingSetpoint) { - discoveryEntry.discovery_payload.temperature_low_command_topic = setpoint.name; - discoveryEntry.discovery_payload.temperature_low_state_template = - `{{ value_json.${setpoint.property} }}`; - discoveryEntry.discovery_payload.temperature_low_state_topic = true; - discoveryEntry.discovery_payload.temperature_high_command_topic = coolingSetpoint.name; - discoveryEntry.discovery_payload.temperature_high_state_template = - `{{ value_json.${coolingSetpoint.property} }}`; - discoveryEntry.discovery_payload.temperature_high_state_topic = true; - } else { - discoveryEntry.discovery_payload.temperature_command_topic = setpoint.name; - discoveryEntry.discovery_payload.temperature_state_template = - `{{ value_json.${setpoint.property} }}`; - discoveryEntry.discovery_payload.temperature_state_topic = true; - } - - const fanMode = expose.features.find((f) => f.name === 'fan_mode'); - if (fanMode) { - discoveryEntry.discovery_payload.fan_modes = fanMode.values; - discoveryEntry.discovery_payload.fan_mode_command_topic = true; - discoveryEntry.discovery_payload.fan_mode_state_template = - `{{ value_json.${fanMode.property} }}`; - discoveryEntry.discovery_payload.fan_mode_state_topic = true; - } - - const preset = expose.features.find((f) => f.name === 'preset'); - if (preset) { - discoveryEntry.discovery_payload.hold_modes = preset.values; - discoveryEntry.discovery_payload.hold_command_topic = true; - discoveryEntry.discovery_payload.hold_state_template = - `{{ value_json.${preset.property} }}`; - discoveryEntry.discovery_payload.hold_state_topic = true; - } - - const awayMode = expose.features.find((f) => f.name === 'away_mode'); - if (awayMode) { - discoveryEntry.discovery_payload.away_mode_command_topic = true; - discoveryEntry.discovery_payload.away_mode_state_topic = true; - discoveryEntry.discovery_payload.away_mode_state_template = - `{{ value_json.${awayMode.property} }}`; - } - - if (expose.endpoint) { - discoveryEntry.discovery_payload.state_topic_postfix = expose.endpoint; - } - } else if (expose.type === 'lock') { - assert(!expose.endpoint, `Endpoint not supported for lock type`); - const state = expose.features.find((f) => f.name === 'state'); - assert(state, 'No state found'); - discoveryEntry = { - type: 'lock', - object_id: 'lock', - discovery_payload: { - command_topic: true, - value_template: `{{ value_json.${state.property} }}`, - }, - }; - - if (state.property === 'keypad_lockout') { - // deprecated: keypad_lockout is messy, but changing is breaking - discoveryEntry.discovery_payload.payload_lock = state.value_on; - discoveryEntry.discovery_payload.payload_unlock = state.value_off; - discoveryEntry.discovery_payload.state_topic = true; - discoveryEntry.object_id = 'keypad_lock'; - } else if (state.property === 'child_lock') { - // deprecated: child_lock is messy, but changing is breaking - discoveryEntry.discovery_payload.payload_lock = state.value_on; - discoveryEntry.discovery_payload.payload_unlock = state.value_off; - discoveryEntry.discovery_payload.state_locked = 'LOCKED'; - discoveryEntry.discovery_payload.state_unlocked = 'UNLOCKED'; - discoveryEntry.discovery_payload.state_topic = true; - discoveryEntry.object_id = 'child_lock'; - } else { - discoveryEntry.discovery_payload.state_locked = state.value_on; - discoveryEntry.discovery_payload.state_unlocked = state.value_off; - } - - if (state.property !== 'state') { - discoveryEntry.discovery_payload.command_topic_postfix = state.property; - } - } else if (expose.type === 'cover') { - assert(!expose.endpoint, `Endpoint not supported for cover type`); - const hasPosition = expose.features.find((e) => e.name === 'position'); - const hasTilt = expose.features.find((e) => e.name === 'tilt'); - - discoveryEntry = { - type: 'cover', - object_id: 'cover', - discovery_payload: { - command_topic: true, - state_topic: !hasPosition, - }, - }; - - if (!hasPosition && !hasTilt) { - discoveryEntry.discovery_payload.optimistic = true; - } - - if (hasPosition) { - discoveryEntry.discovery_payload = {...discoveryEntry.discovery_payload, - value_template: '{{ value_json.position }}', - set_position_template: '{ "position": {{ position }} }', - set_position_topic: true, - position_topic: true, - }; - } - - if (hasTilt) { - discoveryEntry.discovery_payload = {...discoveryEntry.discovery_payload, - tilt_command_topic: true, - tilt_status_topic: true, - tilt_status_template: '{{ value_json.tilt }}', - }; - } - } else if (expose.type === 'fan') { - assert(!expose.endpoint, `Endpoint not supported for fan type`); - discoveryEntry = { - type: 'fan', - object_id: 'fan', - discovery_payload: { - state_topic: true, - state_value_template: '{{ value_json.fan_state }}', - command_topic: true, - command_topic_postfix: 'fan_state', - }, - }; - - const speed = expose.features.find((e) => e.name === 'mode'); - if (speed) { - discoveryEntry.discovery_payload.speed_state_topic = true; - discoveryEntry.discovery_payload.speed_command_topic = true; - discoveryEntry.discovery_payload.speed_value_template = '{{ value_json.fan_mode }}'; - discoveryEntry.discovery_payload.speeds = speed.values; - } - } else if (expose.type === 'binary') { + discoveryEntry = { + type: 'sensor', + object_id: expose.endpoint ? `${expose.name}_${expose.endpoint}` : `${expose.name}`, + discovery_payload: { + unit_of_measurement: expose.unit ? expose.unit : '-', + value_template: `{{ value_json.${expose.property} }}`, + ...lookup[expose.name], + }, + }; + } else if (expose.type === 'enum' || expose.type === 'text' || expose.type === 'composite') { + const ACCESS_STATE = 1; + if (expose.access & ACCESS_STATE) { const lookup = { - occupancy: {device_class: 'motion'}, - battery_low: {device_class: 'battery'}, - water_leak: {device_class: 'moisture'}, - vibration: {device_class: 'vibration'}, - contact: {device_class: 'door'}, - smoke: {device_class: 'smoke'}, - gas: {device_class: 'gas'}, - carbon_monoxide: {device_class: 'safety'}, - presence: {device_class: 'presence'}, + action: {icon: 'mdi:gesture-double-tap'}, }; - assert(!expose.endpoint, `Endpoint not supported for binary type`); - - discoveryEntry = { - type: 'binary_sensor', - object_id: expose.name, - discovery_payload: { - value_template: `{{ value_json.${expose.property} }}`, - payload_on: expose.value_on, - payload_off: expose.value_off, - ...(lookup[expose.name] || {}), - }, - }; - } else if (expose.type === 'numeric') { - const lookup = { - battery: {device_class: 'battery'}, - temperature: {device_class: 'temperature'}, - humidity: {device_class: 'humidity'}, - illuminance_lux: {device_class: 'illuminance'}, - illuminance: {device_class: 'illuminance'}, - soil_moisture: {icon: 'mdi:water-percent'}, - position: {icon: 'mdi:valve'}, - pressure: {device_class: 'pressure'}, - power: {icon: 'mdi:flash'}, - linkquality: {icon: 'mdi:signal'}, - current: {icon: 'mdi:current-ac'}, - voltage: {icon: 'mdi:alpha-v'}, - current_phase_b: {icon: 'mdi:current-ac'}, - voltage_phase_b: {icon: 'mdi:alpha-v'}, - current_phase_c: {icon: 'mdi:current-ac'}, - voltage_phase_c: {icon: 'mdi:alpha-v'}, - energy: {icon: 'mdi:power-plug'}, - smoke_density: {icon: 'mdi:google-circles-communities'}, - gas_density: {icon: 'mdi:google-circles-communities'}, - pm25: {icon: 'mdi:air-filter'}, - pm10: {icon: 'mdi:air-filter'}, - voc: {icon: 'mdi:air-filter'}, - aqi: {icon: 'mdi:air-filter'}, - hcho: {icon: 'mdi:air-filter'}, - requested_brightness_level: {icon: 'mdi:brightness-5'}, - requested_brightness_percent: {icon: 'mdi:brightness-5'}, - eco2: {icon: 'mdi:molecule-co2'}, - co2: {icon: 'mdi:molecule-co2'}, - local_temperature: {device_class: 'temperature'}, - }; - - assert(!expose.endpoint, `Endpoint not supported for numeric type`); - discoveryEntry = { type: 'sensor', - object_id: expose.name, + object_id: expose.property, discovery_payload: { - unit_of_measurement: expose.unit ? expose.unit : '-', value_template: `{{ value_json.${expose.property} }}`, ...lookup[expose.name], }, }; - } else if (expose.type === 'enum' || expose.type === 'text' || expose.type === 'composite') { - const ACCESS_STATE = 1; - if (expose.access & ACCESS_STATE) { - const lookup = { - action: {icon: 'mdi:gesture-double-tap'}, - }; - - discoveryEntry = { - type: 'sensor', - object_id: expose.property, - discovery_payload: { - value_template: `{{ value_json.${expose.property} }}`, - ...lookup[expose.name], - }, - }; - } - } else { - throw new Error(`Unsupported exposes type: '${expose.type}'`); - } - - if (discoveryEntry) { - this.mapping[def.model].push(discoveryEntry); } + } else { + throw new Error(`Unsupported exposes type: '${expose.type}'`); + } + + if (discoveryEntry) { + this.mapping[def.model].push(discoveryEntry); } - } else { - logger.warn(`Supported device '${def.model}' has no Home Assistant mapping`); } } @@ -433,10 +428,10 @@ class HomeAssistant extends Extension { const endpoint = match[1]; const endpointRegExp = new RegExp(`(.*)_${endpoint}`); const payload = {}; - for (const key of Object.keys(data.payload)) { + for (const key of Object.keys(data.messagePayload)) { const keyMatch = endpointRegExp.exec(key); if (keyMatch) { - payload[keyMatch[1]] = data.payload[key]; + payload[keyMatch[1]] = data.messagePayload[key]; } } @@ -453,7 +448,8 @@ class HomeAssistant extends Extension { * https://github.com/Koenkk/zigbee2mqtt/issues/959#issuecomment-480341347 */ if (settings.get().advanced.homeassistant_legacy_triggers) { - const keys = ['action', 'click'].filter((k) => data.payload.hasOwnProperty(k) && data.payload[k] !== ''); + const keys = ['action', 'click'].filter((k) => + data.messagePayload.hasOwnProperty(k) && data.messagePayload[k] !== ''); for (const key of keys) { this.publishEntityState(data.entity.device.ieeeAddr, {[key]: ''}); } @@ -466,9 +462,9 @@ class HomeAssistant extends Extension { * and republish it to zigbee2mqtt/my_devic/action */ if (data.entity.definition) { - const keys = ['action', 'click'].filter((k) => data.payload[k] && data.payload[k] !== ''); + const keys = ['action', 'click'].filter((k) => data.messagePayload[k] && data.messagePayload[k] !== ''); for (const key of keys) { - const value = data.payload[key].toString(); + const value = data.messagePayload[key].toString(); await this.publishDeviceTriggerDiscover(data.entity, key, value); await this.mqtt.publish(`${data.entity.name}/${key}`, value, {}); } @@ -499,7 +495,7 @@ class HomeAssistant extends Extension { ]; for (const entry of mockedValues) { - if (entry.condition && !data.payload.hasOwnProperty(entry.property)) { + if (entry.condition && !data.messagePayload.hasOwnProperty(entry.property)) { logger.debug(`Mocking '${entry.property}' value for Home Assistant`); this.publishEntityState(data.entity.device.ieeeAddr, {[entry.property]: entry.value}); } @@ -583,8 +579,10 @@ class HomeAssistant extends Extension { configs = JSON.parse(JSON.stringify(configs)); if (resolvedEntity.settings.homeassistant) { + const s = resolvedEntity.settings.homeassistant; + configs = configs.filter((config) => !s.hasOwnProperty(config.object_id) || s[config.object_id] != null); configs.forEach((config) => { - const configOverride = resolvedEntity.settings.homeassistant[config.object_id]; + const configOverride = s[config.object_id]; if (configOverride) { config.object_id = configOverride.object_id || config.object_id; config.type = configOverride.type || config.type; @@ -869,6 +867,11 @@ class HomeAssistant extends Extension { } async publishDeviceTriggerDiscover(entity, key, value, force=false) { + if (entity.settings.hasOwnProperty('homeassistant') && + (entity.settings.homeassistant == null || entity.settings.homeassistant.device_automation == null)) { + return; + } + const device = entity.device; if (!this.discoveredTriggers[device.ieeeAddr]) { this.discoveredTriggers[device.ieeeAddr] = new Set(); diff --git a/lib/extension/report.js b/lib/extension/legacy/report.js similarity index 62% rename from lib/extension/report.js rename to lib/extension/legacy/report.js index 7d419dd56..3339fe9fb 100644 --- a/lib/extension/report.js +++ b/lib/extension/legacy/report.js @@ -1,11 +1,9 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); -const logger = require('../util/logger'); -const settings = require('../util/settings'); +const logger = require('../../util/logger'); +const settings = require('../../util/settings'); const ZNLDP12LM = zigbeeHerdsmanConverters.devices.find((d) => d.model === 'ZNLDP12LM'); -const utils = require('../util/utils'); -const Extension = require('./extension'); -const debounce = require('debounce'); -const ZigbeeHerdsman = require('zigbee-herdsman'); +const utils = require('../../util/utils'); +const Extension = require('../extension'); const defaultConfiguration = { minimumReportInterval: 3, maximumReportInterval: 300, reportableChange: 1, @@ -60,64 +58,11 @@ const clusters = { ], }; -const pollOnMessage = [ - { - // Key is used this.pollDebouncers uniqueness - key: 1, - // On messages that have the cluster and type of below - cluster: { - manuSpecificPhilips: [ - {type: 'commandHueNotification', data: {button: 2}}, - {type: 'commandHueNotification', data: {button: 3}}, - ], - genLevelCtrl: [ - {type: 'commandStep', data: {}}, - {type: 'commandStepWithOnOff', data: {}}, - {type: 'commandStop', data: {}}, - {type: 'commandMoveWithOnOff', data: {}}, - {type: 'commandStopWithOnOff', data: {}}, - {type: 'commandMove', data: {}}, - ], - }, - // Read the following attributes - read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']}, - // When the bound devices/members of group have the following manufacturerIDs - manufacturerIDs: [ - ZigbeeHerdsman.Zcl.ManufacturerCode.Philips, - ZigbeeHerdsman.Zcl.ManufacturerCode.ATMEL, - ZigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD, - ZigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT, - ], - }, - { - key: 2, - cluster: { - genOnOff: [ - {type: 'commandOn', data: {}}, - {type: 'commandOff', data: {}}, - {type: 'commandOffWithEffect', data: {}}, - ], - manuSpecificPhilips: [ - {type: 'commandHueNotification', data: {button: 1}}, - {type: 'commandHueNotification', data: {button: 4}}, - ], - }, - read: {cluster: 'genOnOff', attributes: ['onOff']}, - manufacturerIDs: [ - ZigbeeHerdsman.Zcl.ManufacturerCode.Philips, - ZigbeeHerdsman.Zcl.ManufacturerCode.ATMEL, - ZigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD, - ZigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT, - ], - }, -]; - class Report extends Extension { constructor(zigbee, mqtt, state, publishEntityState, eventBus) { super(zigbee, mqtt, state, publishEntityState, eventBus); this.queue = new Set(); this.failed = new Set(); - this.pollDebouncers = {}; this.enabled = settings.get().advanced.report; } @@ -242,61 +187,6 @@ class Report extends Extension { if (this.shouldSetupReporting(resolvedEntity, type)) { await this.setupReporting(resolvedEntity); } - - if (type === 'message') { - this.poll(data); - } - } - - poll(message) { - /** - * This method poll bound endpoints and group members for state changes. - * - * A use case is e.g. a Hue Dimmer switch bound to a Hue bulb. - * Hue bulbs only report their on/off state. - * When dimming the bulb via the dimmer switch the state is therefore not reported. - * When we receive a message from a Hue dimmer we read the brightness from the bulb (if bound). - */ - const polls = pollOnMessage.filter((p) => - p.cluster[message.cluster] && p.cluster[message.cluster].find((c) => c.type === message.type && - utils.equalsPartial(message.data, c.data)), - ); - - if (polls.length) { - let toPoll = []; - - // Add bound devices - toPoll = toPoll.concat([].concat(...message.device.endpoints.map((e) => - e.binds.map((e) => e).filter((e) => e.target)))); - toPoll = toPoll.filter((e) => e.target.constructor.name === 'Endpoint'); - toPoll = toPoll.filter((e) => e.target.getDevice().type !== 'Coordinator'); - toPoll = toPoll.map((e) => e.target); - - // If message is published to a group, add members of the group - const group = message.groupID !== 0 ? this.zigbee.getGroupByID(message.groupID) : null; - if (group) { - toPoll = toPoll.concat(group.members); - } - - toPoll = new Set(toPoll); - - for (const endpoint of toPoll) { - for (const poll of polls) { - if (!poll.manufacturerIDs.includes(endpoint.getDevice().manufacturerID)) { - continue; - } - - const key = `${endpoint.deviceIeeeAddress}_${endpoint.ID}_${poll.key}`; - if (!this.pollDebouncers[key]) { - this.pollDebouncers[key] = debounce(async () => { - await endpoint.read(poll.read.cluster, poll.read.attributes); - }, 1000); - } - - this.pollDebouncers[key](); - } - } - } } } diff --git a/lib/extension/networkMap.js b/lib/extension/networkMap.js index e1d8f057a..83b8e7bfa 100644 --- a/lib/extension/networkMap.js +++ b/lib/extension/networkMap.js @@ -212,6 +212,7 @@ class NetworkMap extends Extension { for (const device of devices.filter((d) => d.type != 'EndDevice')) { failed.set(device, []); + await utils.sleep(1); // sleep 1 second between each scan to reduce stress on network. const resolvedEntity = this.zigbee.resolveEntity(device); try { const result = await device.lqi(); @@ -244,7 +245,7 @@ class NetworkMap extends Extension { model: resolvedEntity.definition.model, vendor: resolvedEntity.definition.vendor, description: resolvedEntity.definition.description, - supports: Array.from(new Set((resolvedEntity.definition.exposes || []).map((e) => { + supports: Array.from(new Set((resolvedEntity.definition.exposes).map((e) => { return e.hasOwnProperty('name') ? e.name : `${e.type} (${e.features.map((f) => f.name).join(', ')})`; diff --git a/lib/util/logger.js b/lib/util/logger.js index 0c590b9f4..b231e39f1 100644 --- a/lib/util/logger.js +++ b/lib/util/logger.js @@ -21,10 +21,14 @@ if (output.includes('file')) { fx.mkdirSync(directory); } +const z2mToWinstonLevel = (level) => level === 'warn' ? 'warning' : level; +const winstonToZ2mLevel = (level) => level === 'warning' ? 'warn' : level; + // Determine the log level. -const level = settings.get().advanced.log_level; +let level = settings.get().advanced.log_level; const validLevels = ['info', 'error', 'warn', 'debug']; assert(validLevels.includes(level), `'${level}' is not a valid log_level, use one of '${validLevels.join(', ')}'`); +level = z2mToWinstonLevel(level); const levelWithCompensatedLength = { 'info': 'info ', @@ -45,7 +49,7 @@ const transportsToUse = [ winston.format.timestamp({format: timestampFormat}), winston.format.printf(/* istanbul ignore next */(info) => { let {timestamp, level, message} = info; - level = level === 'warning' ? 'warn' : level; + level = winstonToZ2mLevel(level); const prefix = colorizer.colorize(level, `Zigbee2MQTT:${levelWithCompensatedLength[level]}`); return `${prefix} ${timestamp.split('.')[0]}: ${message}`; }), @@ -63,7 +67,7 @@ const transportFileOptions = { winston.format.timestamp({format: timestampFormat}), winston.format.printf(/* istanbul ignore next */(info) => { let {timestamp, level, message} = info; - level = level === 'warning' ? 'warn' : level; + level = winstonToZ2mLevel(level); return `${levelWithCompensatedLength[level]} ${timestamp.split('.')[0]}: ${message}`; }), ), @@ -129,11 +133,16 @@ function logOutput() { } } +logger.addTransport = (transport) => { + transport.level = transportsToUse[0].level; + logger.add(transport); +}; logger.cleanup = cleanup; logger.logOutput = logOutput; -logger.getLevel = () => transportsToUse[0].level; +logger.getLevel = () => winstonToZ2mLevel(transportsToUse[0].level); logger.setLevel = (level) => { - transportsToUse.forEach((transport) => transport.level = level); + level = z2mToWinstonLevel(level); + logger.transports.forEach((transport) => transport.level = level); }; // winston.config.syslog.levels doesnt have warn, but is required for syslog. diff --git a/lib/util/settings.js b/lib/util/settings.js index 276b8b8e1..fcf4b2abb 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -7,6 +7,7 @@ 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 defaults = { passlist: [], @@ -137,172 +138,6 @@ const defaults = { external_converters: [], }; -const schema = { - type: 'object', - properties: { - device_options: {type: 'object'}, - homeassistant: {type: 'boolean'}, - permit_join: {type: 'boolean'}, - mqtt: { - type: 'object', - properties: { - base_topic: {type: 'string'}, - server: {type: 'string'}, - keepalive: {type: 'number'}, - ca: {type: 'string'}, - key: {type: 'string'}, - cert: {type: 'string'}, - user: {type: 'string'}, - password: {type: 'string'}, - client_id: {type: 'string'}, - reject_unauthorized: {type: 'boolean'}, - include_device_information: {type: 'boolean'}, - version: {type: 'number'}, - force_disable_retain: {type: 'boolean'}, - }, - required: ['base_topic', 'server'], - }, - serial: { - type: 'object', - properties: { - port: {type: ['string', 'null']}, - disable_led: {type: 'boolean'}, - adapter: {type: 'string', enum: ['deconz', 'zstack', 'zigate']}, - }, - }, - blocklist: {type: 'array', items: {type: 'string'}}, - passlist: {type: 'array', items: {type: 'string'}}, - // Deprecated: use block/passlist - whitelist: {type: 'array', items: {type: 'string'}}, - ban: {type: 'array', items: {type: 'string'}}, - experimental: { - type: 'object', - properties: { - transmit_power: {type: 'number'}, - output: {type: 'string', enum: ['attribute_and_json', 'attribute', 'json']}, - }, - }, - advanced: { - type: 'object', - properties: { - legacy_api: {type: 'boolean'}, - pan_id: {type: 'number'}, - ext_pan_id: {type: 'array', items: {type: 'number'}}, - channel: {type: 'number', minimum: 11, maximum: 26}, - cache_state: {type: 'boolean'}, - cache_state_persistent: {type: 'boolean'}, - cache_state_send_on_startup: {type: 'boolean'}, - log_rotation: {type: 'boolean'}, - log_level: {type: 'string', enum: ['info', 'warn', 'error', 'debug']}, - log_output: {type: 'array', items: {type: 'string'}}, - log_directory: {type: 'string'}, - log_file: {type: 'string'}, - baudrate: {type: 'number'}, - rtscts: {type: 'boolean'}, - soft_reset_timeout: {type: 'number', minimum: 0}, - network_key: {type: ['array', 'string'], items: {type: 'number'}}, - last_seen: {type: 'string', enum: ['disable', 'ISO_8601', 'ISO_8601_local', 'epoch']}, - elapsed: {type: 'boolean'}, - availability_timeout: {type: 'number', minimum: 0}, - availability_blocklist: {type: 'array', items: {type: 'string'}}, - availability_passlist: {type: 'array', items: {type: 'string'}}, - // Deprecated, use block/passlist - availability_blacklist: {type: 'array', items: {type: 'string'}}, - availability_whitelist: {type: 'array', items: {type: 'string'}}, - report: {type: 'boolean'}, - homeassistant_discovery_topic: {type: 'string'}, - homeassistant_status_topic: {type: 'string'}, - timestamp_format: {type: 'string'}, - adapter_concurrent: {type: 'number'}, - adapter_delay: {type: 'number'}, - }, - }, - map_options: { - type: 'object', - properties: { - graphviz: { - type: 'object', - properties: { - colors: { - type: 'object', - properties: { - fill: { - type: 'object', - properties: { - enddevice: {type: 'string'}, - coordinator: {type: 'string'}, - router: {type: 'string'}, - }, - }, - font: { - type: 'object', - properties: { - enddevice: {type: 'string'}, - coordinator: {type: 'string'}, - router: {type: 'string'}, - }, - }, - line: { - type: 'object', - properties: { - active: {type: 'string'}, - inactive: {type: 'string'}, - }, - }, - }, - }, - }, - }, - }, - }, - devices: { - type: 'object', - propertyNames: { - pattern: '^0x[\\d\\w]{16}$', - }, - patternProperties: { - '^.*$': { - type: 'object', - properties: { - friendly_name: {type: 'string'}, - retain: {type: 'boolean'}, - retention: {type: 'number'}, - qos: {type: 'number'}, - filtered_attributes: {type: 'array', items: {type: 'string'}}, - }, - required: ['friendly_name'], - }, - }, - }, - groups: { - type: 'object', - propertyNames: { - pattern: '^[\\w].*$', - }, - patternProperties: { - '^.*$': { - type: 'object', - properties: { - friendly_name: {type: 'string'}, - retain: {type: 'boolean'}, - devices: {type: 'array', items: {type: 'string'}}, - optimistic: {type: 'boolean'}, - qos: {type: 'number'}, - filtered_attributes: {type: 'array', items: {type: 'string'}}, - }, - required: ['friendly_name'], - }, - }, - }, - }, - external_converters: { - type: 'array', - items: {type: 'string'}, - }, - required: ['homeassistant', 'permit_join', 'mqtt'], -}; - - let _settings; let _settingsWithDefaults; @@ -314,7 +149,12 @@ function write() { const actual = yaml.read(file); // In case the setting is defined in a separte file (e.g. !secret network_key) update it there. - for (const path of [['mqtt', 'user'], ['mqtt', 'password'], ['advanced', 'network_key']]) { + for (const path of [ + ['mqtt', 'user'], + ['mqtt', 'password'], + ['advanced', 'network_key'], + ['frontend', 'auth_token'], + ]) { if (actual[path[0]] && actual[path[0]][path[1]]) { const match = /!(.*) (.*)/g.exec(actual[path[0]][path[1]]); if (match) { @@ -378,6 +218,11 @@ function validate() { errors.push(`advanced.network_key: should be array or 'GENERATE' (is '${_settings.advanced.network_key}')`); } + if (_settings.advanced && _settings.advanced.pan_id && typeof _settings.advanced.pan_id === 'string' && + _settings.advanced.pan_id !== 'GENERATE') { + errors.push(`advanced.pan_id: should be number or 'GENERATE' (is '${_settings.advanced.pan_id}')`); + } + // Verify that all friendly names are unique const names = []; const check = (name) => { @@ -439,6 +284,10 @@ function read() { s.advanced.network_key = interpetValue(s.advanced.network_key); } + if (s.frontend && s.frontend.auth_token) { + s.frontend.auth_token = interpetValue(s.frontend.auth_token); + } + // Read devices/groups configuration from separate file if specified. const readDevicesOrGroups = (type) => { if (typeof s[type] === 'string' || Array.isArray(s[type])) { @@ -801,6 +650,7 @@ module.exports = { removeDeviceFromGroup, changeEntityOptions, changeFriendlyName, + schema, // For tests only _write: write, diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json new file mode 100644 index 000000000..e653ebed3 --- /dev/null +++ b/lib/util/settings.schema.json @@ -0,0 +1,562 @@ +{ + "type": "object", + "properties": { + "device_options": { + "type": "object" + }, + "homeassistant": { + "title": "Homeassistant integration", + "type": "boolean", + "description": "Home Assistant integration (MQTT discovery)", + "default": false + }, + "permit_join": { + "type": "boolean", + "default": false, + "title": "Permit join", + "description": "Allow new devices to join. WARNING: Disable this after all devices have been paired!" + }, + "external_converters": { + "type": "array", + "title": "External converters", + "items": { + "type": "string" + }, + "examples": ["DIYRuZ_FreePad.js"] + }, + "mqtt": { + "type": "object", + "title": "MQTT settings", + "properties": { + "base_topic": { + "type": "string", + "title": "Base topic", + "description": "MQTT base topic for Zigbee2MQTT MQTT messages", + "examples": ["zigbee2mqtt"] + }, + "server": { + "type": "string", + "title": "MQTT server", + "description": "MQTT server URL (use mqtts:// for SSL/TLS connection)", + "examples": ["mqtt://localhost:1883"] + }, + "keepalive": { + "type": "number", + "title": "Keepalive", + "description": "MQTT keepalive in second", + "default": 60 + }, + "ca": { + "type": "string", + "title": "Certificate authority", + "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", + "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", + "examples": ["/etc/ssl/mqtt-client.crt"] + }, + "user": { + "type": "string", + "title": "User", + "description": "MQTT server authentication user", + "examples": ["johnnysilverhand"] + }, + "password": { + "type": "string", + "title": "Password", + "description": "MQTT server authentication password", + "examples": ["ILOVEPELMENI"] + }, + "client_id": { + "type": "string", + "title": "Client ID", + "description": "MQTT client ID", + "examples": ["MY_CLIENT_ID"] + }, + "reject_unauthorized": { + "type": "boolean", + "title": "Reject unauthorized", + "description": "Disable self-signed SSL certificate", + "default": true + }, + "include_device_information": { + "type": "boolean", + "title": "Include device information", + "description": "Include device information to mqtt messages", + "default": false + }, + "version": { + "type": ["number", "null"], + "title": "Version", + "description": "MQTT protocol version", + "default": 4 + }, + "force_disable_retain": { + "type": "boolean", + "title": "Force disable retain", + "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 + } + }, + "required": ["base_topic", "server"] + }, + "serial": { + "type": "object", + "title": "Serial settings", + "properties": { + "port": { + "type": ["string", "null"], + "title": "Port", + "description": "Location of the adapter. To autodetect the port, set null", + "examples": ["/dev/ttyACM0"] + }, + "disable_led": { + "type": "boolean", + "title": "Disable led", + "description": "Disable LED of the adapter if supported", + "default": false + }, + "adapter": { + "type": "string", + "enum": ["deconz", "zstack", "zigate"], + "title": "Adapter", + "description": "Adapter type, not needed unless you are experiencing problems" + } + } + }, + "blocklist": { + "title": "Blocklist", + "description": "Block devices from the network (by ieeeAddr)", + "type": "array", + "items": { + "type": "string" + } + }, + "passlist": { + "title": "Passlist", + "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": { + "type": "string" + } + }, + "whitelist": { + "type": "array", + "title": "Whitelist (deprecated)", + "items": { + "type": "string" + } + }, + "ban": { + "type": "array", + "title": "Ban (deprecated)", + "items": { + "type": "string" + } + }, + "experimental": { + "type": "object", + "title": "Experimental options", + "properties": { + "transmit_power": { + "type": ["number", "null"], + "title": "Transmit power" + }, + "output": { + "type": "string", + "enum": ["attribute_and_json", "attribute", "json"], + "title": "MQTT output type", + "description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)" + } + } + }, + "advanced": { + "type": "object", + "title": "Advanced settings", + "properties": { + "legacy_api": { + "type": "boolean", + "title": "Legacy API", + "description": "Disables the legacy api (false = disable)", + "default": true + }, + "pan_id": { + "oneOf": [{ + "type": "string", + "title": "Pan ID (string)" + }, + { + "type": "number", + "title": "Pan ID (number)" + } + ], + "title": "Pan ID", + "description": "ZigBee pan ID" + }, + "ext_pan_id": { + "type": "array", + "items": { + "type": "number" + }, + "title": "Ext Pan ID", + "description": "Zigbee extended pan ID" + }, + "channel": { + "type": "number", + "minimum": 11, + "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)" + }, + "cache_state": { + "type": "boolean", + "title": "Cache state", + "description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant", + "default": true + }, + "cache_state_persistent": { + "type": "boolean", + "title": "Chache state persistent", + "description": "Persist cached state, only used when Cache state(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)", + "default": true + }, + "log_rotation": { + "type": "boolean", + "title": "Log rotation", + "description": "Log rotation", + "default": true + }, + "log_level": { + "type": "string", + "enum": ["info", "warn", "error", "debug"], + "title": "Log level", + "description": "Logging level", + "default": "info" + }, + "log_output": { + "type": "array", + "items": { + "type": "string", + "enum": ["console", "file", "syslog"] + }, + "title": "Log output", + "description": "Output location of the log, leave empty to supress logging" + }, + "log_directory": { + "type": "string", + "title": "Log directory", + "description": "Location of log directory", + "examples": ["data/log/%TIMESTAMP%"] + }, + "log_file": { + "type": "string", + "title": "Log file", + "description": "Log file name, can also contain timestamp", + "examples": ["zigbee2mqtt_%TIMESTAMP%.log"], + "default": "log.txt" + }, + "baudrate": { + "type": "number", + "title": "Baudrate", + "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 + }, + "soft_reset_timeout": { + "type": "number", + "minimum": 0, + "title": "Soft reset timeout (deprecated)", + "description": "Soft reset ZNP after timeout", + "readOnly": true + }, + "network_key": { + "oneOf": [{ + "type": "string", + "title": "Network key(string)" + }, + { + "type": "array", + "items": { + "type": "number" + }, + "title": "Network key(array)" + } + ], + "title": "Network key", + "description": "Network encryption key, will improve security (Note: changing requires repairing of all devices)" + }, + "last_seen": { + "type": "string", + "enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"], + "title": "Last seen", + "description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message", + "default": "disable" + }, + "elapsed": { + "type": "boolean", + "title": "Elapsed", + "description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg", + "default": false + }, + "availability_timeout": { + "type": "number", + "minimum": 0, + "default": 0, + "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" + }, + "availability_blocklist": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Availability Blocklist", + "description": "Prevent devices from being checked for availability" + }, + "availability_passlist": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Availability passlist", + "description": "Only enable availability check for certain devices" + }, + "availability_blacklist": { + "type": "array", + "title": "Availability blacklist (deprecated)", + "items": { + "type": "string" + } + }, + "availability_whitelist": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Availability whitelist (deprecated)" + }, + "report": { + "type": "boolean", + "title": "Reporting", + "description": "Enables report feature" + }, + "homeassistant_discovery_topic": { + "type": "string", + "title": "Homeassistant discovery topic", + "examples": ["homeassistant"] + }, + "homeassistant_status_topic": { + "type": "string", + "title": "Home Assistant status topic", + "examples": ["homeassistant/status"] + }, + "timestamp_format": { + "type": "string", + "title": "Timestamp format", + "description": "Log timestamp format", + "examples": ["YYYY-MM-DD HH:mm:ss"] + }, + "adapter_concurrent": { + "title": "Adapter concurrency", + "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"], + "title": "Adapter delay" + } + } + }, + "map_options": { + "type": "object", + "title": "Networkmap options", + "properties": { + "graphviz": { + "type": "object", + "properties": { + "colors": { + "type": "object", + "properties": { + "fill": { + "type": "object", + "properties": { + "enddevice": { + "type": "string" + }, + "coordinator": { + "type": "string" + }, + "router": { + "type": "string" + } + } + }, + "font": { + "type": "object", + "properties": { + "enddevice": { + "type": "string" + }, + "coordinator": { + "type": "string" + }, + "router": { + "type": "string" + } + } + }, + "line": { + "type": "object", + "properties": { + "active": { + "type": "string" + }, + "inactive": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "devices": { + "type": "object", + "propertyNames": { + "pattern": "^0x[\\d\\w]{16}$" + }, + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/device" + } + } + }, + "groups": { + "type": "object", + "propertyNames": { + "pattern": "^[\\w].*$" + }, + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/group" + } + } + } + }, + "required": ["mqtt"], + "definitions": { + "device": { + "type": "object", + "properties": { + "friendly_name": { + "type": "string", + "title": "Friendly name", + "description": "Used in the MQTT topic of a device. By default this is the device ID", + "readOnly": true + }, + "retain": { + "type": "boolean", + "title": "Retain", + "description": "Retain MQTT messages of this device" + }, + "retention": { + "type": "number", + "title": "Retention", + "description": "Sets the MQTT Message Expiry in seconds, Make sure to set mqtt.version to 5" + }, + "qos": { + "type": "number", + "title": "QoS", + "descritption": "QoS level for MQTT messages of this device" + }, + "debounce": { + "type": "number", + "title": "Debounce", + "description": "Debounces messages of this device" + }, + "debounce_ignore": { + "type": "array", + "items": { + "type": "string" + }, + "examples": ["action"], + "title": "Ignore debounce", + "description": "Protects unique payload values of specified payload properties from overriding within debounce time" + }, + "retrieve_state": { + "type": "boolean", + "title": "Retrieve State", + "description": "Retrieves the state after setting it (Should only be enabled when the reporting feature does not work for this device)" + }, + "optimistic": { + "type": "boolean", + "title": "Optimistic", + "description": "Publish optimistic state after set (default true)" + }, + "filtered_attributes": { + "type": "array", + "items": { + "type": "string" + }, + "examples": ["temperature", "battery", "action"], + "title": "Filtered attributes", + "description": "Allows to prevent certain attributes from being published" + } + }, + "required": ["friendly_name"] + }, + "group": { + "type": "object", + "properties": { + "friendly_name": { + "type": "string" + }, + "retain": { + "type": "boolean" + }, + "devices": { + "type": "array", + "items": { + "type": "string" + } + }, + "optimistic": { + "type": "boolean" + }, + "qos": { + "type": "number" + }, + "filtered_attributes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["friendly_name"] + } + } +} diff --git a/lib/util/utils.js b/lib/util/utils.js index 07f1780d5..e169e9aea 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -190,6 +190,15 @@ function toSnakeCase(value) { function validateFriendlyName(name) { const errors = []; + for (const endpointName of endpointNames) { + if (name.toLowerCase().endsWith('/' + endpointName)) { + errors.push(`friendly_name is not allowed to end with: '/${endpointName}'`); + } + } + + if (name.length === 0) errors.push(`friendly_name shoud atleast be 1 char long`); + if (name.endsWith('/') || name.startsWith('/')) errors.push(`friendly_name is not allowed to end or start with /`); + if (name.endsWith(String.fromCharCode(0))) errors.push(`friendly_name is not allowed to contain null char`); if (endpointNames.includes(name)) errors.push(`Following friendly_name are not allowed: '${endpointNames}'`); if (name.match(/.*\/\d*$/)) errors.push(`Friendly name cannot end with a "/DIGIT" ('${name}')`); if (name.includes('#') || name.includes('+')) { @@ -197,6 +206,9 @@ function validateFriendlyName(name) { } return errors; } +function sleep(seconds) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); +} module.exports = { millisecondsToSeconds: (milliseconds) => milliseconds / 1000, @@ -205,6 +217,7 @@ module.exports = { getDependencyVersion, objectHasProperties, toSnakeCase, + sleep, getObjectProperty, getEndpointNames: () => endpointNames, isXiaomiDevice: (device) => { diff --git a/lib/zigbee.js b/lib/zigbee.js index ae2cbb924..1cff4d73f 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -53,6 +53,12 @@ class Zigbee extends events.EventEmitter { herdsmanSettings.network.networkKey = newKey; } + if (herdsmanSettings.network.panID === 'GENERATE') { + const newPanID = Math.floor(Math.random() * (0xFFFF - 2)) + 1; + settings.set(['advanced', 'pan_id'], newPanID); + herdsmanSettings.network.panID = newPanID; + } + try { herdsmanSettings.acceptJoiningDeviceHandler = this.acceptJoiningDeviceHandler; this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, logger); @@ -68,6 +74,7 @@ class Zigbee extends events.EventEmitter { this.herdsman.on('deviceJoined', (data) => this.emit('event', 'deviceJoined', data)); this.herdsman.on('deviceLeave', (data) => this.emit('event', 'deviceLeave', data)); this.herdsman.on('message', (data) => this.emit('event', 'message', data)); + this.herdsman.on('permitJoinChanged', (data) => this.emit('permitJoinChanged', data)); logger.info('zigbee-herdsman started'); logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`); @@ -118,15 +125,15 @@ class Zigbee extends events.EventEmitter { logger.info('zigbee-herdsman stopped'); } - async permitJoin(permit, resolvedEntity) { + async permitJoin(permit, resolvedEntity, time=undefined) { permit ? logger.info(`Zigbee: allowing new devices to join${resolvedEntity ? ` via ${resolvedEntity.name}` : ''}.`) : logger.info('Zigbee: disabling joining new devices.'); if (resolvedEntity && permit) { - await this.herdsman.permitJoin(permit, resolvedEntity.device); + await this.herdsman.permitJoin(permit, resolvedEntity.device, time); } else { - await this.herdsman.permitJoin(permit); + await this.herdsman.permitJoin(permit, undefined, time); } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b03e3d489..3fcde21da 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "zigbee2mqtt", - "version": "1.17.0", + "version": "1.17.0-dev", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -428,9 +428,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -440,7 +440,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -682,35 +682,35 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -800,15 +800,15 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/node": { - "version": "14.14.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", - "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==", + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==", "dev": true }, "@types/normalize-package-data": { @@ -830,9 +830,9 @@ "dev": true }, "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -845,43 +845,43 @@ "dev": true }, "@typescript-eslint/experimental-utils": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz", - "integrity": "sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz", + "integrity": "sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.11.0", - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/typescript-estree": "4.11.0", + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/scope-manager": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz", - "integrity": "sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz", + "integrity": "sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0" + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1" } }, "@typescript-eslint/types": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.0.tgz", - "integrity": "sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.1.tgz", + "integrity": "sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz", - "integrity": "sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz", + "integrity": "sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -917,12 +917,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz", - "integrity": "sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz", + "integrity": "sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA==", "dev": true, "requires": { - "@typescript-eslint/types": "4.11.0", + "@typescript-eslint/types": "4.14.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -2042,13 +2042,13 @@ } }, "eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", - "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2072,7 +2072,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2530,9 +2530,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -2580,9 +2580,9 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, "fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -2661,9 +2661,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "fn.name": { @@ -2714,9 +2714,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "dev": true, "optional": true }, @@ -2828,9 +2828,9 @@ } }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -3012,9 +3012,9 @@ "dev": true }, "humanize-duration": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.25.0.tgz", - "integrity": "sha512-QXth17C/8J2QVO0urby5sANmgvwdOF4YZW3mO13ZTkJli4ltVphzMJVG+HkVuTN/bIq1W/O0jRYSyQACus/GVQ==" + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.25.1.tgz", + "integrity": "sha512-P+dRo48gpLgc2R9tMRgiDRNULPKCmqFYgguwqOO2C0fjO35TgdURDQDANSR1Nt92iHlbHGMxOTnsB8H8xnMa2Q==" }, "iconv-lite": { "version": "0.4.24", @@ -4160,18 +4160,18 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", "dev": true }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "dev": true, "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -4571,9 +4571,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -4959,6 +4959,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "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==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -5766,15 +5772,35 @@ "dev": true }, "table": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", - "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.12.4", + "ajv": "^7.0.2", "lodash": "^4.17.20", "slice-ansi": "^4.0.0", "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==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "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==", + "dev": true + } } }, "terminal-link": { @@ -5941,9 +5967,9 @@ "dev": true }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -6086,9 +6112,9 @@ } }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } @@ -6344,9 +6370,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", @@ -6406,15 +6432,15 @@ } }, "zigbee-herdsman": { - "version": "0.13.46", - "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.13.46.tgz", - "integrity": "sha512-GH0JvIC0cd91cqLQpJ8Cd+o2+gKct2wsMGq8c6XTiGeJpcF47fIlMaHWEK/CuBcBJ6rnid8mGPdTHmOsPT1Bmw==", + "version": "0.13.59", + "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.13.59.tgz", + "integrity": "sha512-A1AwKzayvcm88JrwdmoaFm80sHU4qk0GteKe5kMoN7Mqqo0+wArEa0XAV9pyHTpItRco7y+F7Vw2eEat6RuoJw==", "requires": { "debounce": "^1.2.0", "debug": "^4.3.1", "fast-deep-equal": "^3.1.3", "mixin-deep": "^2.0.1", - "serialport": "9.0.4", + "serialport": "9.0.6", "slip": "^1.0.2" }, "dependencies": { @@ -7394,9 +7420,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -7405,7 +7431,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -7999,25 +8025,25 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, @@ -8066,6 +8092,11 @@ "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", @@ -8093,9 +8124,9 @@ } }, "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "requires": { "type-detect": "4.0.8" } @@ -8185,18 +8216,18 @@ } }, "@types/jest": { - "version": "26.0.19", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.19.tgz", - "integrity": "sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==", + "version": "26.0.20", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", + "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/nedb": { "version": "1.8.11", @@ -8207,9 +8238,9 @@ } }, "@types/node": { - "version": "14.14.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", - "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==" + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -8248,14 +8279,15 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.0.tgz", - "integrity": "sha512-x4arJMXBxyD6aBXLm3W7mSDZRiABzy+2PCLJbL7OPqlp53VXhaA1HKK7R2rTee5OlRhnUgnp8lZyVIqjnyPT6g==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.0.tgz", + "integrity": "sha512-IJ5e2W7uFNfg4qh9eHkHRUCbgZ8VKtGwD07kannJvM5t/GU8P8+24NX8gi3Hf5jST5oWPY8kyV1s/WtfiZ4+Ww==", "requires": { - "@typescript-eslint/experimental-utils": "4.11.0", - "@typescript-eslint/scope-manager": "4.11.0", + "@typescript-eslint/experimental-utils": "4.14.0", + "@typescript-eslint/scope-manager": "4.14.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" @@ -8272,50 +8304,50 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz", - "integrity": "sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.0.tgz", + "integrity": "sha512-6i6eAoiPlXMKRbXzvoQD5Yn9L7k9ezzGRvzC/x1V3650rUk3c3AOjQyGYyF9BDxQQDK2ElmKOZRD0CbtdkMzQQ==", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.11.0", - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/typescript-estree": "4.11.0", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.11.0.tgz", - "integrity": "sha512-NBTtKCC7ZtuxEV5CrHUO4Pg2s784pvavc3cnz6V+oJvVbK4tH9135f/RBP6eUA2KHiFKAollSrgSctQGmHbqJQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.0.tgz", + "integrity": "sha512-sUDeuCjBU+ZF3Lzw0hphTyScmDDJ5QVkyE21pRoBo8iDl7WBtVFS+WDN3blY1CH3SBt7EmYCw6wfmJjF0l/uYg==", "requires": { - "@typescript-eslint/scope-manager": "4.11.0", - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/typescript-estree": "4.11.0", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz", - "integrity": "sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.0.tgz", + "integrity": "sha512-/J+LlRMdbPh4RdL4hfP1eCwHN5bAhFAGOTsvE6SxsrM/47XQiPSgF5MDgLyp/i9kbZV9Lx80DW0OpPkzL+uf8Q==", "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0" + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0" } }, "@typescript-eslint/types": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.0.tgz", - "integrity": "sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==" + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz", + "integrity": "sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==" }, "@typescript-eslint/typescript-estree": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz", - "integrity": "sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.0.tgz", + "integrity": "sha512-wRjZ5qLao+bvS2F7pX4qi2oLcOONIB+ru8RGBieDptq/SudYwshveORwCVU4/yMAd4GK7Fsf8Uq1tjV838erag==", "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -8335,11 +8367,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz", - "integrity": "sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.0.tgz", + "integrity": "sha512-MeHHzUyRI50DuiPgV9+LxcM52FCJFYjJiWHtXlbyC27b80mfOwKeiKI+MHOTEpcpfmoPFm/vvQS88bYIx6PZTA==", "requires": { - "@typescript-eslint/types": "4.11.0", + "@typescript-eslint/types": "4.14.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -8823,15 +8855,15 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserslist": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", - "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "requires": { - "caniuse-lite": "^1.0.30001165", + "caniuse-lite": "^1.0.30001173", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.621", + "electron-to-chromium": "^1.3.634", "escalade": "^3.1.1", - "node-releases": "^1.1.67" + "node-releases": "^1.1.69" } }, "bser": { @@ -8873,12 +8905,12 @@ } }, "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "requires": { "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "get-intrinsic": "^1.0.2" } }, "callsites": { @@ -8892,9 +8924,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001170", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz", - "integrity": "sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA==" + "version": "1.0.30001179", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001179.tgz", + "integrity": "sha512-blMmO0QQujuUWZKyVrD1msR4WNDAqb/UPO1Sw2WWsQ7deoM5bJiicKnWJ1Y0NS/aGINSnKPIWBMw5luX+NDUCA==" }, "capture-exit": { "version": "2.0.0", @@ -8925,14 +8957,14 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "optional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -8951,9 +8983,9 @@ } }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "optional": true }, "braces": { @@ -9183,11 +9215,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js-compat": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", - "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", + "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "requires": { - "browserslist": "^4.15.0", + "browserslist": "^4.16.1", "semver": "7.0.0" }, "dependencies": { @@ -9415,9 +9447,9 @@ } }, "electron-to-chromium": { - "version": "1.3.633", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.633.tgz", - "integrity": "sha512-bsVCsONiVX1abkWdH7KtpuDAhsQ3N3bjPYhROSAXE78roJKet0Y5wznA14JE9pzbwSZmSMAW6KiKYf1RvbTJkA==" + "version": "1.3.644", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.644.tgz", + "integrity": "sha512-N7FLvjDPADxad+OXXBuYfcvDvCBG0aW8ZZGr7G91sZMviYbnQJFxdSvUus4SJ0K7Q8dzMxE+Wx1d/CrJIIJ0sw==" }, "emittery": { "version": "0.7.2", @@ -9519,12 +9551,12 @@ } }, "eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", - "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -9548,7 +9580,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -9985,9 +10017,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10124,9 +10156,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==" }, "for-in": { "version": "1.0.2", @@ -10162,14 +10194,14 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "fs-readdir-recursive": { @@ -10183,9 +10215,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "optional": true }, "function-bind": { @@ -10304,9 +10336,9 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -12221,13 +12253,6 @@ "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": { @@ -12367,16 +12392,16 @@ } }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -12513,9 +12538,9 @@ } }, "node-releases": { - "version": "1.1.67", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", - "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==" + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==" }, "noop-logger": { "version": "0.1.1", @@ -12729,9 +12754,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -13061,9 +13086,9 @@ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.6.tgz", + "integrity": "sha512-jjyuCp+IEMIm3N1H1LLTJW1EISEJV9+5oHdEyrt43Pg9cDSb6rrLZei2cVWpl0xTjmmlpec/lEQGYgM7xfpGCQ==", "requires": { "jsesc": "~0.5.0" }, @@ -13167,6 +13192,11 @@ "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", @@ -13275,15 +13305,16 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "serialport": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.4.tgz", - "integrity": "sha512-Wtl6sxciHKNdJKW8jUZm+f5KRXt0J0UP25ksM8N31l08KTdAhZ5MmKNUFpoO/YYG6TvHFOu+07PUX35m+TUw7g==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.6.tgz", + "integrity": "sha512-T9eY4HFzQij0Hd/RsPcZySdeuAqzV5iGICHz8FXUSKVn2SvGT5zjfz/H+pRwI86k+3iFVOyddEyy8gbVNVbW7A==", "requires": { "@serialport/binding-mock": "^9.0.2", "@serialport/bindings": "^9.0.4", "@serialport/parser-byte-length": "^9.0.1", "@serialport/parser-cctalk": "^9.0.1", "@serialport/parser-delimiter": "^9.0.1", + "@serialport/parser-inter-byte-timeout": "^9.0.1", "@serialport/parser-readline": "^9.0.1", "@serialport/parser-ready": "^9.0.1", "@serialport/parser-regex": "^9.0.1", @@ -13799,16 +13830,27 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "table": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", - "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "requires": { - "ajv": "^6.12.4", + "ajv": "^7.0.2", "lodash": "^4.17.20", "slice-ansi": "^4.0.0", "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", @@ -13819,6 +13861,11 @@ "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", @@ -13851,9 +13898,9 @@ } }, "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -13975,9 +14022,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz", + "integrity": "sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw==", "requires": { "tslib": "^1.8.1" } @@ -14022,9 +14069,9 @@ } }, "typedoc": { - "version": "0.20.0-beta.30", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.20.0-beta.30.tgz", - "integrity": "sha512-nAisQV1Wmu/a/AgZMJ4/H7Z0e+eR/XVaF7ilSM5b7YJ2HP7iuqFw4j/e6a88ZHYf1nNfjBB9XpaqP/JYwHo4sQ==", + "version": "0.20.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.20.17.tgz", + "integrity": "sha512-FLEAanvCDGIFzfgXppN7GdYzsdDJ8+5wD4QfmCti8LAjRnn6qJIdQOmI5kqrpdiTzNCK7Wvs1vpT0vqMgO+uGQ==", "requires": { "colors": "^1.4.0", "fs-extra": "^9.0.1", @@ -14036,18 +14083,18 @@ "progress": "^2.0.3", "shelljs": "^0.8.4", "shiki": "^0.2.7", - "typedoc-default-themes": "0.12.0-beta.10" + "typedoc-default-themes": "^0.12.5" } }, "typedoc-default-themes": { - "version": "0.12.0-beta.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.0-beta.10.tgz", - "integrity": "sha512-RqTLRQvzuLrNEZ1VcmYoQiFkixW0QvVUv0R35jlkhxAMGtoo/giUVtWrWY1+Yp7HBnfI/wZKvhZpQYswbDL7eQ==" + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.5.tgz", + "integrity": "sha512-JQ2O9laZ/EhfWUWYp/8EyuShYhtXLhIa6DU8eZNUfaurMhEgKdffbadKNv6HMmTfOxAcgiePg06OCxNX8EyP3g==" }, "typedoc-plugin-markdown": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.1.1.tgz", - "integrity": "sha512-esrSaGw9NeGOR1fixubeSYCtQ9tw1iv7xa6bMvwznjs+jOUEdP0/OBe7l2bbk3PoqhNiFBozBJDo3CpcMJGHnw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.4.3.tgz", + "integrity": "sha512-7keqrkDfGECpjRnGLDpvS+Knw1fkQ0PHdoW6CJBs6uuaa2lJUUGgzDqbj2yWCghzF4jcK4hhGTcxpa9unNkGZg==", "requires": { "handlebars": "^4.7.6" } @@ -14068,9 +14115,9 @@ "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==" }, "uglify-js": { - "version": "3.12.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.3.tgz", - "integrity": "sha512-feZzR+kIcSVuLi3s/0x0b2Tx4Iokwqt+8PJM7yRHKuldg4MLdam4TCFeICv+lgDtuYiCtdmrtIP+uN9LWvDasw==", + "version": "3.12.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.5.tgz", + "integrity": "sha512-SgpgScL4T7Hj/w/GexjnBHi3Ien9WS1Rpfg5y91WXMj9SY997ZCQU76mH4TpLwwfmMvoOU8wiaRkIf6NaH3mtg==", "optional": true }, "unicode-canonical-property-names-ecmascript": { @@ -14109,9 +14156,9 @@ } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "unset-value": { "version": "1.0.0", @@ -14156,9 +14203,9 @@ "optional": true }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } @@ -14396,9 +14443,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", @@ -14480,15 +14527,15 @@ } }, "zigbee-herdsman-converters": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/zigbee-herdsman-converters/-/zigbee-herdsman-converters-14.0.3.tgz", - "integrity": "sha512-ObZqRgK81C3BIbTXaGL/QEUXP1F1tmj8vjAWVtrlGWkYjCQp4GYKxQU1FHNn342V6/3WxBo122OWrFFRxH3Xfw==", + "version": "14.0.42", + "resolved": "https://registry.npmjs.org/zigbee-herdsman-converters/-/zigbee-herdsman-converters-14.0.42.tgz", + "integrity": "sha512-RPY7Kwx+Y32SfU0uLhFwaGBhp4pdp3eMNczL8PvS1rlQ9rGLOETrmeAMcaXSTIizNoGtPOmuXlSka+9pvbEEPA==", "requires": { "axios": "^0.21.1", "buffer-crc32": "^0.2.13", "https-proxy-agent": "^5.0.0", - "tar-stream": "^2.1.4", - "zigbee-herdsman": "^0.13.41" + "tar-stream": "^2.2.0", + "zigbee-herdsman": "^0.13.59" }, "dependencies": { "@babel/code-frame": { @@ -14836,9 +14883,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -14847,7 +14894,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" } @@ -15058,32 +15105,32 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "requires": { "type-detect": "4.0.8" } @@ -15163,14 +15210,14 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/node": { - "version": "14.14.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", - "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==" + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -15188,9 +15235,9 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" }, "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "requires": { "@types/yargs-parser": "*" } @@ -15201,39 +15248,39 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/experimental-utils": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz", - "integrity": "sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz", + "integrity": "sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ==", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.11.0", - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/typescript-estree": "4.11.0", + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/scope-manager": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz", - "integrity": "sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz", + "integrity": "sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw==", "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0" + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1" } }, "@typescript-eslint/types": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.0.tgz", - "integrity": "sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==" + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.1.tgz", + "integrity": "sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==" }, "@typescript-eslint/typescript-estree": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz", - "integrity": "sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz", + "integrity": "sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ==", "requires": { - "@typescript-eslint/types": "4.11.0", - "@typescript-eslint/visitor-keys": "4.11.0", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -15243,11 +15290,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz", - "integrity": "sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz", + "integrity": "sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA==", "requires": { - "@typescript-eslint/types": "4.11.0", + "@typescript-eslint/types": "4.14.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -16085,12 +16132,12 @@ } }, "eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", - "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -16114,7 +16161,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -16476,9 +16523,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -16499,9 +16546,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "requires": { "reusify": "^1.0.4" } @@ -16549,14 +16596,14 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==" }, "follow-redirects": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", - "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==" }, "for-in": { "version": "1.0.2", @@ -16597,9 +16644,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "optional": true }, "function-bind": { @@ -16678,9 +16725,9 @@ } }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -17798,16 +17845,16 @@ } }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -18065,9 +18112,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -18367,6 +18414,11 @@ "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", @@ -19026,20 +19078,38 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "table": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", - "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "requires": { - "ajv": "^6.12.4", + "ajv": "^7.0.2", "lodash": "^4.17.20", "slice-ansi": "^4.0.0", "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" + } + }, + "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==" + } } }, "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -19148,9 +19218,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", "requires": { "tslib": "^1.8.1" } @@ -19242,9 +19312,9 @@ } }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } @@ -19431,9 +19501,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", @@ -19483,22 +19553,22 @@ } }, "zigbee-herdsman": { - "version": "0.13.41", - "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.13.41.tgz", - "integrity": "sha512-qHAvPHr8CrzD9pRiiqNmlwxh/KvIfvGpopGBl4Hl2LJln3ruzt1cpHA3jcvR/WUC4vQsTtdEk2911BNEb5APWw==", + "version": "0.13.59", + "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.13.59.tgz", + "integrity": "sha512-A1AwKzayvcm88JrwdmoaFm80sHU4qk0GteKe5kMoN7Mqqo0+wArEa0XAV9pyHTpItRco7y+F7Vw2eEat6RuoJw==", "requires": { "debounce": "^1.2.0", "debug": "^4.3.1", "fast-deep-equal": "^3.1.3", "mixin-deep": "^2.0.1", - "serialport": "9.0.3", + "serialport": "9.0.6", "slip": "^1.0.2" }, "dependencies": { "@babel/cli": { - "version": "7.12.8", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.8.tgz", - "integrity": "sha512-/6nQj11oaGhLmZiuRUfxsujiPDc9BBReemiXgIbxc+M5W+MIiFKYwvNDJvBfnGKNsJTKbUfEheKc9cwoPHAVQA==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.10.tgz", + "integrity": "sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==", "requires": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", "chokidar": "^3.4.0", @@ -19513,9 +19583,9 @@ } }, "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "requires": { "@babel/highlight": "^7.10.4" } @@ -19526,44 +19596,43 @@ "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==" }, "@babel/core": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", - "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", + "@babel/generator": "^7.12.10", "@babel/helper-module-transforms": "^7.12.1", "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.7", + "@babel/parser": "^7.12.10", "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.9", - "@babel/types": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", "lodash": "^4.17.19", - "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" } }, "@babel/generator": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", - "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", "requires": { - "@babel/types": "^7.12.5", + "@babel/types": "^7.12.11", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", + "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.10" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -19626,21 +19695,21 @@ } }, "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" } }, "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.10" } }, "@babel/helper-hoist-variables": { @@ -19684,11 +19753,11 @@ } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz", - "integrity": "sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", "requires": { - "@babel/types": "^7.12.7" + "@babel/types": "^7.12.10" } }, "@babel/helper-plugin-utils": { @@ -19707,14 +19776,14 @@ } }, "@babel/helper-replace-supers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", - "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" } }, "@babel/helper-simple-access": { @@ -19734,22 +19803,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", "requires": { - "@babel/types": "^7.11.0" + "@babel/types": "^7.12.11" } }, "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" }, "@babel/helper-validator-option": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", - "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==" + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", + "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==" }, "@babel/helper-wrap-function": { "version": "7.12.3", @@ -19783,14 +19852,14 @@ } }, "@babel/parser": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz", - "integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==" + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-remap-async-to-generator": "^7.12.1", @@ -20054,9 +20123,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", - "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", + "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -20284,9 +20353,9 @@ } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", - "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", + "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -20319,15 +20388,15 @@ } }, "@babel/preset-env": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz", - "integrity": "sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", + "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", "requires": { "@babel/compat-data": "^7.12.7", "@babel/helper-compilation-targets": "^7.12.5", "@babel/helper-module-imports": "^7.12.5", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.1", + "@babel/helper-validator-option": "^7.12.11", "@babel/plugin-proposal-async-generator-functions": "^7.12.1", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-dynamic-import": "^7.12.1", @@ -20356,7 +20425,7 @@ "@babel/plugin-transform-arrow-functions": "^7.12.1", "@babel/plugin-transform-async-to-generator": "^7.12.1", "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.11", "@babel/plugin-transform-classes": "^7.12.1", "@babel/plugin-transform-computed-properties": "^7.12.1", "@babel/plugin-transform-destructuring": "^7.12.1", @@ -20382,12 +20451,12 @@ "@babel/plugin-transform-spread": "^7.12.1", "@babel/plugin-transform-sticky-regex": "^7.12.7", "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.10", "@babel/plugin-transform-unicode-escapes": "^7.12.1", "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.7", - "core-js-compat": "^3.7.0", + "@babel/types": "^7.12.11", + "core-js-compat": "^3.8.0", "semver": "^5.5.0" } }, @@ -20432,27 +20501,27 @@ } }, "@babel/traverse": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz", - "integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7", + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz", - "integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.12.11", "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } @@ -20472,9 +20541,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -20483,7 +20552,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -21077,25 +21146,25 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, @@ -21117,9 +21186,9 @@ } }, "@serialport/bindings": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-9.0.3.tgz", - "integrity": "sha512-hnqVqEc4IqGCIjztGkd30V0KcTatQ1T/SS03MZ9KLn6e3y2PSXFqf0TqxB0qF7K9lGHWldMTiPOQaZnuV/oZLQ==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-9.0.4.tgz", + "integrity": "sha512-6dlE1vm5c1xk667f1Zm7D+msbHJ9jdnUr9l8DResKpj2iCBzbCNsW+yCYq26WxzXWc1L2HUaS3/aL+k0wm5amg==", "requires": { "@serialport/binding-abstract": "^9.0.2", "@serialport/parser-readline": "^9.0.1", @@ -21144,6 +21213,11 @@ "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", @@ -21171,9 +21245,9 @@ } }, "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "requires": { "type-detect": "4.0.8" } @@ -21263,18 +21337,18 @@ } }, "@types/jest": { - "version": "26.0.18", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.18.tgz", - "integrity": "sha512-scDPs+mELZgsFetTgBSsIxKGrlitn9t/d2ecP+S1QSIGD+31fkMBEftLfOAX5k3tU06/0PjreJIQ+gWEbbHqpw==", + "version": "26.0.20", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", + "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/nedb": { "version": "1.8.11", @@ -21285,9 +21359,9 @@ } }, "@types/node": { - "version": "14.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", - "integrity": "sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==" + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -21295,9 +21369,9 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==" + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", + "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==" }, "@types/serialport": { "version": "8.0.1", @@ -21313,27 +21387,28 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" }, "@types/yargs": { - "version": "15.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.11.tgz", - "integrity": "sha512-jfcNBxHFYJ4nPIacsi3woz1+kvUO6s1CyeEhtnDHBjHUMNj5UlW2GynmnSgiJJEdNg9yW5C8lfoNRZrHGv5EqA==", + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", + "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz", - "integrity": "sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.0.tgz", + "integrity": "sha512-IJ5e2W7uFNfg4qh9eHkHRUCbgZ8VKtGwD07kannJvM5t/GU8P8+24NX8gi3Hf5jST5oWPY8kyV1s/WtfiZ4+Ww==", "requires": { - "@typescript-eslint/experimental-utils": "4.9.1", - "@typescript-eslint/scope-manager": "4.9.1", + "@typescript-eslint/experimental-utils": "4.14.0", + "@typescript-eslint/scope-manager": "4.14.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" @@ -21350,50 +21425,50 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz", - "integrity": "sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.0.tgz", + "integrity": "sha512-6i6eAoiPlXMKRbXzvoQD5Yn9L7k9ezzGRvzC/x1V3650rUk3c3AOjQyGYyF9BDxQQDK2ElmKOZRD0CbtdkMzQQ==", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.9.1", - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/typescript-estree": "4.9.1", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.1.tgz", - "integrity": "sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.0.tgz", + "integrity": "sha512-sUDeuCjBU+ZF3Lzw0hphTyScmDDJ5QVkyE21pRoBo8iDl7WBtVFS+WDN3blY1CH3SBt7EmYCw6wfmJjF0l/uYg==", "requires": { - "@typescript-eslint/scope-manager": "4.9.1", - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/typescript-estree": "4.9.1", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz", - "integrity": "sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.0.tgz", + "integrity": "sha512-/J+LlRMdbPh4RdL4hfP1eCwHN5bAhFAGOTsvE6SxsrM/47XQiPSgF5MDgLyp/i9kbZV9Lx80DW0OpPkzL+uf8Q==", "requires": { - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/visitor-keys": "4.9.1" + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0" } }, "@typescript-eslint/types": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.1.tgz", - "integrity": "sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==" + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz", + "integrity": "sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==" }, "@typescript-eslint/typescript-estree": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz", - "integrity": "sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.0.tgz", + "integrity": "sha512-wRjZ5qLao+bvS2F7pX4qi2oLcOONIB+ru8RGBieDptq/SudYwshveORwCVU4/yMAd4GK7Fsf8Uq1tjV838erag==", "requires": { - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/visitor-keys": "4.9.1", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -21413,11 +21488,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz", - "integrity": "sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.0.tgz", + "integrity": "sha512-MeHHzUyRI50DuiPgV9+LxcM52FCJFYjJiWHtXlbyC27b80mfOwKeiKI+MHOTEpcpfmoPFm/vvQS88bYIx6PZTA==", "requires": { - "@typescript-eslint/types": "4.9.1", + "@typescript-eslint/types": "4.14.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -21579,9 +21654,9 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "async-each": { "version": "1.0.3", @@ -21711,9 +21786,9 @@ } }, "babel-preset-current-node-syntax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz", - "integrity": "sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "requires": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -21901,15 +21976,15 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserslist": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.15.0.tgz", - "integrity": "sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "requires": { - "caniuse-lite": "^1.0.30001164", + "caniuse-lite": "^1.0.30001173", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.612", + "electron-to-chromium": "^1.3.634", "escalade": "^3.1.1", - "node-releases": "^1.1.67" + "node-releases": "^1.1.69" } }, "bser": { @@ -21951,12 +22026,12 @@ } }, "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "requires": { "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "get-intrinsic": "^1.0.2" } }, "callsites": { @@ -21970,9 +22045,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001165", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz", - "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==" + "version": "1.0.30001179", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001179.tgz", + "integrity": "sha512-blMmO0QQujuUWZKyVrD1msR4WNDAqb/UPO1Sw2WWsQ7deoM5bJiicKnWJ1Y0NS/aGINSnKPIWBMw5luX+NDUCA==" }, "capture-exit": { "version": "2.0.0", @@ -22003,14 +22078,14 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "optional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -22029,9 +22104,9 @@ } }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "optional": true }, "braces": { @@ -22147,11 +22222,6 @@ "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", @@ -22266,11 +22336,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js-compat": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", - "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", + "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "requires": { - "browserslist": "^4.15.0", + "browserslist": "^4.16.1", "semver": "7.0.0" }, "dependencies": { @@ -22498,9 +22568,9 @@ } }, "electron-to-chromium": { - "version": "1.3.620", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.620.tgz", - "integrity": "sha512-YbgWXUR2Mu+Fp6rm3GZ5YJdNo8SgZKLUTNSl2PNvdOcM8OIz07jRJnRkIaV9vdszFv9UUuGChh19w9qSuoLJgw==" + "version": "1.3.644", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.644.tgz", + "integrity": "sha512-N7FLvjDPADxad+OXXBuYfcvDvCBG0aW8ZZGr7G91sZMviYbnQJFxdSvUus4SJ0K7Q8dzMxE+Wx1d/CrJIIJ0sw==" }, "emittery": { "version": "0.7.2", @@ -22508,9 +22578,9 @@ "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "end-of-stream": { "version": "1.4.4", @@ -22602,12 +22672,12 @@ } }, "eslint": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", - "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -22631,7 +22701,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -22640,7 +22710,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", + "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -23068,9 +23138,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -23139,9 +23209,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastq": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", - "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", "requires": { "reusify": "^1.0.4" } @@ -23207,9 +23277,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==" }, "for-in": { "version": "1.0.2", @@ -23245,14 +23315,14 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "fs-readdir-recursive": { @@ -23266,9 +23336,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "optional": true }, "function-bind": { @@ -23307,9 +23377,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", + "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -23387,9 +23457,9 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -23554,9 +23624,9 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -23598,9 +23668,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.6.tgz", - "integrity": "sha512-IZUoxEjNjubzrmvzZU4lKP7OnYmX72XRl3sqkfJhBKweKi5rnGi5+IUdlj/H1M+Ip5JQ1WzaDMOBRY90Ajc5jg==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "interpret": { "version": "1.4.0", @@ -25304,13 +25374,6 @@ "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": { @@ -25415,9 +25478,9 @@ } }, "marked": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.5.tgz", - "integrity": "sha512-2AlqgYnVPOc9WDyWu7S5DJaEZsfk6dNh/neatQ3IHUW4QLutM/VPSH9lG7bif+XjFWc9K9XR3QvR+fXuECmfdA==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.7.tgz", + "integrity": "sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA==" }, "merge-stream": { "version": "2.0.0", @@ -25450,16 +25513,16 @@ } }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -25562,9 +25625,9 @@ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-notifier": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz", - "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", + "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", "optional": true, "requires": { "growly": "^1.3.0", @@ -25596,9 +25659,9 @@ } }, "node-releases": { - "version": "1.1.67", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", - "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==" + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==" }, "noop-logger": { "version": "0.1.1", @@ -25812,9 +25875,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -26144,9 +26207,9 @@ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.6.tgz", + "integrity": "sha512-jjyuCp+IEMIm3N1H1LLTJW1EISEJV9+5oHdEyrt43Pg9cDSb6rrLZei2cVWpl0xTjmmlpec/lEQGYgM7xfpGCQ==", "requires": { "jsesc": "~0.5.0" }, @@ -26250,6 +26313,11 @@ "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", @@ -26358,15 +26426,16 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "serialport": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.3.tgz", - "integrity": "sha512-1JjH9jtWZ5up2SQTeNPA4I3vhHCDYh1AjN3SybZYf5m9KF9tFVIOGLGIMncqnWKYx3ks/wqfCpmpYUHkFYC3wg==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.6.tgz", + "integrity": "sha512-T9eY4HFzQij0Hd/RsPcZySdeuAqzV5iGICHz8FXUSKVn2SvGT5zjfz/H+pRwI86k+3iFVOyddEyy8gbVNVbW7A==", "requires": { "@serialport/binding-mock": "^9.0.2", - "@serialport/bindings": "^9.0.3", + "@serialport/bindings": "^9.0.4", "@serialport/parser-byte-length": "^9.0.1", "@serialport/parser-cctalk": "^9.0.1", "@serialport/parser-delimiter": "^9.0.1", + "@serialport/parser-inter-byte-timeout": "^9.0.1", "@serialport/parser-readline": "^9.0.1", "@serialport/parser-ready": "^9.0.1", "@serialport/parser-regex": "^9.0.1", @@ -26488,19 +26557,40 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "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": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" } } }, @@ -26861,42 +26951,58 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "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": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "3.0.0", + "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": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } } } @@ -26913,9 +27019,9 @@ } }, "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -27037,9 +27143,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz", + "integrity": "sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw==", "requires": { "tslib": "^1.8.1" } @@ -27084,9 +27190,9 @@ } }, "typedoc": { - "version": "0.20.0-beta.27", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.20.0-beta.27.tgz", - "integrity": "sha512-+TMTlYV7N6mIGEuNIaPEqss3jWOLX2v7rLVz5vT+PfjVgwX+1s+LdPe+plV1hm5Ha1HwFNuVlozXxxWvf95dwA==", + "version": "0.20.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.20.17.tgz", + "integrity": "sha512-FLEAanvCDGIFzfgXppN7GdYzsdDJ8+5wD4QfmCti8LAjRnn6qJIdQOmI5kqrpdiTzNCK7Wvs1vpT0vqMgO+uGQ==", "requires": { "colors": "^1.4.0", "fs-extra": "^9.0.1", @@ -27098,18 +27204,18 @@ "progress": "^2.0.3", "shelljs": "^0.8.4", "shiki": "^0.2.7", - "typedoc-default-themes": "0.12.0-beta.10" + "typedoc-default-themes": "^0.12.5" } }, "typedoc-default-themes": { - "version": "0.12.0-beta.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.0-beta.10.tgz", - "integrity": "sha512-RqTLRQvzuLrNEZ1VcmYoQiFkixW0QvVUv0R35jlkhxAMGtoo/giUVtWrWY1+Yp7HBnfI/wZKvhZpQYswbDL7eQ==" + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.5.tgz", + "integrity": "sha512-JQ2O9laZ/EhfWUWYp/8EyuShYhtXLhIa6DU8eZNUfaurMhEgKdffbadKNv6HMmTfOxAcgiePg06OCxNX8EyP3g==" }, "typedoc-plugin-markdown": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.1.0.tgz", - "integrity": "sha512-DhY2WbWfkLyU/MJV8a3TeJjY8e/IBzgG6SEFhtFp6GgmXXdsSHJre2ASFRH6T2ShIUZAPdvHkFcehpSVAfNgPA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.4.3.tgz", + "integrity": "sha512-7keqrkDfGECpjRnGLDpvS+Knw1fkQ0PHdoW6CJBs6uuaa2lJUUGgzDqbj2yWCghzF4jcK4hhGTcxpa9unNkGZg==", "requires": { "handlebars": "^4.7.6" } @@ -27125,14 +27231,14 @@ "integrity": "sha512-xHq9DzkoQywS7FyPneMm2/Hr9GRoCpjSQXkVN0W6SCJKP7fguqg2tasgh+8l5/mW6YSYvqCqEbkSYLbuD4Y6gA==" }, "typescript": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", - "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==" }, "uglify-js": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz", - "integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==", + "version": "3.12.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.5.tgz", + "integrity": "sha512-SgpgScL4T7Hj/w/GexjnBHi3Ien9WS1Rpfg5y91WXMj9SY997ZCQU76mH4TpLwwfmMvoOU8wiaRkIf6NaH3mtg==", "optional": true }, "unicode-canonical-property-names-ecmascript": { @@ -27171,9 +27277,9 @@ } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "unset-value": { "version": "1.0.0", @@ -27218,9 +27324,9 @@ "optional": true }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } @@ -27252,9 +27358,9 @@ "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==" }, "v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", + "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", "requires": { "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0", @@ -27416,11 +27522,6 @@ "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", @@ -27463,9 +27564,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", @@ -27510,11 +27611,6 @@ "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", @@ -27554,9 +27650,9 @@ } }, "zigbee2mqtt-frontend": { - "version": "0.2.129", - "resolved": "https://registry.npmjs.org/zigbee2mqtt-frontend/-/zigbee2mqtt-frontend-0.2.129.tgz", - "integrity": "sha512-gvzgYMtI7XF97HueC65MxwqB7Hcoc2HNRnyydqJuq11w0FrzFBg/VLhDpsW5Sor8oz/C8cjd8rtS1yC2jqI1Eg==" + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/zigbee2mqtt-frontend/-/zigbee2mqtt-frontend-0.3.23.tgz", + "integrity": "sha512-DMkgB/uOQL6Qpact1pWGSXawZLWYGvkTXRd2ouMvNfbx/pJYTB3GHIMLuWeAFx4y635i6NaNAQ/61AZ8IwOXWw==" } } } diff --git a/package.json b/package.json index c2b8b5450..138d26801 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zigbee2mqtt", - "version": "1.17.0", + "version": "1.17.0-dev", "description": "Zigbee to MQTT bridge using Zigbee-herdsman", "main": "index.js", "repository": { @@ -51,9 +51,9 @@ "winston": "^3.3.3", "winston-syslog": "^2.4.4", "ws": "^7.3.1", - "zigbee-herdsman": "0.13.46", - "zigbee-herdsman-converters": "14.0.3", - "zigbee2mqtt-frontend": "0.2.129" + "zigbee-herdsman": "0.13.59", + "zigbee-herdsman-converters": "14.0.42", + "zigbee2mqtt-frontend": "0.3.23" }, "devDependencies": { "eslint": "*", @@ -72,10 +72,10 @@ } }, "collectCoverageFrom": [ - "lib/**" + "lib/**/*.js" ] }, "bin": { - "zigbee2mqtt": "./cli.js" + "zigbee2mqtt": "cli.js" } } diff --git a/test/assets/mock-external-converter-multiple.js b/test/assets/mock-external-converter-multiple.js index 338a065f0..4bf7568f0 100644 --- a/test/assets/mock-external-converter-multiple.js +++ b/test/assets/mock-external-converter-multiple.js @@ -11,10 +11,23 @@ const homeassistantSwitch = { const mockDevices = [{ mock: 1, - model: 'external_converters_device', + model: 'external_converters_device_1', homeassistant: [homeassistantSwitch], + zigbeeModel: ['external_converter_device_1'], + vendor: 'external_1', + description: 'external_1', + fromZigbee: [], + toZigbee: [], + exposes: [], }, { - mock: 2 + mock: 2, + model: 'external_converters_device_2', + zigbeeModel: ['external_converter_device_2'], + vendor: 'external_2', + description: 'external_2', + fromZigbee: [], + toZigbee: [], + exposes: [], }]; module.exports = mockDevices; \ No newline at end of file diff --git a/test/assets/mock-external-converter.js b/test/assets/mock-external-converter.js index 01a31b0ef..493c1b972 100644 --- a/test/assets/mock-external-converter.js +++ b/test/assets/mock-external-converter.js @@ -2,10 +2,11 @@ const mockDevice = { mock: true, zigbeeModel: ['external_converter_device'], vendor: 'external', - model: ['external_converter_device'], - definition: { - foo: 'bar' - } + model: 'external_converter_device', + description: 'external', + fromZigbee: [], + toZigbee: [], + exposes: [], }; module.exports = mockDevice; \ No newline at end of file diff --git a/test/availability.test.js b/test/availability.test.js index d87b32102..f3f1dbe0c 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -181,7 +181,7 @@ describe('Availability', () => { it('Should not retrieve the state when device is turned on/off within availability timeout on deviceJoined', async () => { MQTT.publish.mockClear(); - const device = zigbeeHerdsman.devices.bulb_color; + const device = zigbeeHerdsman.devices.E11_G13; const endpoint = device.getEndpoint(1); endpoint.read.mockClear(); await zigbeeHerdsman.events.deviceJoined({device}); diff --git a/test/bind.test.js b/test/bind.test.js index a383b34bc..0a2ed2a13 100644 --- a/test/bind.test.js +++ b/test/bind.test.js @@ -6,6 +6,8 @@ const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); const stringify = require('json-stable-stringify-without-jsonify'); +jest.mock('debounce', () => jest.fn(fn => fn)); +const debounce = require('debounce'); describe('Bind', () => { let controller; @@ -25,6 +27,11 @@ describe('Bind', () => { data.writeDefaultConfiguration(); settings._reRead(); data.writeEmptyState(); + zigbeeHerdsman.groups.group_1.members = []; + zigbeeHerdsman.devices.bulb_color.getEndpoint(1).configureReporting.mockClear(); + zigbeeHerdsman.devices.bulb_color.getEndpoint(1).bind.mockClear(); + zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read.mockClear(); + debounce.mockClear(); controller = new Controller(); await controller.start(); await flushPromises(); @@ -32,21 +39,35 @@ describe('Bind', () => { MQTT.publish.mockClear(); }); - it('Should bind', async () => { + it('Should bind to device and configure reporting', async () => { const device = zigbeeHerdsman.devices.remote; const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); + + // Setup + const originalDeviceOutputClusters = device.getEndpoint(1).outputClusters; + device.getEndpoint(1).outputClusters = [...device.getEndpoint(1).outputClusters, 768]; + const originalTargetBinds = target.binds; + target.binds = [{cluster: {name: 'genLevelCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)}]; + target.getClusterAttributeValue.mockImplementationOnce((cluster, value) => undefined); mockClear(device); + target.configureReporting.mockImplementationOnce(() => {throw new Error("timeout")}); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); - expect(endpoint.bind).toHaveBeenCalledTimes(3); + expect(target.read).toHaveBeenCalledWith('lightingColorCtrl', [ 'colorCapabilities' ]); + expect(endpoint.bind).toHaveBeenCalledTimes(4); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(5); + expect(endpoint.bind).toHaveBeenCalledWith("lightingColorCtrl", target); + expect(target.configureReporting).toHaveBeenCalledTimes(3); + expect(target.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]); + expect(target.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]); + expect(target.configureReporting).toHaveBeenCalledWith("lightingColorCtrl",[{"attribute":"colorTemperature","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1},{"attribute":"currentX","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1},{"attribute":"currentY","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1}]); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl", "lightingColorCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -56,6 +77,10 @@ describe('Bind', () => { { retain: true, qos: 0 }, expect.any(Function) ); + + // Teardown + target.binds = originalTargetBinds; + device.getEndpoint(1).outputClusters = originalDeviceOutputClusters; }); it('Should bind only specifief clusters', async () => { @@ -92,19 +117,44 @@ describe('Bind', () => { it('Should unbind', async () => { const device = zigbeeHerdsman.devices.remote; const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); + + // setup + target.configureReporting.mockImplementationOnce(() => {throw new Error("timeout")}); + const originalRemoteBinds = device.getEndpoint(1).binds; + device.getEndpoint(1).binds = []; + const originalTargetBinds = target.binds; + target.binds = [ + {cluster: {name: 'genOnOff'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)}, + {cluster: {name: 'genLevelCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)}, + {cluster: {name: 'lightingColorCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)} + ]; + const endpoint = device.getEndpoint(1); mockClear(device); + delete zigbeeHerdsman.devices.bulb_color.meta.configured; + expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(undefined); MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); expect(endpoint.unbind).toHaveBeenCalledTimes(3); expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); + + // Disable reporting + expect(target.configureReporting).toHaveBeenCalledTimes(3); + expect(target.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 0, "reportableChange": 0}]); + expect(target.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 5, "reportableChange": 1}]); + expect(target.configureReporting).toHaveBeenCalledWith("lightingColorCtrl",[{"attribute":"colorTemperature","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentX","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentY","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1}]); + expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); + + // Teardown + target.binds = originalTargetBinds; + device.getEndpoint(1).binds = originalRemoteBinds; }); it('Should unbind coordinator', async () => { @@ -129,7 +179,10 @@ describe('Bind', () => { it('Should bind to groups', async () => { const device = zigbeeHerdsman.devices.remote; const target = zigbeeHerdsman.groups.group_1; + const target1Member = zigbeeHerdsman.devices.bulb.getEndpoint(1); const endpoint = device.getEndpoint(1); + target.members.push(target1Member); + target1Member.configureReporting.mockClear(); mockClear(device); MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'group_1'})); await flushPromises(); @@ -137,11 +190,43 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); + expect(target1Member.configureReporting).toHaveBeenCalledTimes(2); + expect(target1Member.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]); + expect(target1Member.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); + + // Should configure reproting for device added to group + target1Member.configureReporting.mockClear(); + await MQTT.events.message('zigbee2mqtt/bridge/group/group_1/add', 'bulb'); + await flushPromises(); + expect(target1Member.configureReporting).toHaveBeenCalledTimes(2); + expect(target1Member.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]); + expect(target1Member.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]); + }); + + it('Should unbind from group', async () => { + const device = zigbeeHerdsman.devices.remote; + const target = zigbeeHerdsman.groups.group_1; + const target1Member = zigbeeHerdsman.devices.bulb.getEndpoint(1); + const endpoint = device.getEndpoint(1); + target.members.push(target1Member); + target1Member.configureReporting.mockClear(); + mockClear(device); + MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'group_1'})); + await flushPromises(); + expect(endpoint.unbind).toHaveBeenCalledTimes(3); + expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); + expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target); + expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/unbind', + stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); }); it('Should bind to group by number', async () => { @@ -267,7 +352,6 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'bulb_color', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -298,7 +382,6 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'bulb_color', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -319,7 +402,6 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'Coordinator', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -339,7 +421,6 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'group_1', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -359,7 +440,6 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target); expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'group_1', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -413,7 +493,6 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", 901); expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", 901); expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", 901); - expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'default_bind_group', cluster: 'genScenes'}}); expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log'); @@ -421,4 +500,36 @@ describe('Bind', () => { expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('zigbee2mqtt/bridge/log'); expect(JSON.parse(MQTT.publish.mock.calls[2][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'default_bind_group', cluster: 'genLevelCtrl'}}); }); + + it('Should poll bounded Hue bulb when receiving message from Hue dimmer', async () => { + const remote = zigbeeHerdsman.devices.remote; + const data = {"button":3,"unknown1":3145728,"type":2,"unknown2":0,"time":1}; + const payload = {data, cluster: 'manuSpecificPhilips', device: remote, endpoint: remote.getEndpoint(2), type: 'commandHueNotification', linkquality: 10, groupID: 0}; + await zigbeeHerdsman.events.message(payload); + await flushPromises(); + expect(debounce).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.devices.bulb_color.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]); + }); + + it('Should poll grouped Hue bulb when receiving message from TRADFRI remote', async () => { + zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read.mockClear(); + zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read.mockClear(); + const remote = zigbeeHerdsman.devices.tradfri_remote; + const data = {"stepmode":0,"stepsize":43,"transtime":5}; + const payload = {data, cluster: 'genLevelCtrl', device: remote, endpoint: remote.getEndpoint(1), type: 'commandStepWithOnOff', linkquality: 10, groupID: 15071}; + await zigbeeHerdsman.events.message(payload); + await flushPromises(); + expect(debounce).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]); + + // Should also only debounce once + await zigbeeHerdsman.events.message(payload); + await flushPromises(); + expect(debounce).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(2); + + // Should only call Hue bulb, not e.g. tradfri + expect(zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read).toHaveBeenCalledTimes(0); + }); }); diff --git a/test/bridge.test.js b/test/bridge.test.js index 0d4d79b08..f7eb6784d 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -41,7 +41,7 @@ describe('Bridge', () => { const directory = settings.get().advanced.log_directory; expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', - stringify({ "commit": version.commitHash, "config": { "advanced": { "adapter_concurrent": null, "adapter_delay": null, "availability_blacklist": [], "availability_blocklist": [], "availability_passlist": [], "availability_timeout": 0, "availability_whitelist": [], "cache_state": true, "cache_state_persistent": true, "cache_state_send_on_startup": true, "channel": 11, "elapsed": false, "ext_pan_id": [221, 221, 221, 221, 221, 221, 221, 221], "homeassistant_discovery_topic": "homeassistant", "homeassistant_legacy_triggers": true, "homeassistant_status_topic": "hass/status", "last_seen": "disable", "legacy_api": false, "log_directory": directory, "log_file": "log.txt", "log_level": "info", "log_output": ["console", "file"], "log_rotation": true, "log_syslog": {}, "pan_id": 6754, "report": false, "soft_reset_timeout": 0, "timestamp_format": "YYYY-MM-DD HH:mm:ss" }, "ban": [], "blocklist": [], "device_options": {}, "devices": { "0x000b57fffec6a5b2": { "friendly_name": "bulb", "retain": true }, "0x000b57fffec6a5b3": { "friendly_name": "bulb_color", "retain": false }, "0x000b57fffec6a5b4": { "friendly_name": "bulb_color_2", "retain": false }, "0x000b57fffec6a5b7": { "friendly_name": "bulb_2", "retain": false }, "0x0017880104a44559": { "friendly_name": "J1_cover" }, "0x0017880104e43559": { "friendly_name": "U202DST600ZB" }, "0x0017880104e44559": { "friendly_name": "3157100_thermostat" }, "0x0017880104e45517": { "friendly_name": "remote", "retain": true }, "0x0017880104e45518": { "friendly_name": "0x0017880104e45518" }, "0x0017880104e45520": { "friendly_name": "button", "retain": false }, "0x0017880104e45521": { "friendly_name": "button_double_key", "retain": false }, "0x0017880104e45522": { "friendly_name": "weather_sensor", "qos": 1, "retain": false }, "0x0017880104e45523": { "friendly_name": "occupancy_sensor", "retain": false }, "0x0017880104e45524": { "friendly_name": "power_plug", "retain": false }, "0x0017880104e45526": { "friendly_name": "GL-S-007ZS" }, "0x0017880104e45529": { "friendly_name": "unsupported2", "retain": false }, "0x0017880104e45530": { "friendly_name": "button_double_key_interviewing", "retain": false }, "0x0017880104e45540": { "friendly_name": "ikea_onoff" }, "0x0017880104e45541": { "friendly_name": "wall_switch", "retain": false }, "0x0017880104e45542": { "friendly_name": "wall_switch_double", "retain": false }, "0x0017880104e45543": { "friendly_name": "led_controller_1", "retain": false }, "0x0017880104e45544": { "friendly_name": "led_controller_2", "retain": false }, "0x0017880104e45545": { "friendly_name": "dimmer_wall_switch", "retain": false }, "0x0017880104e45547": { "friendly_name": "curtain", "retain": false }, "0x0017880104e45548": { "friendly_name": "fan", "retain": false }, "0x0017880104e45549": { "friendly_name": "siren", "retain": false }, "0x0017880104e45550": { "friendly_name": "thermostat", "retain": false }, "0x0017880104e45551": { "friendly_name": "smart vent", "retain": false }, "0x0017880104e45552": { "friendly_name": "j1", "retain": false }, "0x0017880104e45553": { "friendly_name": "bulb_enddevice", "retain": false }, "0x0017880104e45559": { "friendly_name": "cc2530_router", "retain": false }, "0x0017880104e45560": { "friendly_name": "livolo", "retain": false }, "0x0017882104a44559": { "friendly_name": "TS0601_thermostat" }, "0x0017882194e45543": { "friendly_name": "QS-Zigbee-D02-TRIAC-2C-LN" }, "0x90fd9ffffe4b64aa": { "friendly_name": "SP600_OLD" }, "0x90fd9ffffe4b64ab": { "friendly_name": "SP600_NEW" }, "0x90fd9ffffe4b64ac": { "friendly_name": "MKS-CM-W5" }, "0x90fd9ffffe4b64ae": { "friendly_name": "tradfri_remote", "retain": false }, "0x90fd9ffffe4b64af": { "friendly_name": "roller_shutter" }, "0x90fd9ffffe4b64ax": { "friendly_name": "ZNLDP12LM" } }, "experimental": { "output": "json" }, "external_converters": [], "groups": { "1": { "friendly_name": "group_1", "retain": false }, "11": { "devices": ["bulb_2"], "friendly_name": "group_with_tradfri", "retain": false }, "14": { "devices": ["power_plug"], "friendly_name": "switch_group", "retain": false }, "12": { "devices": ["TS0601_thermostat"], "friendly_name": "thermostat_group", "retain": false }, "15071": { "devices": ["bulb_color_2", "bulb_2"], "friendly_name": "group_tradfri_remote", "retain": false }, "2": { "friendly_name": "group_2", "retain": false } }, "homeassistant": false, "map_options": { "graphviz": { "colors": { "fill": { "coordinator": "#e04e5d", "enddevice": "#fff8ce", "router": "#4ea3e0" }, "font": { "coordinator": "#ffffff", "enddevice": "#000000", "router": "#ffffff" }, "line": { "active": "#009900", "inactive": "#994444" } } } }, "mqtt": { "base_topic": "zigbee2mqtt", "include_device_information": false, "server": "mqtt://localhost", "force_disable_retain": false, }, "passlist": [], "permit_join": true, "serial": { "disable_led": false, "port": "/dev/dummy" }, "whitelist": [] }, "coordinator": { "meta": { "revision": 20190425, "version": 1 }, "type": "z-Stack" }, "log_level": "info", "network": { "channel": 15, "extended_pan_id": [0, 11, 22], "pan_id": 5674 }, "permit_join": false, "version": version.version }), + stringify({ "commit": version.commitHash, "config": { "advanced": { "adapter_concurrent": null, "adapter_delay": null, "availability_blacklist": [], "availability_blocklist": [], "availability_passlist": [], "availability_timeout": 0, "availability_whitelist": [], "cache_state": true, "cache_state_persistent": true, "cache_state_send_on_startup": true, "channel": 11, "elapsed": false, "ext_pan_id": [221, 221, 221, 221, 221, 221, 221, 221], "homeassistant_discovery_topic": "homeassistant", "homeassistant_legacy_triggers": true, "homeassistant_status_topic": "hass/status", "last_seen": "disable", "legacy_api": false, "log_directory": directory, "log_file": "log.txt", "log_level": "info", "log_output": ["console", "file"], "log_rotation": true, "log_syslog": {}, "pan_id": 6754, "report": false, "soft_reset_timeout": 0, "timestamp_format": "YYYY-MM-DD HH:mm:ss" }, "ban": [], "blocklist": [], "device_options": {}, "devices": { "0x000b57fffec6a5b2": { "friendly_name": "bulb", "retain": true }, "0x000b57fffec6a5b3": { "friendly_name": "bulb_color", "retain": false }, "0x000b57fffec6a5b4": { "friendly_name": "bulb_color_2", "retain": false }, "0x000b57fffec6a5b7": { "friendly_name": "bulb_2", "retain": false }, "0x0017880104a44559": { "friendly_name": "J1_cover" }, "0x0017880104e43559": { "friendly_name": "U202DST600ZB" }, "0x0017880104e44559": { "friendly_name": "3157100_thermostat" }, "0x0017880104e45517": { "friendly_name": "remote", "retain": true }, "0x0017880104e45518": { "friendly_name": "0x0017880104e45518" }, "0x0017880104e45520": { "friendly_name": "button", "retain": false }, "0x0017880104e45521": { "friendly_name": "button_double_key", "retain": false }, "0x0017880104e45522": { "friendly_name": "weather_sensor", "qos": 1, "retain": false }, "0x0017880104e45523": { "friendly_name": "occupancy_sensor", "retain": false }, "0x0017880104e45524": { "friendly_name": "power_plug", "retain": false }, "0x0017880104e45526": { "friendly_name": "GL-S-007ZS" }, "0x0017880104e45529": { "friendly_name": "unsupported2", "retain": false }, "0x0017880104e45530": { "friendly_name": "button_double_key_interviewing", "retain": false }, "0x0017880104e45540": { "friendly_name": "ikea_onoff" }, "0x0017880104e45541": { "friendly_name": "wall_switch", "retain": false }, "0x0017880104e45542": { "friendly_name": "wall_switch_double", "retain": false }, "0x0017880104e45543": { "friendly_name": "led_controller_1", "retain": false }, "0x0017880104e45544": { "friendly_name": "led_controller_2", "retain": false }, "0x0017880104e45545": { "friendly_name": "dimmer_wall_switch", "retain": false }, "0x0017880104e45547": { "friendly_name": "curtain", "retain": false }, "0x0017880104e45548": { "friendly_name": "fan", "retain": false }, "0x0017880104e45549": { "friendly_name": "siren", "retain": false }, "0x0017880104e45550": { "friendly_name": "thermostat", "retain": false }, "0x0017880104e45551": { "friendly_name": "smart vent", "retain": false }, "0x0017880104e45552": { "friendly_name": "j1", "retain": false }, "0x0017880104e45553": { "friendly_name": "bulb_enddevice", "retain": false }, "0x0017880104e45559": { "friendly_name": "cc2530_router", "retain": false }, "0x0017880104e45560": { "friendly_name": "livolo", "retain": false }, "0x0017882104a44559": { "friendly_name": "TS0601_thermostat" }, "0x0017882194e45543": { "friendly_name": "QS-Zigbee-D02-TRIAC-2C-LN" }, "0x90fd9ffffe4b64aa": { "friendly_name": "SP600_OLD" }, "0x90fd9ffffe4b64ab": { "friendly_name": "SP600_NEW" }, "0x90fd9ffffe4b64ac": { "friendly_name": "MKS-CM-W5" }, "0x90fd9ffffe4b64ae": { "friendly_name": "tradfri_remote", "retain": false }, "0x90fd9ffffe4b64af": { "friendly_name": "roller_shutter" }, "0x90fd9ffffe4b64ax": { "friendly_name": "ZNLDP12LM" } }, "experimental": { "output": "json" }, "external_converters": [], "groups": { "1": { "friendly_name": "group_1", "retain": false }, "11": { "devices": ["bulb_2"], "friendly_name": "group_with_tradfri", "retain": false }, "14": { "devices": ["power_plug"], "friendly_name": "switch_group", "retain": false }, "12": { "devices": ["TS0601_thermostat"], "friendly_name": "thermostat_group", "retain": false }, "15071": { "devices": ["bulb_color_2", "bulb_2"], "friendly_name": "group_tradfri_remote", "retain": false }, "2": { "friendly_name": "group_2", "retain": false } }, "homeassistant": false, "map_options": { "graphviz": { "colors": { "fill": { "coordinator": "#e04e5d", "enddevice": "#fff8ce", "router": "#4ea3e0" }, "font": { "coordinator": "#ffffff", "enddevice": "#000000", "router": "#ffffff" }, "line": { "active": "#009900", "inactive": "#994444" } } } }, "mqtt": { "base_topic": "zigbee2mqtt", "include_device_information": false, "server": "mqtt://localhost", "force_disable_retain": false, }, "passlist": [], "permit_join": true, "serial": { "disable_led": false, "port": "/dev/dummy" }, "whitelist": [] }, "coordinator": { "meta": { "revision": 20190425, "version": 1 }, "type": "z-Stack" }, "log_level": "info", "network": { "channel": 15, "extended_pan_id": [0, 11, 22], "pan_id": 5674 }, "permit_join": false, "version": version.version, config_schema: settings.schema }), { retain: true, qos: 0 }, expect.any(Function) ); @@ -50,7 +50,7 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', - stringify([{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":[],"output":[]},"configured_reportings":[]}},"friendly_name":"Coordinator","ieee_address":"0x00124b00120144ae","interview_completed":false,"interviewing":false,"model_id":null,"network_address":0,"power_source":null,"software_build_id":null,"supported":false,"type":"Coordinator"},{"date_code":null,"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","property":"color_temp","type":"numeric","unit":"mired","value_max":500,"value_min":150}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","vendor":"IKEA"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[{"attribute":"onOff","cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}]}},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","interview_completed":true,"interviewing":false,"model_id":"TRADFRI bulb E27 WS opal 980lm","network_address":40369,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"Hue dimmer switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["on-press","on-hold","on-hold-release","up-press","up-hold","up-hold-release","down-press","down-hold","down-hold-release","off-press","off-hold","off-hold-release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"324131092621","vendor":"Philips"},"endpoints":{"1":{"bindings":[{"cluster":"genLevelCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"id":1,"type":"group"}}],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]},"2":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genOta","genOnOff"]},"configured_reportings":[]}},"friendly_name":"remote","ieee_address":"0x0017880104e45517","interview_completed":true,"interviewing":false,"model_id":"RWL021","network_address":6535,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]}},"friendly_name":"0x0017880104e45518","ieee_address":"0x0017880104e45518","interview_completed":true,"interviewing":false,"model_id":"notSupportedModelID","network_address":6536,"power_source":"Battery","software_build_id":null,"supported":false,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara wireless switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["single","double","tripple","quadruple","hold","release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WXKG11LM","vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]}},"friendly_name":"button","ieee_address":"0x0017880104e45520","interview_completed":true,"interviewing":false,"model_id":"lumi.sensor_switch.aq2","network_address":6537,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Mi power plug ZigBee","exposes":[{"features":[{"access":7,"description":"On/off state of the switch","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"}],"type":"switch"},{"access":5,"description":"Instantaneous measured power","name":"power","property":"power","type":"numeric","unit":"W"},{"access":5,"description":"Sum of consumed energy","name":"energy","property":"energy","type":"numeric","unit":"kWh"},{"access":1,"description":"Measured temperature value","name":"temperature","property":"temperature","type":"numeric","unit":"°C"},{"access":3,"description":"Enable/disable the power outage memory, this recovers the on/off mode after power failure","name":"power_outage_memory","property":"power_outage_memory","type":"binary","value_off":false,"value_on":true},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"ZNCZ02LM","vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[]}},"friendly_name":"power_plug","ieee_address":"0x0017880104e45524","interview_completed":true,"interviewing":false,"model_id":"lumi.plug","network_address":6540,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"}]), + stringify( [{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":[],"output":[]},"configured_reportings":[]}},"friendly_name":"Coordinator","ieee_address":"0x00124b00120144ae","interview_completed":false,"interviewing":false,"model_id":null,"network_address":0,"power_source":null,"software_build_id":null,"supported":false,"type":"Coordinator"},{"date_code":null,"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454}],"property":"color_temp","type":"numeric","unit":"mired","value_max":454,"value_min":250},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":454,"value_min":250}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","supports_ota":true,"vendor":"IKEA"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[{"attribute":"onOff","cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}]}},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","interview_completed":true,"interviewing":false,"model_id":"TRADFRI bulb E27 WS opal 980lm","network_address":40369,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"Hue dimmer switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["on-press","on-hold","on-hold-release","up-press","up-hold","up-hold-release","down-press","down-hold","down-hold-release","off-press","off-hold","off-hold-release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"324131092621","supports_ota":true,"vendor":"Philips"},"endpoints":{"1":{"bindings":[{"cluster":"genLevelCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"lightingColorCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"id":1,"type":"group"}},{"cluster":"genLevelCtrl","target":{"id":1,"type":"group"}}],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]},"2":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genOta","genOnOff"]},"configured_reportings":[]}},"friendly_name":"remote","ieee_address":"0x0017880104e45517","interview_completed":true,"interviewing":false,"model_id":"RWL021","network_address":6535,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]}},"friendly_name":"0x0017880104e45518","ieee_address":"0x0017880104e45518","interview_completed":true,"interviewing":false,"model_id":"notSupportedModelID","network_address":6536,"power_source":"Battery","software_build_id":null,"supported":false,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara wireless switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["single","double","triple","quadruple","hold","release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WXKG11LM","supports_ota":false,"vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[]}},"friendly_name":"button","ieee_address":"0x0017880104e45520","interview_completed":true,"interviewing":false,"model_id":"lumi.sensor_switch.aq2","network_address":6537,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Mi power plug ZigBee","exposes":[{"features":[{"access":7,"description":"On/off state of the switch","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"}],"type":"switch"},{"access":5,"description":"Instantaneous measured power","name":"power","property":"power","type":"numeric","unit":"W"},{"access":1,"description":"Sum of consumed energy","name":"energy","property":"energy","type":"numeric","unit":"kWh"},{"access":1,"description":"Measured temperature value","name":"temperature","property":"temperature","type":"numeric","unit":"°C"},{"access":3,"description":"Enable/disable the power outage memory, this recovers the on/off mode after power failure","name":"power_outage_memory","property":"power_outage_memory","type":"binary","value_off":false,"value_on":true},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"ZNCZ02LM","supports_ota":false,"vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[]}},"friendly_name":"power_plug","ieee_address":"0x0017880104e45524","interview_completed":true,"interviewing":false,"model_id":"lumi.plug","network_address":6540,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -96,7 +96,6 @@ describe('Bridge', () => { MQTT.publish.mockClear(); await zigbeeHerdsman.events.deviceJoined({device: zigbeeHerdsman.devices.bulb}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({"type":"device_joined","data":{"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2"}}), @@ -158,7 +157,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - stringify({"data":{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","property":"color_temp","type":"numeric","unit":"mired","value_max":500,"value_min":150}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","vendor":"IKEA"},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","status":"successful","supported":true},"type":"device_interview"}), + stringify({"data":{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454}],"property":"color_temp","type":"numeric","unit":"mired","value_max":454,"value_min":250},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":454,"value_min":250}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","supports_ota":true,"vendor":"IKEA"},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","status":"successful","supported":true},"type":"device_interview"}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -201,8 +200,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', 'true'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":true},"status":"ok"}), @@ -214,8 +212,19 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false})); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/permit_join', + stringify({"data":{"value":false},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + + zigbeeHerdsman.permitJoin.mockClear(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": "False"})); + await flushPromises(); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":false},"status":"ok"}), @@ -235,6 +244,27 @@ describe('Bridge', () => { ); }); + it('Should allow permit join for certain time', async () => { + zigbeeHerdsman.permitJoin.mockClear(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false, "time": 10})); + await flushPromises(); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, 10); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/permit_join', + stringify({"data":{"value":false,"time": 10},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should republish bridge info when permit join changes', async () => { + MQTT.publish.mockClear(); + await zigbeeHerdsman.events.permitJoinChanged({permitted: false, time: 10}); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + }); + it('Should allow permit join via device', async () => { const device = zigbeeHerdsman.devices.bulb; zigbeeHerdsman.permitJoin.mockClear(); @@ -242,8 +272,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({value: true, device: 'bulb'})); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, device); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, device, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":true,"device":"bulb"},"status":"ok"}), @@ -457,6 +486,17 @@ describe('Bridge', () => { ); }); + it('Shouldnt allow rename device with to now allowed name', async () => { + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb', to: 'living_room/blinds/center'})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/rename', + stringify({"data":{},"status":"error","error":"friendly_name is not allowed to end with: '/center'"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should allow rename group', async () => { MQTT.publish.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', stringify({from: 'group_1', to: 'group_new_name'})); @@ -567,6 +607,17 @@ describe('Bridge', () => { ); }); + it('Should throw not implemented erron on bridge/request/options', async () => { + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/options', stringify({})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/options', + stringify({"data":{},"status":"error","error":"Saving settings is not yet implemented"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should allow to add group by string', async () => { MQTT.publish.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/request/group/add', 'group_193'); diff --git a/test/configure.test.js b/test/configure.test.js index 8f68310b4..4975fec3e 100644 --- a/test/configure.test.js +++ b/test/configure.test.js @@ -137,11 +137,11 @@ describe('Configure', () => { }); it('Fail to configure via MQTT when device has no configure', async () => { - await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb", transaction: 20})); + await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb_enddevice", transaction: 20})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', - stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}), + stringify({"data":{"id": "bulb_enddevice"},"status":"error","error": "Device 'bulb_enddevice' cannot be configured","transaction":20}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -161,9 +161,9 @@ describe('Configure', () => { }); it('Legacy api: Should skip reconfigure when device does not require this', async () => { - await MQTT.events.message('zigbee2mqtt/bridge/configure', 'bulb'); + await MQTT.events.message('zigbee2mqtt/bridge/configure', '0x0017880104e45553'); await flushPromises(); - expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'bulb', device does not require this.`) + expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'bulb_enddevice', device does not require this.`) }); it('Should not configure when interviewing', async () => { diff --git a/test/controller.test.js b/test/controller.test.js index be03c65f3..691bb1c6d 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -36,7 +36,7 @@ describe('Controller', () => { expect(zigbeeHerdsman.setLED).toHaveBeenCalledTimes(0); expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); expect(logger.info).toHaveBeenCalledWith(`Currently ${Object.values(zigbeeHerdsman.devices).length - 1} devices are joined:`) expect(logger.info).toHaveBeenCalledWith('bulb (0x000b57fffec6a5b2): LED1545G12 - IKEA TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (Router)'); expect(logger.info).toHaveBeenCalledWith('remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch (EndDevice)'); @@ -93,12 +93,15 @@ describe('Controller', () => { expect(MQTT.connect).toHaveBeenCalledWith("mqtt://localhost", expected); }); - it('Should generate network_key when set to GENERATE', async () => { + it('Should generate network_key and pan_id when set to GENERATE', async () => { settings.set(['advanced', 'network_key'], 'GENERATE'); + settings.set(['advanced', 'pan_id'], 'GENERATE'); await controller.start(); await flushPromises(); expect(zigbeeHerdsman.constructor.mock.calls[0][0].network.networkKey.length).toStrictEqual(16); + expect(zigbeeHerdsman.constructor.mock.calls[0][0].network.panID).toStrictEqual(expect.any(Number)); expect(data.read().advanced.network_key.length).toStrictEqual(16); + expect(data.read().advanced.pan_id).toStrictEqual(expect.any(Number)); }); it('Start controller should publish cached states', async () => { @@ -202,7 +205,7 @@ describe('Controller', () => { settings.set(['permit_join'], false); await controller.start(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); }); it('Start controller with disable_led', async () => { diff --git a/test/externalConverters.test.js b/test/externalConverters.test.js index 705e1ecc6..b6bef0780 100644 --- a/test/externalConverters.test.js +++ b/test/externalConverters.test.js @@ -70,10 +70,11 @@ describe('Loads external converters', () => { mock: true, zigbeeModel: ['external_converter_device'], vendor: 'external', - model: ['external_converter_device'], - definition: { - foo: 'bar' - } + model: 'external_converter_device', + description: 'external', + fromZigbee: [], + toZigbee: [], + exposes: [] }); }); @@ -87,10 +88,23 @@ describe('Loads external converters', () => { expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2); expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(1, { mock: 1, - model: 'external_converters_device', + model: 'external_converters_device_1', + zigbeeModel: ['external_converter_device_1'], + vendor: 'external_1', + description: 'external_1', + fromZigbee: [], + toZigbee: [], + exposes: [] }); expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(2, { - mock: 2 + mock: 2, + model: 'external_converters_device_2', + zigbeeModel: ['external_converter_device_2'], + vendor: 'external_2', + description: 'external_2', + fromZigbee: [], + toZigbee: [], + exposes: [] }); }); diff --git a/test/group.test.js b/test/group.test.js index 593debc62..d5584c31e 100644 --- a/test/group.test.js +++ b/test/group.test.js @@ -98,6 +98,17 @@ describe('Groups', () => { expect(logger.error).toHaveBeenCalledWith("Cannot find 'not_existing_bla' of group 'group_1'"); }); + it('Should resolve device friendly names', async () => { + settings.set(['devices', zigbeeHerdsman.devices.bulb.ieeeAddr, 'friendly_name'], 'bulb_friendly_name'); + settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: ['bulb_friendly_name', 'bulb_color']}}); + await controller.start(); + await flushPromises(); + expect(zigbeeHerdsman.groups.group_1.members).toStrictEqual([ + zigbeeHerdsman.devices.bulb.getEndpoint(1), + zigbeeHerdsman.devices.bulb_color.getEndpoint(1) + ]); + }); + it('Legacy api: Add to group via MQTT', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); @@ -328,6 +339,38 @@ describe('Groups', () => { expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); + it('Should publish state change for group when members state change', async () => { + // Created for https://github.com/Koenkk/zigbee2mqtt/issues/5725 + const device = zigbeeHerdsman.devices.bulb_color; + const endpoint = device.getEndpoint(1); + const group = zigbeeHerdsman.groups.group_1; + group.members.push(endpoint); + settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [device.ieeeAddr]}}); + await controller.start(); + await flushPromises(); + + MQTT.publish.mockClear(); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledTimes(2); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + + MQTT.publish.mockClear(); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'OFF'})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledTimes(2); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function)); + + MQTT.publish.mockClear(); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledTimes(2); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + }); + it('Should publish state change of all members when a group changes its state, filtered', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index e10f7f7fa..55e8d79f4 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -529,6 +529,24 @@ describe('HomeAssistant extension', () => { expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/temperature/config') }); + it('Shouldnt discover sensor when set to null', async () => { + logger.error.mockClear(); + settings.set(['devices', '0x0017880104e45522'], { + homeassistant: {humidity: null}, + friendly_name: 'weather_sensor', + retain: false, + }) + + controller = new Controller(false); + await controller.start(); + + await flushPromises(); + + const topics = MQTT.publish.mock.calls.map((c) => c[0]); + expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/humidity/config') + expect(topics).toContain('homeassistant/sensor/0x0017880104e45522/temperature/config') + }); + it('Should discover devices with fan', async () => { controller = new Controller(false); await controller.start(); @@ -1238,6 +1256,28 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/device_automation/0x0017880104e45520/action_double/config', expect.any(String), expect.any(Object), expect.any(Function)); }); + it('Should not discover device_automtation when disabled', async () => { + settings.set(['device_options'], { + homeassistant: {device_automation: null}, + }) + controller = new Controller(false); + await controller.start(); + await flushPromises(); + MQTT.publish.mockClear(); + + const device = zigbeeHerdsman.devices.WXKG11LM; + const payload1 = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}; + await zigbeeHerdsman.events.message(payload1); + await flushPromises(); + + expect(MQTT.publish).not.toHaveBeenCalledWith( + 'homeassistant/device_automation/0x0017880104e45520/action_single/config', + expect.any(String), + expect.any(Object), + expect.any(Function), + ); + }); + it('Should not discover sensor_click when legacy: false is set', async () => { settings.set(['devices', '0x0017880104e45520'], { legacy: false, @@ -1364,14 +1404,11 @@ 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')); - const beforeCount = Object.entries((new HomeAssistant(null, null, null, null, {on: () => {}}))._getMapping()).length; settings.set(['external_converters'], ['mock-external-converter-multiple.js']); controller = new Controller(); const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant'); await controller.start(); await flushPromises(); - const afterCount = Object.entries(ha._getMapping()).length; - expect(beforeCount + 1).toStrictEqual(afterCount); const homeassistantSwitch = { type: 'switch', @@ -1383,7 +1420,7 @@ describe('HomeAssistant extension', () => { command_topic: true, }, }; - expect(ha._getMapping()['external_converters_device']).toEqual([homeassistantSwitch]); + expect(ha._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]); }); it('Should clear outdated configs', async () => { diff --git a/test/legacy/bridgeLegacy.test.js b/test/legacy/bridgeLegacy.test.js index 8823eb2ab..f94c5cba2 100644 --- a/test/legacy/bridgeLegacy.test.js +++ b/test/legacy/bridgeLegacy.test.js @@ -123,12 +123,12 @@ describe('Bridge legacy', () => { MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'true'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); zigbeeHerdsman.permitJoin.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'false'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); }); it('Should allow to reset', async () => { diff --git a/test/report.test.js b/test/legacy/report.test.js similarity index 86% rename from test/report.test.js rename to test/legacy/report.test.js index ec6b39d24..70725c02b 100644 --- a/test/report.test.js +++ b/test/legacy/report.test.js @@ -1,6 +1,6 @@ -const data = require('./stub/data'); -const logger = require('./stub/logger'); -const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); +const data = require('../stub/data'); +const logger = require('../stub/logger'); +const zigbeeHerdsman = require('../stub/zigbeeHerdsman'); zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3'); zigbeeHerdsman.returnDevices.push('0x00124b00120144ae'); zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b2'); @@ -8,16 +8,15 @@ zigbeeHerdsman.returnDevices.push('0x0017880104e45553'); zigbeeHerdsman.returnDevices.push('0x0017880104e45559'); zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b4'); zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b7'); +zigbeeHerdsman.returnDevices.push('0x0017880104e45524'); zigbeeHerdsman.returnDevices.push('0x90fd9ffffe4b64ax'); -const MQTT = require('./stub/mqtt'); -const settings = require('../lib/util/settings'); -const Controller = require('../lib/controller'); +const MQTT = require('../stub/mqtt'); +const settings = require('../../lib/util/settings'); +const Controller = require('../../lib/controller'); const flushPromises = () => new Promise(setImmediate); const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); -jest.mock('debounce', () => jest.fn(fn => fn)); -const debounce = require('debounce'); -const mocksClear = [MQTT.publish, logger.warn, logger.debug, debounce]; +const mocksClear = [MQTT.publish, logger.warn, logger.debug]; describe('Report', () => { let controller; @@ -240,36 +239,6 @@ describe('Report', () => { expect(endpoint.bind).toHaveBeenCalledTimes(1); }); - it('Should poll bounded Hue bulb when receiving message from Hue dimmer', async () => { - const remote = zigbeeHerdsman.devices.remote; - const data = {"button":3,"unknown1":3145728,"type":2,"unknown2":0,"time":1}; - const payload = {data, cluster: 'manuSpecificPhilips', device: remote, endpoint: remote.getEndpoint(2), type: 'commandHueNotification', linkquality: 10, groupID: 0}; - await zigbeeHerdsman.events.message(payload); - await flushPromises(); - expect(debounce).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.devices.bulb_color.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]); - }); - - it('Should poll grouped Hue bulb when receiving message from TRADFRI remote and should', async () => { - const remote = zigbeeHerdsman.devices.tradfri_remote; - const data = {"stepmode":0,"stepsize":43,"transtime":5}; - const payload = {data, cluster: 'genLevelCtrl', device: remote, endpoint: remote.getEndpoint(1), type: 'commandStepWithOnOff', linkquality: 10, groupID: 15071}; - await zigbeeHerdsman.events.message(payload); - await flushPromises(); - expect(debounce).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]); - - // Should also only debounce once - await zigbeeHerdsman.events.message(payload); - await flushPromises(); - expect(debounce).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(2); - - // Should only call Hue bulb, not e.g. tradfri - expect(zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read).toHaveBeenCalledTimes(0); - }); - it('Should not configure reporting for the ZNLDP12LM closuresWindowCovering as it is ignored', async () => { const device = zigbeeHerdsman.devices.ZNLDP12LM; const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1); diff --git a/test/logger.test.js b/test/logger.test.js index 6ef931241..f9d708a5c 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -6,6 +6,7 @@ const path = require('path'); const data = require('./stub/data'); let stdOutWriteOriginal; const rimraf = require('rimraf'); +const Transport = require('winston-transport'); describe('Logger', () => { beforeEach(async () => { @@ -66,6 +67,26 @@ describe('Logger', () => { expect(logger.getLevel()).toBe('debug'); }); + it('Add transport', () => { + class DummyTransport extends Transport { + log(info, callback) { + } + } + + const logger = require('../lib/util/logger.js'); + expect(logger.transports.length).toBe(2); + logger.addTransport(new DummyTransport()); + expect(logger.transports.length).toBe(3); + }); + + it('Set and get log level warn <-> warning', () => { + const logger = require('../lib/util/logger.js'); + logger.logOutput(); + logger.setLevel('warn'); + expect(logger.transports[0].level).toBe('warning'); + expect(logger.getLevel()).toBe('warn'); + }); + it('Logger should be console and file by default', () => { const logger = require('../lib/util/logger.js'); logger.logOutput(); diff --git a/test/networkMap.test.js b/test/networkMap.test.js index d839c8a59..a336aaaba 100644 --- a/test/networkMap.test.js +++ b/test/networkMap.test.js @@ -17,10 +17,10 @@ const MQTT = require('./stub/mqtt'); const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); Date.now = jest.fn() Date.now.mockReturnValue(10000); const mocksClear = [MQTT.publish, logger.warn, logger.debug]; +const setTimeoutNative = setTimeout; describe('Networkmap', () => { let controller; @@ -43,6 +43,11 @@ describe('Networkmap', () => { const device = zigbeeHerdsman.devices.bulb_color; device.lastSeen = 1000; external_converter_device.lastSeen = 1000; + global.setTimeout = (r) => r(); + }); + + afterEach(async () => { + global.setTimeout = setTimeoutNative; }); function mock() { @@ -92,7 +97,7 @@ describe('Networkmap', () => { let call = MQTT.publish.mock.calls[0]; expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw'); - const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_xy), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"model":["external_converter_device"],"supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}; + const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description": "external","model":"external_converter_device","supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}; expect(JSON.parse(call[1])).toStrictEqual(expected); /** @@ -143,7 +148,7 @@ describe('Networkmap', () => { "0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (6536)failed: lqi,routingTable|Boef notSupportedModelID|9 seconds ago}"]; "0x0017880104e45559" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{cc2530_router|0x0017880104e45559 (6540)|Custom devices (DiY) [CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/) (CC2530.ROUTER)|9 seconds ago}"]; "0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"] - "0x0017880104e45511" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{0x0017880104e45511|0x0017880104e45511 (1114)|external undefined (external_converter_device)|9 seconds ago}"]; + "0x0017880104e45511" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{0x0017880104e45511|0x0017880104e45511 (1114)|external external (external_converter_device)|9 seconds ago}"]; "0x0017880104e45511" -> "0x00124b00120144ae" [penwidth=1, weight=0, color="#994444", label="92"] }`; @@ -177,7 +182,7 @@ describe('Networkmap', () => { --- 0x0017880104e45511 (1114) --- - external undefined (external_converter_device) + external external (external_converter_device) --- 9 seconds ago ] @@ -266,7 +271,7 @@ describe('Networkmap', () => { let call = MQTT.publish.mock.calls[0]; expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap'); - const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_xy), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"model":["external_converter_device"],"supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"}; + const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"}; const actual = JSON.parse(call[1]); expect(actual).toStrictEqual(expected); }); diff --git a/test/publish.test.js b/test/publish.test.js index 58b309da7..87b1bde64 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -354,7 +354,7 @@ describe('Publish', () => { await MQTT.events.message('zigbee2mqtt/thermostat_group/set', stringify({child_lock: 'LOCK'})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); - expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 7, length_hi: 0, length_lo: 1, status: 0, transid: 0}, {disableDefaultResponse: true}); + expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 7, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true}); }); it('Should publish messages to groups with on and brightness', async () => { @@ -1247,7 +1247,7 @@ describe('Publish', () => { await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); - expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 3, length_hi: 0, length_lo: 1, status: 0, transid: 1}, {disableDefaultResponse: true}); + expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 3, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true}); expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5'); expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'}); diff --git a/test/receive.test.js b/test/receive.test.js index 7d4dbdb6b..9eacb279b 100755 --- a/test/receive.test.js +++ b/test/receive.test.js @@ -314,7 +314,7 @@ describe('Receive', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/occupancy_sensor'); - expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045}); + expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045, 'temperature': 19}); expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false}); }); diff --git a/test/settings.test.js b/test/settings.test.js index 97240fdbd..4bafd2219 100644 --- a/test/settings.test.js +++ b/test/settings.test.js @@ -622,6 +622,18 @@ describe('Settings', () => { expect(settings.validate()).toEqual(expect.arrayContaining([error])); }); + it('Should not allow any string values for pan_id', () => { + write(configurationFile, { + ...minimalConfig, + advanced: {pan_id: 'NOT_GENERATE'}, + }); + + settings._reRead(); + + const error = `advanced.pan_id: should be number or 'GENERATE' (is 'NOT_GENERATE')`; + expect(settings.validate()).toEqual(expect.arrayContaining([error])); + }); + it('Should allow retention configuration with MQTT v5', () => { write(configurationFile, { ...minimalConfig, @@ -717,6 +729,42 @@ describe('Settings', () => { expect(settings.validate()).toEqual(expect.arrayContaining([error])); }); + it('Configuration friendly name cannot be empty', async () => { + write(configurationFile, { + ...minimalConfig, + devices: {'0x0017880104e45519': {friendly_name: '', retain: false}}, + }); + + settings._reRead(); + + const error = `friendly_name shoud atleast be 1 char long`; + expect(settings.validate()).toEqual(expect.arrayContaining([error])); + }); + + it('Configuration friendly name cannot end with /', async () => { + write(configurationFile, { + ...minimalConfig, + devices: {'0x0017880104e45519': {friendly_name: 'blaa/', retain: false}}, + }); + + settings._reRead(); + + const error = `friendly_name is not allowed to end or start with /`; + expect(settings.validate()).toEqual(expect.arrayContaining([error])); + }); + + it('Configuration friendly name cannot contain null char', async () => { + write(configurationFile, { + ...minimalConfig, + devices: {'0x0017880104e45519': {friendly_name: 'blaa/blaa' + String.fromCharCode(0), retain: false}}, + }); + + settings._reRead(); + + const error = `friendly_name is not allowed to contain null char`; + expect(settings.validate()).toEqual(expect.arrayContaining([error])); + }); + it('Configuration shouldnt be valid when friendly_name ends with /DIGIT', async () => { write(configurationFile, { ...minimalConfig, diff --git a/test/stub/logger.js b/test/stub/logger.js index 82e92b544..5a8db036d 100644 --- a/test/stub/logger.js +++ b/test/stub/logger.js @@ -19,6 +19,7 @@ const mock = { cleanup: jest.fn(), logOutput: jest.fn(), add: (transport) => transports.push(transport), + addTransport: (transport) => transports.push(transport), setLevel: (newLevel) => {level = newLevel}, getLevel: () => level, setTransportsEnabled: (value) => {transportsEnabled = value}, diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index 3faf5a9bd..c178e02e2 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -85,9 +85,11 @@ class Endpoint { Object.values(groups).forEach((g) => this.removeFromGroup(g)) } - this.getClusterAttributeValue = (cluster, value) => { + this.getClusterAttributeValue = jest.fn(); + this.getClusterAttributeValue.mockImplementation((cluster, value) => { + if (!(cluster in this.clusterValues)) return undefined; return this.clusterValues[cluster][value]; - } + }); } } @@ -124,14 +126,25 @@ const bulb_color = new Device('Router', '0x000b57fffec6a5b3', 40399, 4107, [new const bulb_color_2 = new Device('Router', '0x000b57fffec6a5b4', 401292, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b4')], true, "Mains (single phase)", "LLC020", false, 'Philips', '2019.09', '5.127.1.26581'); const bulb_2 = new Device('Router', '0x000b57fffec6a5b7', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b7', [], {lightingColorCtrl: {colorCapabilities: 17}})], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"); const TS0601_thermostat = new Device('EndDevice', '0x0017882104a44559', 6544,4151, [new Endpoint(1, [], [], '0x0017882104a44559')], true, "Mains (single phase)", 'kud7u2l'); +const ZNCZ02LM = new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug"); + +const groups = { + 'group_1': new Group(1, []), + 'group_tradfri_remote': new Group(15071, [bulb_color_2.endpoints[0], bulb_2.endpoints[0]]), + 'group/with/slashes': new Group(99, []), + 'group_with_tradfri': new Group(11, [bulb_2.endpoints[0]]), + 'thermostat_group': new Group(12, [TS0601_thermostat.endpoints[0]]), + 'group_with_switch': new Group(14, [ZNCZ02LM.endpoints[0]]), + 'default_bind_group': new Group(901, []), +} const devices = { - 'coordinator': new Device('Coordinator', '0x00124b00120144ae', 0, 0, [new Endpoint(1, [], [])], false), + 'coordinator': new Device('Coordinator', '0x00124b00120144ae', 0, 0, [new Endpoint(1, [], [], '0x00124b00120144ae')], false), 'bulb': new Device('Router', '0x000b57fffec6a5b2', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b2', [], {lightingColorCtrl: {colorCapabilities: 17}}, [{cluster: {name: 'genOnOff'}, attribute: {name: 'onOff'}, minimumReportInterval: 1, maximumReportInterval: 10, reportableChange: 20}])], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"), 'bulb_color': bulb_color, 'bulb_2': bulb_2, 'bulb_color_2': bulb_color_2, - 'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0], cluster: {ID: 8, name: 'genLevelCtrl'}}, {target: new Group(1, []), cluster: {ID: 6, name: 'genOnOff'}}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"), + 'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0], cluster: {ID: 8, name: 'genLevelCtrl'}}, {target: bulb_color.endpoints[0], cluster: {ID: 6, name: 'genOnOff'}}, {target: bulb_color.endpoints[0], cluster: {ID: 768, name: 'lightingColorCtrl'}}, {target: groups.group_1, cluster: {ID: 6, name: 'genOnOff'}}, {target: groups.group_1, cluster: {ID: 6, name: 'genLevelCtrl'}}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"), 'unsupported': new Device('EndDevice', '0x0017880104e45518', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID", false, "notSupportedMfg"), 'unsupported2': new Device('EndDevice', '0x0017880104e45529', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID"), 'interviewing': new Device('EndDevice', '0x0017880104e45530', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", undefined, true), @@ -140,7 +153,7 @@ const devices = { 'WXKG02LM_rev1': new Device('EndDevice', '0x0017880104e45521', 6538,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Battery", "lumi.sensor_86sw2.es1"), 'WSDCGQ11LM': new Device('EndDevice', '0x0017880104e45522', 6539,4151, [new Endpoint(1, [0], [])], true, "Battery", "lumi.weather"), 'RTCGQ11LM': new Device('EndDevice', '0x0017880104e45523', 6540,4151, [new Endpoint(1, [0], [])], true, "Battery", "lumi.sensor_motion.aq2"), - 'ZNCZ02LM': new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug"), + 'ZNCZ02LM': ZNCZ02LM, 'E1743': new Device('Router', '0x0017880104e45540', 6540,4476, [new Endpoint(1, [0], [])], true, "Mains (single phase)", 'TRADFRI on/off switch'), 'QBKG04LM': new Device('Router', '0x0017880104e45541', 6549,4151, [new Endpoint(1, [0], [25]), new Endpoint(2, [0, 6], [])], true, "Mains (single phase)", 'lumi.ctrl_neutral1'), 'QBKG03LM':new Device('Router', '0x0017880104e45542', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45542'), new Endpoint(2, [0, 6], [], '0x0017880104e45542'), new Endpoint(3, [0, 6], [], '0x0017880104e45542')], true, "Mains (single phase)", 'lumi.ctrl_neutral2'), @@ -173,16 +186,6 @@ const devices = { 'QS_Zigbee_D02_TRIAC_2C_LN':new Device('Router', '0x0017882194e45543', 6549,4151, [new Endpoint(1, [0], [], '0x0017882194e45543'), new Endpoint(2, [0, 6], [], '0x0017882194e45543')], true, "Mains (single phase)", 'TS110F', false, '_TYZB01_v8gtiaed'), } -const groups = { - 'group_1': new Group(1, []), - 'group_tradfri_remote': new Group(15071, [bulb_color_2.endpoints[0], bulb_2.endpoints[0]]), - 'group/with/slashes': new Group(99, []), - 'group_with_tradfri': new Group(11, [bulb_2.endpoints[0]]), - 'thermostat_group': new Group(12, [TS0601_thermostat.endpoints[0]]), - 'group_with_switch': new Group(14, [devices.ZNCZ02LM.endpoints[0]]), - 'default_bind_group': new Group(901, []), -} - const mock = { setTransmitPower: jest.fn(), touchlinkFactoryReset: jest.fn(),