Skip to content

Commit

Permalink
feat(poll-state): Enable dynamic interval length
Browse files Browse the repository at this point in the history
Feature Request #370
  • Loading branch information
zachowj committed Oct 30, 2021
1 parent 6266803 commit f6904c4
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 48 deletions.
8 changes: 8 additions & 0 deletions locales/en-US/poll-state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"poll-state": {
"errors": {
"jsonata_error": "JSONata Error: __message__",
"interval_nan": "Offset is not a number: __offset__"
}
}
}
92 changes: 69 additions & 23 deletions src/controllers/PollState.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
const ta = require('time-ago');

const EventsHaNode = require('./EventsHaNode');
const {
getTimeInMilliseconds,
getEntitiesFromJsonata,
} = require('../helpers/utils');
const { TYPEDINPUT_JSONATA } = require('../const');

const nodeOptions = {
config: {
entity_id: (nodeDef) => (nodeDef.entity_id || '').trim(),
updateinterval: (nodeDef) =>
!isNaN(nodeDef.updateinterval)
? Number(nodeDef.updateinterval)
: 60,
updateinterval: {},
updateIntervalType: {},
updateIntervalUnits: {},
outputinitially: {},
outputonchanged: {},
Expand All @@ -27,25 +30,6 @@ class PollState extends EventsHaNode {
throw new Error('Entity Id is required');
}

if (!this.timer) {
const interval = this.nodeConfig.updateinterval;

switch (this.nodeConfig.updateIntervalUnits) {
case 'minutes':
this.updateInterval = interval * (60 * 1000);
break;
case 'hours':
this.updateInterval = interval * (60 * 60 * 1000);
break;
default:
this.updateInterval = interval * 1000;
}
this.timer = setInterval(
this.onTimer.bind(this),
this.updateInterval
);
}

if (this.nodeConfig.outputonchanged) {
this.addEventClientListener(
`ha_events:state_changed:${this.nodeConfig.entity_id}`,
Expand All @@ -63,6 +47,26 @@ class PollState extends EventsHaNode {
);
}
}

if (this.isHomeAssistantRunning) {
this.onIntervalUpdate();
}
this.addEventClientListener(
'ha_client:ready',
this.onIntervalUpdate.bind(this)
);
if (
this.nodeConfig.updateIntervalType === TYPEDINPUT_JSONATA &&
this.nodeConfig.updateinterval.length > 12
) {
const ids = getEntitiesFromJsonata(this.nodeConfig.updateinterval);
ids.forEach((id) => {
this.addEventClientListener(
`ha_events:state_changed:${id}`,
this.onIntervalUpdate.bind(this)
);
});
}
}

onClose(removed) {
Expand Down Expand Up @@ -156,6 +160,48 @@ class PollState extends EventsHaNode {
const entityLastChanged = entityState.last_changed;
return new Date(entityLastChanged);
}

getInterval() {
let interval = this.nodeConfig.updateinterval || '0';
if (this.nodeConfig.updateIntervalType === TYPEDINPUT_JSONATA) {
try {
interval = this.evaluateJSONata(interval);
} catch (e) {
this.node.error(
this.RED._('poll-state.errors.jsonata_error', {
message: e.message,
})
);
throw new Error('error');
}
}

const intervalMs = getTimeInMilliseconds(
interval,
this.nodeConfig.updateIntervalUnits
);
if (isNaN(intervalMs)) {
this.node.error(
this.RED._('poll-state.errors.offset_nan', { interval })
);
throw new Error(this.RED._('poll-state.status.error'));
}

return Number(intervalMs);
}

onIntervalUpdate() {
const interval = this.getInterval();
// create new timer if interval changed
if (interval !== this.updateinterval) {
clearInterval(this.timer);
this.updateinterval = interval;
this.timer = setInterval(
this.onTimer.bind(this),
this.updateinterval
);
}
}
}

module.exports = PollState;
11 changes: 11 additions & 0 deletions src/migrations/poll-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ const migrations = [
return newSchema;
},
},
{
version: 2,
up: (schema) => {
const newSchema = {
...schema,
version: 2,
updateIntervalType: 'num',
};
return newSchema;
},
},
];

module.exports = migrations;
14 changes: 13 additions & 1 deletion test/migrations/poll-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const VERSION_1 = {
halt_if_compare: 'is',
updateIntervalUnits: 'seconds',
};
const VERSION_2 = {
...VERSION_1,
version: 2,
updateIntervalType: 'num',
};

