diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 993288b8..76361e39 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -19,11 +19,11 @@ class Bridge extends Extension { 'group/options': this.groupOptions.bind(this), 'group/remove': this.groupRemove.bind(this), 'group/rename': this.groupRename.bind(this), - 'permitjoin': this.permitJoin.bind(this), - 'config/lastseen': this.configLastSeen.bind(this), + 'permit_join': this.permitJoin.bind(this), + 'config/last_seen': this.configLastSeen.bind(this), 'config/elapsed': this.configElapsed.bind(this), - 'config/loglevel': this.configLogLevel.bind(this), - 'touchlink/factoryreset': this.touchlinkFactoryReset.bind(this), + 'config/log_level': this.configLogLevel.bind(this), + 'touchlink/factory_reset': this.touchlinkFactoryReset.bind(this), }; } @@ -62,9 +62,12 @@ class Bridge extends Extension { if (['deviceJoined', 'deviceLeave', 'deviceInterview'].includes(type)) { let payload; const ieeeAddress = data.device ? data.device.ieeeAddr : data.ieeeAddr; - if (type === 'deviceJoined') payload = {friendlyName: resolvedEntity.settings.friendlyName, ieeeAddress}; - else if (type === 'deviceInterview') { - payload = {friendlyName: resolvedEntity.settings.friendlyName, status: data.status, ieeeAddress}; + if (type === 'deviceJoined') { + payload = {friendly_name: resolvedEntity.settings.friendlyName, ieee_address: ieeeAddress}; + } else if (type === 'deviceInterview') { + payload = { + friendly_name: resolvedEntity.settings.friendlyName, status: data.status, ieee_address: ieeeAddress, + }; if (data.status === 'successful') { payload.supported = !!resolvedEntity.definition; payload.definition = resolvedEntity.definition ? { @@ -74,9 +77,13 @@ class Bridge extends Extension { supports: resolvedEntity.definition.supports, } : null; } - } else payload = {ieeeAddress}; // deviceLeave + } else payload = {ieee_address: ieeeAddress}; // deviceLeave - await this.mqtt.publish('bridge/event', JSON.stringify({type, data: payload}), {retain: false, qos: 0}); + await this.mqtt.publish( + 'bridge/event', + JSON.stringify({type: utils.toSnakeCase(type), data: payload}), + {retain: false, qos: 0}, + ); } if ('deviceLeave' === type || ('deviceInterview' === type && data.status !== 'started')) { @@ -105,16 +112,16 @@ class Bridge extends Extension { } async groupAdd(message) { - if (typeof message === 'object' && !message.hasOwnProperty('friendlyName')) { + if (typeof message === 'object' && !message.hasOwnProperty('friendly_name')) { throw new Error(`Invalid payload`); } - const friendlyName = typeof message === 'object' ? message.friendlyName : message; + const friendlyName = typeof message === 'object' ? message.friendly_name : message; const ID = typeof message === 'object' && message.hasOwnProperty('ID') ? message.ID : null; const group = settings.addGroup(friendlyName, ID); this.zigbee.createGroup(group.ID); this.publishGroups(); - return utils.getResponse(message, {friendlyName: group.friendlyName, ID: group.ID}, null); + return utils.getResponse(message, {friendly_name: group.friendlyName, ID: group.ID}, null); } async deviceRename(message) { @@ -320,8 +327,8 @@ class Bridge extends Extension { version: this.zigbee2mqttVersion.version, commit: this.zigbee2mqttVersion.commitHash, coordinator: this.coordinatorVersion, - logLevel: logger.getLevel(), - permitJoin: await this.zigbee.getPermitJoin(), + log_level: logger.getLevel(), + permit_join: await this.zigbee.getPermitJoin(), }; await this.mqtt.publish('bridge/info', JSON.stringify(payload), {retain: true, qos: 0}); @@ -339,17 +346,17 @@ class Bridge extends Extension { } : null; return { - ieeeAddress: device.ieeeAddr, + ieee_address: device.ieeeAddr, type: device.type, - networkAddress: device.networkAddress, + network_address: device.networkAddress, supported: !!definition, - friendlyName: resolved.name, + friendly_name: resolved.name, definition: definitionPayload, - powerSource: device.powerSource, - softwareBuildID: device.softwareBuildID, - dateCode: device.dateCode, + power_source: device.powerSource, + software_build_ID: device.softwareBuildID, + date_code: device.dateCode, interviewing: device.interviewing, - interviewCompleted: device.interviewCompleted, + interview_completed: device.interviewCompleted, }; }); @@ -361,10 +368,10 @@ class Bridge extends Extension { const resolved = this.zigbee.resolveEntity(group); return { ID: group.groupID, - friendlyName: resolved.name, + friendly_name: resolved.name, members: group.members.map((m) => { return { - ieeeAddress: m.deviceIeeeAddress, + ieee_address: m.deviceIeeeAddress, endpoint: m.ID, }; }), diff --git a/lib/extension/otaUpdate.js b/lib/extension/otaUpdate.js index 36881ee2..a28f7472 100644 --- a/lib/extension/otaUpdate.js +++ b/lib/extension/otaUpdate.js @@ -2,7 +2,8 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); const utils = require('../util/utils'); const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/ota_update/.+$`); -const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/otaUpdate/(update|check)`, 'i'); +const topicRegex = + new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check)`, 'i'); const Extension = require('./extension'); const MINUTES_10 = 1000 * 60 * 10; @@ -24,10 +25,8 @@ class OTAUpdate extends Extension { /* istanbul ignore else */ if (settings.get().experimental.new_api) { - this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/otaUpdate/check`); - this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/otaUpdate/update`); - this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/otaupdate/check`); - this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/otaupdate/update`); + this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/check`); + this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/update`); } } @@ -200,8 +199,8 @@ class OTAUpdate extends Extension { (to ? `, from '${fromS}' to '${toS}'` : ``); logger.info(msg); this.publishEntityState(resolvedEntity.device.ieeeAddr, {update_available: false}); - responseData.from = from_; - responseData.to = to; + responseData.from = from_ ? utils.toSnakeCase(from_) : null; + responseData.to = to ? utils.toSnakeCase(to) : null; /* istanbul ignore else */ if (settings.get().advanced.legacy_api) { @@ -225,7 +224,7 @@ class OTAUpdate extends Extension { const triggeredViaLegacyApi = topic.match(legacyTopicRegex); if (!triggeredViaLegacyApi) { const response = utils.getResponse(message, responseData, error); - await this.mqtt.publish(`bridge/response/device/otaUpdate/${type}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, JSON.stringify(response)); } if (error) { diff --git a/lib/util/utils.js b/lib/util/utils.js index 5f1f239f..e3c365a3 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -172,11 +172,24 @@ function* getExternalConvertersDefinitions(settings) { } } +function toSnakeCase(value) { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + value[toSnakeCase(key)] = value[key]; + delete value[key]; + } + return value; + } else { + return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_ID'); + } +} + module.exports = { millisecondsToSeconds: (milliseconds) => milliseconds / 1000, secondsToMilliseconds: (seconds) => seconds * 1000, getZigbee2mqttVersion, objectHasProperties, + toSnakeCase, getObjectsProperty, getEndpointNames: () => endpointNames, isXiaomiDevice: (device) => { diff --git a/test/bridge.test.js b/test/bridge.test.js index 1626b685..65c4cc1c 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -36,7 +36,7 @@ describe('Bridge', () => { const version = await require('../lib/util/utils').getZigbee2mqttVersion(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', - JSON.stringify({"version":version.version,"commit":version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1,"revision":20190425}},"logLevel":"info","permitJoin":false}), + JSON.stringify({"version":version.version,"commit":version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1,"revision":20190425}},"log_level":"info","permit_join":false}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -45,7 +45,7 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', - JSON.stringify([{"ieeeAddress":"0x000b57fffec6a5b2","type":"Router","networkAddress":40369,"supported":true,"friendlyName":"bulb","definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"},"powerSource":"Mains (single phase)","dateCode":null,"interviewing":false,"interviewCompleted":true},{"ieeeAddress":"0x0017880104e45518","type":"EndDevice","networkAddress":6536,"supported":false,"friendlyName":"0x0017880104e45518","definition":null,"powerSource":"Battery","dateCode":null,"interviewing":false,"interviewCompleted":true}]), + JSON.stringify([{"ieee_address":"0x000b57fffec6a5b2","type":"Router","network_address":40369,"supported":true,"friendly_name":"bulb","definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"},"power_source":"Mains (single phase)","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45518","type":"EndDevice","network_address":6536,"supported":false,"friendly_name":"0x0017880104e45518","definition":null,"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -54,7 +54,7 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/groups', - JSON.stringify([{"ID":1,"friendlyName":"group_1","members":[]},{"ID":15071,"friendlyName":"group_tradfri_remote","members":[]},{"ID":99,"friendlyName":99,"members":[]},{"ID":11,"friendlyName":"group_with_tradfri","members":[]},{"ID":2,"friendlyName":"group_2","members":[]}]), + JSON.stringify([{"ID":1,"friendly_name":"group_1","members":[]},{"ID":15071,"friendly_name":"group_tradfri_remote","members":[]},{"ID":99,"friendly_name":99,"members":[]},{"ID":11,"friendly_name":"group_with_tradfri","members":[]},{"ID":2,"friendly_name":"group_2","members":[]}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -67,7 +67,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceJoined","data":{"friendlyName":"bulb","ieeeAddress":"0x000b57fffec6a5b2"}}), + JSON.stringify({"type":"device_joined","data":{"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -80,7 +80,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceInterview","data":{"friendlyName":"bulb","status":"started","ieeeAddress":"0x000b57fffec6a5b2"}}), + JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"started","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -93,7 +93,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceInterview","data":{"friendlyName":"bulb","status":"failed","ieeeAddress":"0x000b57fffec6a5b2"}}), + JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"failed","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -113,13 +113,13 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceInterview","data":{"friendlyName":"bulb","status":"successful","ieeeAddress":"0x000b57fffec6a5b2","supported":true,"definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"}}}), + JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"successful","ieee_address":"0x000b57fffec6a5b2","supported":true,"definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"}}}), { retain: false, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceInterview","data":{"friendlyName":"0x0017880104e45518","status":"successful","ieeeAddress":"0x0017880104e45518","supported":false,"definition":null}}), + JSON.stringify({"type":"device_interview","data":{"friendly_name":"0x0017880104e45518","status":"successful","ieee_address":"0x0017880104e45518","supported":false,"definition":null}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -138,7 +138,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"deviceLeave","data":{"ieeeAddress":"0x000b57fffec6a5b2"}}), + JSON.stringify({"type":"device_leave","data":{"ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -153,26 +153,26 @@ describe('Bridge', () => { it('Should allow permit join', async () => { zigbeeHerdsman.permitJoin.mockClear(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permitJoin', 'true'); + 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(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permitJoin', + 'zigbee2mqtt/bridge/response/permit_join', JSON.stringify({"data":{"value":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); zigbeeHerdsman.permitJoin.mockClear(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permitJoin', JSON.stringify({"value": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.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(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permitJoin', + 'zigbee2mqtt/bridge/response/permit_join', JSON.stringify({"data":{"value":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -180,10 +180,10 @@ describe('Bridge', () => { it('Should put transaction in response when request is done with transaction', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permitJoin', JSON.stringify({"value": false, "transaction": 22})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value": false, "transaction": 22})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permitJoin', + 'zigbee2mqtt/bridge/response/permit_join', JSON.stringify({"data":{"value":false},"status":"ok", "transaction": 22}), {retain: false, qos: 0}, expect.any(Function) ); @@ -192,10 +192,10 @@ describe('Bridge', () => { it('Should put error in response when request fails', async () => { zigbeeHerdsman.permitJoin.mockImplementationOnce(() => {throw new Error('Failed to connect to adapter')}); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permitJoin', JSON.stringify({"value": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value": false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permitJoin', + 'zigbee2mqtt/bridge/response/permit_join', JSON.stringify({"data":{},"status":"error","error": "Failed to connect to adapter"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -203,10 +203,10 @@ describe('Bridge', () => { it('Should put error in response when format is incorrect', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permitJoin', JSON.stringify({"value_not_good": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value_not_good": false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permitJoin', + 'zigbee2mqtt/bridge/response/permit_join', JSON.stringify({"data":{},"status":"error","error": "No value given"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -347,7 +347,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({from: 'bulb', to: 'bulb_new_name'})); await flushPromises(); expect(settings.getDevice('bulb')).toBeNull(); - expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendlyName": "bulb_new_name", "friendly_name": "bulb_new_name", "retain": true}); + expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb_new_name", "friendlyName": "bulb_new_name", "retain": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', @@ -361,7 +361,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', JSON.stringify({from: 'group_1', to: 'group_new_name'})); await flushPromises(); expect(settings.getGroup('group_1')).toBeNull(); - expect(settings.getGroup('group_new_name')).toStrictEqual({"ID": 1, "devices": [], "friendlyName": "group_new_name", "friendly_name": "group_new_name", "optimistic": true, "retain": false}); + expect(settings.getGroup('group_new_name')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_new_name", "friendlyName": "group_new_name", "optimistic": true, "retain": false}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/rename', @@ -398,7 +398,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({last: true, to: 'bulb_new_name'})); await flushPromises(); expect(settings.getDevice('bulb')).toBeNull(); - expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendlyName": "bulb_new_name", "friendly_name": "bulb_new_name", "retain": true}); + expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb_new_name", "friendlyName": "bulb_new_name", "retain": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', @@ -420,10 +420,10 @@ describe('Bridge', () => { it('Should allow change device options', async () => { MQTT.publish.mockClear(); - expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendlyName": "bulb", "friendly_name": "bulb", "retain": true}); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "retain": true}); MQTT.events.message('zigbee2mqtt/bridge/request/device/options', JSON.stringify({options: {retain: false, transition: 1}, ID: 'bulb'})); await flushPromises(); - expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendlyName": "bulb", "friendly_name": "bulb", "retain": false, "transition": 1}); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "retain": false, "transition": 1}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', JSON.stringify({"data":{"from":{"retain": true},"to":{"retain": false,"transition":1}, "ID":"bulb"},"status":"ok"}), @@ -459,31 +459,31 @@ describe('Bridge', () => { MQTT.publish.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/request/group/add', 'group_193'); await flushPromises(); - expect(settings.getGroup('group_193')).toStrictEqual({"ID": 3, "devices": [], "friendlyName": "group_193", "friendly_name": "group_193", "optimistic": true}); + expect(settings.getGroup('group_193')).toStrictEqual({"ID": 3, "devices": [], "friendly_name": "group_193", "friendlyName": "group_193", "optimistic": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', - JSON.stringify({"data":{"friendlyName":"group_193","ID": 3},"status":"ok"}), + JSON.stringify({"data":{"friendly_name":"group_193","ID": 3},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should allow to add group with ID', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendlyName: "group_193", ID: 9})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendly_name: "group_193", ID: 9})); await flushPromises(); - expect(settings.getGroup('group_193')).toStrictEqual({"ID": 9, "devices": [], "friendlyName": "group_193", "friendly_name": "group_193", "optimistic": true}); + expect(settings.getGroup('group_193')).toStrictEqual({"ID": 9, "devices": [], "friendly_name": "group_193", "friendlyName": "group_193", "optimistic": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', - JSON.stringify({"data":{"friendlyName":"group_193","ID": 9},"status":"ok"}), + JSON.stringify({"data":{"friendly_name":"group_193","ID": 9},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error when add with invalid payload', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendlyName9: "group_193"})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendly_name9: "group_193"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', @@ -494,11 +494,11 @@ describe('Bridge', () => { it('Should allow to set last_seen', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/config/lastSeen', 'ISO_8601'); + MQTT.events.message('zigbee2mqtt/bridge/request/config/last_seen', 'ISO_8601'); await flushPromises(); expect(settings.get().advanced.last_seen).toBe('ISO_8601'); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/config/lastSeen', + 'zigbee2mqtt/bridge/response/config/last_seen', JSON.stringify({"data":{"value":"ISO_8601"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -506,11 +506,11 @@ describe('Bridge', () => { it('Should fail to set last_seen when invalid type', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/config/lastSeen', 'invalid_one'); + MQTT.events.message('zigbee2mqtt/bridge/request/config/last_seen', 'invalid_one'); await flushPromises(); expect(settings.get().advanced.last_seen).toBe('disable'); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/config/lastSeen', + 'zigbee2mqtt/bridge/response/config/last_seen', JSON.stringify({"data":{},"status":"error","error":"'invalid_one' is not an allowed value, allowed: disable,ISO_8601,epoch,ISO_8601_local"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -542,12 +542,12 @@ describe('Bridge', () => { it('Should allow to set log level', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/config/logLevel', 'debug'); + MQTT.events.message('zigbee2mqtt/bridge/request/config/log_level', 'debug'); await flushPromises(); expect(logger.getLevel()).toBe('debug'); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/config/logLevel', + 'zigbee2mqtt/bridge/response/config/log_level', JSON.stringify({"data":{"value":'debug'},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -555,10 +555,10 @@ describe('Bridge', () => { it('Should fail to set log level when invalid type', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/config/logLevel', 'not_valid'); + MQTT.events.message('zigbee2mqtt/bridge/request/config/log_level', 'not_valid'); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/config/logLevel', + 'zigbee2mqtt/bridge/response/config/log_level', JSON.stringify({"data":{},"status":"error","error":"'not_valid' is not an allowed value, allowed: error,warn,info,debug"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -568,11 +568,11 @@ describe('Bridge', () => { MQTT.publish.mockClear(); zigbeeHerdsman.touchlinkFactoryReset.mockClear(); zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(true); - MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factoryReset', ''); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); await flushPromises(); expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/touchlink/factoryReset', + 'zigbee2mqtt/bridge/response/touchlink/factory_reset', JSON.stringify({"data":{},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -582,11 +582,11 @@ describe('Bridge', () => { MQTT.publish.mockClear(); zigbeeHerdsman.touchlinkFactoryReset.mockClear(); zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(false); - MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factoryReset', ''); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); await flushPromises(); expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/touchlink/factoryReset', + 'zigbee2mqtt/bridge/response/touchlink/factory_reset', JSON.stringify({"data":{},"status":"error","error":"Failed to factory reset device through Touchlink"}), {retain: false, qos: 0}, expect.any(Function) ); diff --git a/test/otaUpdate.test.js b/test/otaUpdate.test.js index 08101b77..06e41936 100644 --- a/test/otaUpdate.test.js +++ b/test/otaUpdate.test.js @@ -29,8 +29,8 @@ describe('OTA update', () => { it('Should subscribe to topics', async () => { expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/check'); expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/update'); - expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/otaUpdate/check'); - expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/otaUpdate/update'); + expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/ota_update/check'); + expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/ota_update/update'); }); it('Should OTA update a device', async () => { @@ -50,7 +50,7 @@ describe('OTA update', () => { onUpdate(10, 3600); }); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/update', 'bulb'); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb'); await flushPromises(); expect(logger.info).toHaveBeenCalledWith(`Updating 'bulb' to latest firmware`); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(0); @@ -63,8 +63,8 @@ describe('OTA update', () => { expect(device.dateCode).toBe('20190102'); expect(device.softwareBuildID).toBe(2); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/update', - JSON.stringify({"data":{"ID": "bulb","from":{"softwareBuildID":1,"dateCode":"20190101"},"to":{"softwareBuildID":2,"dateCode":"20190102"}},"status":"ok"}), + 'zigbee2mqtt/bridge/response/device/ota_update/update', + JSON.stringify({"data":{"ID": "bulb","from":{"software_build_ID":1,"date_code":"20190101"},"to":{"software_build_ID":2,"date_code":"20190102"}},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -80,10 +80,10 @@ describe('OTA update', () => { throw new Error('Update failed'); }); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/update', JSON.stringify({ID: "bulb"})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', JSON.stringify({ID: "bulb"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/update', + 'zigbee2mqtt/bridge/response/device/ota_update/update', JSON.stringify({"data":{"ID": "bulb"},"status":"error","error":"Update of 'bulb' failed (Update failed)"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -95,24 +95,24 @@ describe('OTA update', () => { mockClear(mapped); mapped.ota.isUpdateAvailable.mockReturnValueOnce(false); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb"); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1); expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "bulb","updateAvailable":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); MQTT.publish.mockClear(); mapped.ota.isUpdateAvailable.mockReturnValueOnce(true); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb"); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(2); expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "bulb","updateAvailable":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -124,32 +124,32 @@ describe('OTA update', () => { mockClear(mapped); mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('RF singals disturbed because of dogs barking')}); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb"); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1); expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should fail when device does not exist', async () => { - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "not_existing_deviceooo"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "not_existing_deviceooo"); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "not_existing_deviceooo"},"status":"error","error": `Device 'not_existing_deviceooo' does not exist`}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should not check for OTA when device does not support it', async () => { - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "ZNLDP12LM"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "ZNLDP12LM"); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}), {retain: false, qos: 0}, expect.any(Function) ); @@ -164,15 +164,15 @@ describe('OTA update', () => { mapped.ota.isUpdateAvailable.mockImplementationOnce(() => { return new Promise((resolve, reject) => {setTimeout(() => resolve(), 99999)}) }); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb"); await flushPromises(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/check', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb"); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1); jest.runAllTimers(); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/check', + 'zigbee2mqtt/bridge/response/device/ota_update/check', JSON.stringify({"data":{"ID": "bulb"},"status":"error","error": `Update or check for update already in progress for 'bulb'`}), {retain: false, qos: 0}, expect.any(Function) ); @@ -190,11 +190,11 @@ describe('OTA update', () => { const mapped = zigbeeHerdsmanConverters.findByDevice(device) mockClear(mapped); - MQTT.events.message('zigbee2mqtt/bridge/request/device/otaUpdate/update', "bulb"); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', "bulb"); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/device/otaUpdate/update', - JSON.stringify({"data":{"ID":"bulb","from":{"softwareBuildID":1,"dateCode":"2019010"},"to":null},"status":"ok"}), + 'zigbee2mqtt/bridge/response/device/ota_update/update', + JSON.stringify({"data":{"ID":"bulb","from":{"software_build_ID":1,"date_code":"2019010"},"to":null},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); });