diff --git a/lib/util/settings.js b/lib/util/settings.js index 7842329a..852fa93d 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -635,8 +635,10 @@ function changeEntityOptions(IDorName, newOptions) { delete newOptions.devices; if (getDevice(IDorName)) { objectAssignDeep(settings.devices[getDevice(IDorName).ID], newOptions); + utils.removeNullPropertiesFromObject(settings.devices[getDevice(IDorName).ID]); } else if (getGroup(IDorName)) { objectAssignDeep(settings.groups[getGroup(IDorName).ID], newOptions); + utils.removeNullPropertiesFromObject(settings.groups[getGroup(IDorName).ID]); } else { throw new Error(`Device or group '${IDorName}' does not exist`); } diff --git a/lib/util/utils.js b/lib/util/utils.js index b9310233..5b386fff 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -190,6 +190,17 @@ function* getExternalConvertersDefinitions(settings) { } } +function removeNullPropertiesFromObject(obj) { + for (const key of Object.keys(obj)) { + const value = obj[key]; + if (value == null) { + delete obj[key]; + } else if (typeof value === 'object') { + removeNullPropertiesFromObject(value); + } + } +} + function getKey(object, value, fallback, convertTo) { for (const key in object) { if (object[key]===value) { @@ -276,4 +287,5 @@ module.exports = { loadModuleFromText, getKey, sanitizeImageParameter, + removeNullPropertiesFromObject, }; diff --git a/test/bridge.test.js b/test/bridge.test.js index bbeb7f90..5a33e59c 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -593,6 +593,20 @@ describe('Bridge', () => { ); }); + it('Should allow to remove device option', async () => { + MQTT.publish.mockClear(); + settings.set(['devices', '0x000b57fffec6a5b2', 'qos'], 1); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "qos": 1, "retain": true}); + MQTT.events.message('zigbee2mqtt/bridge/request/device/options', stringify({options: {qos: null}, id: 'bulb'})); + await flushPromises(); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "retain": true}); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/options', + stringify({"data":{"from":{"retain": true, "qos": 1},"to":{"retain": true}, "id":"bulb"},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should allow change group options', async () => { MQTT.publish.mockClear(); expect(settings.getGroup('group_1')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_1", "retain": false, "friendlyName": "group_1", "optimistic": true});