describe('Migrations - Poll State Node', function () {
describe('Version 0', function () {
Expand All @@ -47,8 +52,15 @@ describe('Migrations - Poll State Node', function () {
expect(migratedSchema).to.eql(VERSION_1);
});
});
describe('Version 2', function () {
it('should update version 1 to version 2 setting new defaults', function () {
const migrate = migrations.find((m) => m.version === 2);
const migratedSchema = migrate.up(VERSION_1);
expect(migratedSchema).to.eql(VERSION_2);
});
});
it('should update an undefined version to current version', function () {
const migratedSchema = migrate(VERSION_UNDEFINED);
expect(migratedSchema).to.eql(VERSION_1);
expect(migratedSchema).to.eql(VERSION_2);
});
});
33 changes: 11 additions & 22 deletions ui/html/poll-state.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,33 @@
<input type="hidden" id="node-input-outputs" />

<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name">Name</label>
<input type="text" id="node-input-name" placeholder="Name" />
</div>

<div class="form-row">
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
<label for="node-input-server">Server</label>
<input type="text" id="node-input-server" />
</div>

<div class="form-row">
<label for="node-input-entity_id">
<i class="fa fa-cube"></i> Entity ID
</label>
<label for="node-input-entity_id">Entity ID </label>
<input type="text" id="node-input-entity_id" />
</div>

<div class="form-row">
<label for="node-input-updateinterval">
<i class="fa fa-clock-o"></i> Update Interval
</label>
<input
type="text"
id="node-input-updateinterval"
style="text-align: end; width: 50px !important"
/>
<select id="node-input-updateIntervalUnits" style="width: auto !important">
<label for="node-input-updateinterval">Update Interval </label>
<input type="text" id="node-input-updateinterval" style="width: 43%" />
<input type="hidden" id="node-input-updateIntervalType" />
<select id="node-input-updateIntervalUnits" style="width: 27%">
<option value="seconds">seconds</option>
<option value="minutes">minutes</option>
<option value="hours">hours</option>
</select>
</div>

<div class="form-row">
<label for="node-input-halt_if_compare">
<i class="fa fa-check-square"></i> If State
</label>
<label for="node-input-halt_if_compare">If State </label>
<select type="text" id="node-input-halt_if_compare" style="width: 20%">
<option value="is">is</option>
<option value="is_not">is not</option>
Expand All @@ -54,9 +45,7 @@
</div>

<div class="form-row">
<label for="node-input-state_type">
<i class="fa fa-tint"></i> State Type
</label>
<label for="node-input-state_type">State Type </label>
<select type="text" id="node-input-state_type" style="width: auto">
<option value="str">String</option>
<option value="num">Number</option>
Expand All @@ -66,10 +55,10 @@

<div class="form-row checkbox-option">
<input type="checkbox" id="node-input-outputonchanged" />
<label for="node-input-outputonchanged"> Output on Changed </label>
<label for="node-input-outputonchanged">Output on Changed </label>
</div>

<div class="form-row checkbox-option">
<input type="checkbox" id="node-input-outputinitially" />
<label for="node-input-outputinitially"> Output on Connect </label>
<label for="node-input-outputinitially">Output on Connect </label>
</div>
8 changes: 6 additions & 2 deletions ui/js/poll-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ RED.nodes.registerType('poll-state', {
{ property: 'icon', value: '' },
],
},
updateinterval: { value: '60', validate: (v) => !isNaN(v) },
updateinterval: { value: '60' },
updateIntervalType: { value: 'num' },
updateIntervalUnits: { value: 'seconds' },
outputinitially: { value: false },
outputonchanged: { value: false },
Expand All @@ -39,7 +40,10 @@ RED.nodes.registerType('poll-state', {

haServer.init(node, '#node-input-server');
$('#node-input-entity_id').haAutocomplete();
$('#node-input-updateinterval').spinner({ min: 1 });
$('#node-input-updateinterval').typedInput({
types: ['num', 'jsonata'],
typeField: '#node-input-updateIntervalType',
});

ifState.init('#node-input-halt_if', '#node-input-halt_if_compare');
exposeNode.init(node);
Expand Down

0 comments on commit f6904c4

Please sign in to comment.