From 32ba231fc749663a4b44f34c9bfa07276da25a67 Mon Sep 17 00:00:00 2001 From: Francois Blackburn Date: Wed, 15 Apr 2020 16:14:01 -0400 Subject: [PATCH 1/4] device: refactor internal data to use object instead list --- nodes/device.js | 89 ++++++++++++++++++--------------------- test/nodes/device_spec.js | 26 ++++++------ 2 files changed, 55 insertions(+), 60 deletions(-) diff --git a/nodes/device.js b/nodes/device.js index 585c2e4..7e0b91f 100644 --- a/nodes/device.js +++ b/nodes/device.js @@ -53,17 +53,20 @@ module.exports = function HubitatDeviceModule(RED) { return node.hubitat.getDevice(node.deviceId).then((device) => { if (!device.attributes) { throw new Error(JSON.stringify(device)); } - device.attributes.forEach((attribute) => { - attribute.value = attribute.currentValue; - // delete attribute.currentValue; // kept for compatibility - if (node.attribute === attribute.name) { - node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(attribute.value)}` }); - node.log(`Initialized. ${node.attribute}: ${attribute.value}`); - } - }); - node.currentAttributes = device.attributes; + // delete attribute.currentValue; // kept for compatibility + node.currentAttributes = device.attributes.reduce((obj, item) => { + obj[item.name] = { ...item, value: item.currentValue }; + return obj; + }, {}); - if (!node.attribute) { + if (node.attribute) { + const attribute = node.currentAttributes[node.attribute]; + if (!attribute) { + throw new Error(`Selected attribute (${node.attribute}) is not handled by device`); + } + node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(attribute.value)}` }); + node.log(`Initialized. ${node.attribute}: ${attribute.value}`); + } else { node.status({}); node.log('Initialized'); } @@ -83,32 +86,26 @@ module.exports = function HubitatDeviceModule(RED) { return; } } - - let found = false; - node.currentAttributes.forEach((attribute) => { - if (event.name === attribute.name) { - attribute.value = castHubitatValue(node, attribute.dataType, event.value); - attribute.deviceId = node.deviceId; - attribute.currentValue = attribute.value; // deprecated since 0.0.18 - if ((node.attribute === event.name) || (!node.attribute)) { - if (node.attribute) { - node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(attribute.value)}` }); - node.log(`${node.attribute}: ${attribute.value}`); - } else { - node.status({}); - node.log('Attributes refreshed'); - } - if (node.sendEvent) { - const msg = { ...attribute }; - node.send({ payload: msg, topic: node.name }); - } - } - found = true; - } - }); - if (!found) { + const attribute = node.currentAttributes[event.name]; + if (!attribute) { node.status({ fill: 'red', shape: node.shape, text: `Unknown event: ${event.name}` }); } + attribute.value = castHubitatValue(node, attribute.dataType, event.value); + attribute.deviceId = node.deviceId; + attribute.currentValue = attribute.value; // deprecated since 0.0.18 + if ((node.attribute === event.name) || (!node.attribute)) { + if (node.attribute) { + node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(attribute.value)}` }); + node.log(`${node.attribute}: ${attribute.value}`); + } else { + node.status({}); + node.log('Attributes refreshed'); + } + if (node.sendEvent) { + const msg = { ...attribute }; + node.send({ payload: msg, topic: node.name }); + } + } }; this.hubitat.hubitatEvent.on(`device.${node.deviceId}`, callback); @@ -129,22 +126,18 @@ module.exports = function HubitatDeviceModule(RED) { node.status({ fill: 'red', shape: node.shape, text: 'Undefined attribute' }); return; } - let foundAttribute; - node.currentAttributes.forEach((attribute) => { - if (attributeSearched === attribute.name) { - msg.payload = { ...attribute }; - msg.payload.deviceId = node.deviceId; - msg.topic = node.name; - send(msg); - foundAttribute = attribute; - } - }); - if (foundAttribute === undefined) { + const attribute = node.currentAttributes[attributeSearched]; + if (!attribute) { node.status({ fill: 'red', shape: node.shape, text: `Invalid attribute: ${attributeSearched}` }); - } else if (!node.attribute) { + done(); + } + msg.payload = { ...attribute, deviceId: node.deviceId }; + msg.topic = node.name; + send(msg); + if (!node.attribute) { node.status({}); - } else if (node.attribute === foundAttribute.name) { - node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(foundAttribute.value)}` }); + } else if (node.attribute === attribute.name) { + node.status({ fill: 'blue', shape: node.shape, text: `${node.attribute}: ${JSON.stringify(attribute.value)}` }); } done(); }); diff --git a/test/nodes/device_spec.js b/test/nodes/device_spec.js index ad745a8..7f11252 100644 --- a/test/nodes/device_spec.js +++ b/test/nodes/device_spec.js @@ -60,7 +60,7 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 'old-value' }]; + n1.currentAttributes = { testAttribute: { name: 'testAttribute', value: 'old-value' } }; n2.on('input', (msg) => { try { msg.should.have.property('payload', { ...hubitatEvent, currentValue: hubitatEvent.value }); @@ -86,7 +86,7 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 'old-value' }]; + n1.currentAttributes = { testAttribute: { name: 'testAttribute', value: 'old-value' } }; let inError = false; n2.on('input', (msg) => { inError = true; @@ -97,7 +97,7 @@ describe('Hubitat Device Node', () => { done(new Error('device receive wrong event')); } else { try { - n1.should.have.property('currentAttributes', [{ name: 'testAttribute', value: 'old-value' }]); + n1.should.have.property('currentAttributes', { testAttribute: { name: 'testAttribute', value: 'old-value' } }); done(); } catch (err) { done(err); @@ -139,12 +139,12 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 'value' }]; + n1.currentAttributes = { testAttribute: { name: 'testAttribute', value: 'value' } }; n2.on('input', (msg) => { try { // eslint-disable-next-line no-param-reassign msg.payload.value = 'update value in another node'; - n1.currentAttributes.should.containEql({ name: 'testAttribute', value: 'value' }); + n1.currentAttributes.should.containEql({ testAttribute: { name: 'testAttribute', value: 'value' } }); done(); } catch (err) { done(err); @@ -167,12 +167,12 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 'old-value' }]; + n1.currentAttributes = { testAttribute: { value: 'old-value' } }; n2.on('input', (msg) => { try { - n1.currentAttributes[0].should.have.property('value', 'new-value'); + n1.currentAttributes.testAttribute.should.have.property('value', 'new-value'); msg.payload.value = 'overwrite-value'; - n1.currentAttributes[0].should.have.property('value', 'new-value'); + n1.currentAttributes.testAttribute.should.have.property('value', 'new-value'); done(); } catch (err) { done(err); @@ -191,7 +191,7 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 1, dataType: 'NUMBER' }]; + n1.currentAttributes = { testAttribute: { value: 1, dataType: 'NUMBER' } }; n2.on('input', (msg) => { try { msg.payload.should.have.property('value', -2.5); @@ -213,7 +213,7 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: false, dataType: 'BOOL' }]; + n1.currentAttributes = { testAttribute: { value: false, dataType: 'BOOL' } }; n2.on('input', (msg) => { try { msg.payload.should.have.property('value', true); @@ -235,7 +235,8 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: { x: -9, y: 1, z: -2 }, dataType: 'VECTOR3' }]; + n1.currentAttributes = { testAttribute: { value: { x: -9, y: 1, z: -2 }, dataType: 'VECTOR3' } }; + n2.on('input', (msg) => { try { msg.payload.should.have.property('value', { x: 2, y: -4, z: 1.5 }); @@ -257,7 +258,8 @@ describe('Hubitat Device Node', () => { helper.load([deviceNode, configNode], flow, () => { const n1 = helper.getNode('n1'); const n2 = helper.getNode('n2'); - n1.currentAttributes = [{ name: 'testAttribute', value: 'undefined', dataType: 'UNDEFINED' }]; + n1.currentAttributes = { testAttribute: { value: 'undefined', dataType: 'UNDEFINED' } }; + n2.on('input', (msg) => { try { msg.payload.should.have.property('value', 'string'); From b657550d7c592a073236fb00993dbe3fdc42040f Mon Sep 17 00:00:00 2001 From: Francois Blackburn Date: Wed, 15 Apr 2020 16:39:53 -0400 Subject: [PATCH 2/4] device: keep the internal attribute clean --- nodes/device.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/device.js b/nodes/device.js index 7e0b91f..7306da6 100644 --- a/nodes/device.js +++ b/nodes/device.js @@ -91,7 +91,6 @@ module.exports = function HubitatDeviceModule(RED) { node.status({ fill: 'red', shape: node.shape, text: `Unknown event: ${event.name}` }); } attribute.value = castHubitatValue(node, attribute.dataType, event.value); - attribute.deviceId = node.deviceId; attribute.currentValue = attribute.value; // deprecated since 0.0.18 if ((node.attribute === event.name) || (!node.attribute)) { if (node.attribute) { @@ -102,7 +101,7 @@ module.exports = function HubitatDeviceModule(RED) { node.log('Attributes refreshed'); } if (node.sendEvent) { - const msg = { ...attribute }; + const msg = { ...attribute, deviceId: node.deviceId }; node.send({ payload: msg, topic: node.name }); } } From 02fb06d9fecc566dfd951dab4ae63f620645f89c Mon Sep 17 00:00:00 2001 From: Francois Blackburn Date: Wed, 15 Apr 2020 16:56:57 -0400 Subject: [PATCH 3/4] device: allow to output all attributes when not specified --- nodes/device.html | 6 +++--- nodes/device.js | 12 ++++++++++-- test/nodes/device_spec.js | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/nodes/device.html b/nodes/device.html index a4acfba..6e70247 100644 --- a/nodes/device.html +++ b/nodes/device.html @@ -72,7 +72,7 @@ function listHubitatDeviceAttributes(server, deviceId, attribute) { const selectMenu = $('#node-input-attribute'); selectMenu.find('option').remove().end(); - const option = $('