Skip to content

Commit

Permalink
Merge branch 'get-all-attributes'
Browse files Browse the repository at this point in the history
  • Loading branch information
fblackburn1 committed Apr 15, 2020
2 parents 1f9e2bb + 8f0643f commit f7c091b
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 65 deletions.
6 changes: 3 additions & 3 deletions nodes/device.html
Expand Up @@ -72,7 +72,7 @@
function listHubitatDeviceAttributes(server, deviceId, attribute) {
const selectMenu = $('#node-input-attribute');
selectMenu.find('option').remove().end();
const option = $('<option>', { value: '', text: 'Undefined' });
const option = $('<option>', { value: '', text: 'All' });
selectMenu.append(option);
const params = {
usetls: server.usetls,
Expand Down Expand Up @@ -110,7 +110,7 @@
function cleanHubitatDeviceAttributes() {
const selectMenu = $('#node-input-attribute');
selectMenu.find('option').remove().end();
const option = $('<option>', { value: '', text: 'Undefined' });
const option = $('<option>', { value: '', text: 'All' });
selectMenu.append(option);
selectMenu.val('').trigger('change');
}
Expand Down Expand Up @@ -168,7 +168,7 @@ <h3>Details</h3>
<p>This node will keep device state. Every time the device state changes at Hubitat, the webhook will send us the current status.</p>
<p><b>Device</b> dropdown is populated when the server is reachable.</p>
<p><b>Attribute</b> dropdown is populated when <b>Device</b> is selected.
The <b>Undefined</b> value allow to output all events but force to specify the <code>msg.attribute</code> property for the input.</p>
The <b>All</b> value allow to output all events and attributes.</p>
<p><b>Send events</b> allow to send or not event when it receive one from Hubitat.</p>
<p><code>currentValue</code> payload is deprecated and replaced by <code>value</code>.</p>
<p>This node is not compatible with Node-RED 0.x</p>
Expand Down
100 changes: 50 additions & 50 deletions nodes/device.js
Expand Up @@ -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, deviceId: node.deviceId };
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');
}
Expand All @@ -83,32 +86,25 @@ 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.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);

Expand All @@ -125,29 +121,33 @@ 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 };
msg.topic = node.name;
send(msg);
node.status({});
done();
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();
return;
}

msg.payload = { ...attribute };
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();
});

node.on('close', () => {
node.debug('Closed');
this.hubitat.hubitatEvent.removeListener(`device.${node.deviceId}`, callback);
Expand Down
47 changes: 35 additions & 12 deletions test/nodes/device_spec.js
Expand Up @@ -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', deviceId: '42' } };
n2.on('input', (msg) => {
try {
msg.should.have.property('payload', { ...hubitatEvent, currentValue: hubitatEvent.value });
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -139,12 +160,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);
Expand All @@ -167,12 +188,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);
Expand All @@ -191,7 +212,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);
Expand All @@ -213,7 +234,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);
Expand All @@ -235,7 +256,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 });
Expand All @@ -257,7 +279,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');
Expand Down

0 comments on commit f7c091b

Please sign in to comment.