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 = $('
Send events allow to send or not event when it receive one from Hubitat.
currentValue
payload is deprecated and replaced by value
.
This node is not compatible with Node-RED 0.x
diff --git a/nodes/device.js b/nodes/device.js
index 7306da6..4c69b60 100644
--- a/nodes/device.js
+++ b/nodes/device.js
@@ -121,15 +121,22 @@ module.exports = function HubitatDeviceModule(RED) {
}
const attributeSearched = msg.attribute || node.attribute;
- if (attributeSearched === undefined) {
- node.status({ fill: 'red', shape: node.shape, text: 'Undefined attribute' });
+ if (!attributeSearched) {
+ msg.payload = { ...node.currentAttributes }; // FIXME add deviceId
+ msg.topic = node.name;
+ send(msg);
+ node.status({});
+ done();
return;
}
+
const attribute = node.currentAttributes[attributeSearched];
if (!attribute) {
node.status({ fill: 'red', shape: node.shape, text: `Invalid attribute: ${attributeSearched}` });
done();
+ return;
}
+
msg.payload = { ...attribute, deviceId: node.deviceId };
msg.topic = node.name;
send(msg);
@@ -140,6 +147,7 @@ module.exports = function HubitatDeviceModule(RED) {
}
done();
});
+
node.on('close', () => {
node.debug('Closed');
this.hubitat.hubitatEvent.removeListener(`device.${node.deviceId}`, callback);
diff --git a/test/nodes/device_spec.js b/test/nodes/device_spec.js
index 7f11252..b1db41d 100644
--- a/test/nodes/device_spec.js
+++ b/test/nodes/device_spec.js
@@ -130,6 +130,27 @@ describe('Hubitat Device Node', () => {
}
});
});
+ it('should send all atributes when not specified', (done) => {
+ const flow = [
+ defaultConfigNode,
+ { ...defaultDeviceNode, attribute: '', wires: [['n2']] },
+ { id: 'n2', type: 'helper' },
+ ];
+ helper.load([deviceNode, configNode], flow, () => {
+ const n1 = helper.getNode('n1');
+ const n2 = helper.getNode('n2');
+ n1.currentAttributes = { testAttribute: { name: 'testAttribute', value: 'old-value' } };
+ n2.on('input', (msg) => {
+ try {
+ msg.should.have.property('payload', { ...n1.currentAttributes });
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({});
+ });
+ });
it('should not link the internal properties to the output message', (done) => {
const flow = [
defaultConfigNode,
From 8f0643f7b2b358afd9fc5ed123a8e1f86837bf06 Mon Sep 17 00:00:00 2001
From: Francois Blackburn
Date: Wed, 15 Apr 2020 17:04:54 -0400
Subject: [PATCH 4/4] device: embedded deviceId in internal object
reason: simplify logic
---
nodes/device.js | 8 ++++----
test/nodes/device_spec.js | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/nodes/device.js b/nodes/device.js
index 4c69b60..a0929d3 100644
--- a/nodes/device.js
+++ b/nodes/device.js
@@ -55,7 +55,7 @@ module.exports = function HubitatDeviceModule(RED) {
// delete attribute.currentValue; // kept for compatibility
node.currentAttributes = device.attributes.reduce((obj, item) => {
- obj[item.name] = { ...item, value: item.currentValue };
+ obj[item.name] = { ...item, value: item.currentValue, deviceId: node.deviceId };
return obj;
}, {});
@@ -101,7 +101,7 @@ module.exports = function HubitatDeviceModule(RED) {
node.log('Attributes refreshed');
}
if (node.sendEvent) {
- const msg = { ...attribute, deviceId: node.deviceId };
+ const msg = { ...attribute };
node.send({ payload: msg, topic: node.name });
}
}
@@ -122,7 +122,7 @@ module.exports = function HubitatDeviceModule(RED) {
const attributeSearched = msg.attribute || node.attribute;
if (!attributeSearched) {
- msg.payload = { ...node.currentAttributes }; // FIXME add deviceId
+ msg.payload = { ...node.currentAttributes };
msg.topic = node.name;
send(msg);
node.status({});
@@ -137,7 +137,7 @@ module.exports = function HubitatDeviceModule(RED) {
return;
}
- msg.payload = { ...attribute, deviceId: node.deviceId };
+ msg.payload = { ...attribute };
msg.topic = node.name;
send(msg);
if (!node.attribute) {
diff --git a/test/nodes/device_spec.js b/test/nodes/device_spec.js
index b1db41d..4cefc23 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 = { testAttribute: { name: 'testAttribute', value: 'old-value' } };
+ n1.currentAttributes = { testAttribute: { name: 'testAttribute', value: 'old-value', deviceId: '42' } };
n2.on('input', (msg) => {
try {
msg.should.have.property('payload', { ...hubitatEvent, currentValue: hubitatEvent.value });