Skip to content

Commit

Permalink
Removed timer logic, overcomplicated node. Started adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AYapejian committed Feb 10, 2018
1 parent 38ad159 commit 072be2b
Show file tree
Hide file tree
Showing 10 changed files with 4,260 additions and 3,903 deletions.
7 changes: 1 addition & 6 deletions .eslintrc.js
@@ -1,18 +1,13 @@
module.exports = {
root: true,
extends: 'standard',
plugins: ['html'],
parserOptions: { sourceType: 'module' },
env: { browser: true, node: true, mocha: true },
globals: {
'__THEME': true
},
env: { browser: true },
rules: {
'arrow-parens': 0,
'space-before-function-paren': 0,
'no-warning-comments': [0, { 'terms': [ 'todo', 'fixme' ], 'location': 'start' }],
'generator-star-spacing': 0,
'camelcase': 0, // To support home assistant props
semi: ['error', 'always', { 'omitLastInOneLineBlock': true }],
// Because ocd
'standard/object-curly-even-spacing': 0,
Expand Down
1 change: 1 addition & 0 deletions __tests__/lib/timed-task.spec.js
Expand Up @@ -57,5 +57,6 @@ test('TimedTask: should deserialize', function(t) {

t.equals(timedTask.id, opts.id, 'deserialized id is equal');
t.equals(typeof timedTask.task, 'function', 'task function is deserialized correctly');

t.end();
});
73 changes: 73 additions & 0 deletions __tests__/mock-node.js
@@ -0,0 +1,73 @@
'use strict';
var assert = require('assert');

class Context {
constructor(type) {
this._values = {};
this._type = type;
}
get(key) {
return this._values[key];
}
set(key, value) {
console.log(this._type + ' context: set [' + key + '] => [' + value + ']');
this._values[key] = value;
}
}

class MockNode {
constructor() {
this._events = {};
this._state = {};
this._sent = [];
this._context = {};
this._status = {};
}

log() { console.log(...arguments); }
warn() { console.log(...arguments); }
error() { console.log(...arguments); }
on(event, eventFn) {
this._events[event] = eventFn;
}
emit(event, data) {
this._events[event].call(this, data);
}
status(status) {
if (status) this._status = status;
return status;
}
send(msg) {
this._sent.push(msg);
}
sent(index) {
return this._sent[index];
}
context() {
return this._context;
}
}

module.exports = function(nodeRedModule, config) {
const RED = {
nodes: {
getNode: () => {},
registerType: function(nodeName, nodeConfigFunc) {
this.nodeConfigFunc = nodeConfigFunc;
},
createNode: function() {
// TODO write me
}
},
settings: {
get: () => {}
},
comms: {
publish: () => true
}
};
const mockNode = new MockNode();
// Register the node (calls registerType)
nodeRedModule(RED);
return new RED.nodes.nodeConfigFunc.call(mockNode, config); // eslint-disable-line
};
49 changes: 49 additions & 0 deletions __tests__/nodes/server-events-all.spec.js
@@ -0,0 +1,49 @@
const test = require('tape');
const helper = require('node-red/test/nodes/helper');
const ConfigServerNode = require('../../nodes/config-server/config-server');
const ServerEventsNode = require('../../nodes/server-events-all/server-events-all');

test('before: start-server', function(t) {
helper.startServer(() => t.end());
});

test('Simple Node: should load', function(t) {
let flow = [
{ id: 'n1', type: 'server-events', server: 'n2', wires: [] },
{ id: 'n2', type: 'server', name: 'ha-server', url: 'http://localhost:1234', pass: '123' }
];

helper.load([ServerEventsNode, ConfigServerNode], flow, function() {
const n1 = helper.getNode('n1');
t.equals(n1.type, 'server-events', 'simple node should instantiate with type "simple-node"');
helper.unload();
t.end();
});
});

// test('Simple Node: should send lowercased payload', function(t) {
// let flow = [
// { id: 'n1', type: 'simple-node', server: 'n2', wires: [ ['n3'] ] },
// { id: 'n2', type: 'config-node', host: 'localhost', port: '1234' },
// { id: 'n3', type: 'helper' }
// ];

// helper.load([ConfigNode, SimpleNode], flow, function() {
// const n1 = helper.getNode('n1');
// const n3 = helper.getNode('n3');

// n3.on('input', function(msg) {
// t.equals(msg.topic, expectedMsg.topic, 'topics should match');
// t.equals(msg.payload, expectedMsg.payload.toLowerCase(), 'payload should be lowercased match');
// t.end();
// });

// const expectedMsg = { topic: 'test', payload: 'ABC' };
// n1.receive(expectedMsg);
// });
// });

test('after: stop-server', function(t) {
helper.stopServer();
t.end();
});
3 changes: 0 additions & 3 deletions lib/base-node.js
Expand Up @@ -201,9 +201,6 @@ const _internals = {

const _eventHandlers = {
preOnInput(message) {
this.debugToClient('Incoming Message', message);
this.debug('Incoming message: ', message);

try {
const parsedMessage = _internals.parseInputMessage.call(this, this.options.input, message);

Expand Down
127 changes: 48 additions & 79 deletions nodes/trigger-state/trigger-state.html
Expand Up @@ -84,50 +84,26 @@ <h3>Add Constraints</h3>
<h3>Add Outputs</h3>
<div>
<div class="form-row">
<label for="output-timer-type" style="width: 60px"> Send</label>
<input type="hidden" id="node-input-outputs"/> <!-- Needs to exist for node-red output remapping -->

<!-- Type -->
<select type="text" id="output-timer-type" style="width: 112px;">
<option value="immediate"> Immediately </option>
<option value="delayed"> Delayed </option>
</select>

<input type="text" id="output-timer-value" style="width: 22%" disabled />

<select type="text" id="output-timer-unit" style="width: 112px;" disabled>
<option value="milliseconds"> Milliseconds </option>
<option value="seconds"> Seconds </option>
<option value="minutes"> Minutes </option>
<option value="hours"> Hours </option>
</select>
</div>

<div class="form-row">
<label for="output-message-type" style="width: 60px"> Message</label>

<!-- Type -->
<select type="text" id="output-message-type" style="width: 112px;">
<select type="text" id="output-message-type" style="width: 140px;">
<option value="default"> Default Msg </option>
<option value="custom"> Custom Msg </option>
</select>

<input type="text" id="output-message-value" style="width: 52%" disabled/>
<input type="text" id="output-message-value" style="width: 62%" disabled/>
</div>


<!-- Output Comparator Selection -->
<div class="form-row">
<label for="output-comparator-type" style="width: 60px"> If</label>

<select type="text" id="output-comparator-property-type" style="width: 112px">
<option value="always"> Always </option>
<option value="current_state"> State </option>
<option value="previous_state"> Prev State </option>
<option value="property"> Property </option>
<select type="text" id="output-comparator-property-type" style="width: 140px">
<option value="always"> Send Always </option>
<option value="current_state"> If State </option>
<option value="previous_state"> If Prev State </option>
<option value="property"> If Property </option>
</select>

<input type="text" id="output-comparator-property-value" style="width: 52%" disabled />
<input type="text" id="output-comparator-property-value" style="width: 62%" disabled />
</div>

<div class="form-row">
Expand Down Expand Up @@ -188,7 +164,7 @@ <h3>Add Outputs</h3>

// Get custom output by length minus default outputs
const co = this.customoutputs[index - NUM_DEFAULT_OUTPUTS];
let label = `${co.timerType}: `;
let label;
if (co.comparatorPropertyType === 'always') {
label += 'always sent'
} else {
Expand Down Expand Up @@ -226,10 +202,6 @@ <h3>Add Outputs</h3>
list: $('#output-list'),
addBtn: $('#output-add-btn'),

timerType: $('#output-timer-type'),
timerValue: $('#output-timer-value'),
timerUnit: $('#output-timer-unit'),

messageType: $('#output-message-type'),
messageValue: $('#output-message-value'),

Expand Down Expand Up @@ -347,9 +319,6 @@ <h3>Add Outputs</h3>
onAddButtonClicked: function() {
const output = {
outputId: utils.getRandomId(),
timerType: $customoutputs.timerType.val(),
timerValue: $customoutputs.timerValue.val(),
timerUnit: $customoutputs.timerUnit.val(),

messageType: $customoutputs.messageType.val(),
messageValue: $customoutputs.messageValue.val(),
Expand Down Expand Up @@ -381,10 +350,6 @@ <h3>Add Outputs</h3>
onEditableListAdd: function(row, index, d) {
const $row = $(row);

const typeText = (d.timerType === 'immediate')
? 'Immediate'
: 'Delayed';

const messageText = (d.messageType === 'default')
? 'default message'
: d.messageValue;
Expand All @@ -393,11 +358,7 @@ <h3>Add Outputs</h3>
? 'always'
: `${d.comparatorPropertyValue} ${d.comparatorType.replace('_', '')} ${d.comparatorValue}`

const sendAfterText = (d.timerType === 'immediate')
? 'immediately'
: `${d.timerValue + d.timerUnit.substring(0,3)}`;

const html = `Send <strong>${messageText}</strong>, if <strong>${sendWhenText}</strong>, after <strong>${sendAfterText}</strong>`;
const html = `Send <strong>${messageText}</strong>, if <strong>${sendWhenText}</strong>`;

$row.html(html);
},
Expand Down Expand Up @@ -434,16 +395,6 @@ <h3>Add Outputs</h3>

$outputs.val(JSON.stringify(NODE.outputs));
},
onTimerTypeChange: function(e) {
const val = e.target.value;
if (val === 'immediate') {
$customoutputs.timerValue.attr('disabled', 'disabled')
$customoutputs.timerUnit.attr('disabled', 'disabled')
} else {
$customoutputs.timerValue.removeAttr('disabled')
$customoutputs.timerUnit.removeAttr('disabled')
}
},
onMessageTypeChange: function(e) {
const val = e.target.value;
(val === 'default')
Expand Down Expand Up @@ -480,7 +431,6 @@ <h3>Add Outputs</h3>
});

// Constraint select menu change handlers
$customoutputs.timerType.on('change', outputsHandler.onTimerTypeChange);
$customoutputs.messageType.on('change', outputsHandler.onMessageTypeChange);
$customoutputs.comparatorPropertyType.on('change', outputsHandler.comparatorPropertyTypeChange);

Expand Down Expand Up @@ -534,23 +484,42 @@ <h3>Add Outputs</h3>


<script type="text/x-red" data-help-name="trigger-state">
<p class="ha-description">Advanced version of 'server:state-changed' node</p>

<h4 class="ha-title">Configuration:</h4>
<table class="ha-table" style="width: 100%;" border="1" cellpadding="10">
<tbody>
<tr> <td>Entity ID Filter</td> <td>Exact match for entity_id field</td> </tr>
</tbody>
</table>

<br/>

<h4 class="ha-title">Output Object:</h4>
<table class="ha-table" style="width: 100%;" border="1" cellpadding="10">
<tbody>
<tr> <td>topic</td> <td>entity_id (e.g.: sensor.bedroom_temp)</td> </tr>
<tr> <td>payload</td> <td>event.current_state.state (e.g.: 'on', 'off', '88.5', 'home', 'not_home')</td> </tr>
<tr> <td>data</td> <td>original event object from homeassistant</td> </tr>
</tbody>
</table>
<p>Advanced version of 'server:state-changed' node</p>

<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">
[payload|msg] <span class="property-type">string|object</span>
</dt>
<dd>If incoming payload or message is a string and equal to 'enable' or 'disable' then set the node accordingly.</dd>
</dl>

<h3>Outputs</h3>
<dl class="message-properties">
<dt>
topic <span class="property-type">string</span>
</dt>
<dd>the entity_id that triggered the node</dd>
<dt>
payload <span class="property-type">string</span>
</dt>
<dd>the state as sent by home assistant</dd>
<dt>
data <span class="property-type">object</span>
</dt>
<dd>the original home assistant event containing <code>entity_id</code> <code>new_state</code> and <code>old_state</code> properties </dd>
</dl>

<h3>Details</h3>
<p>Coming soon...</p>
<p> TODO Document: Enable / Disable and how it saves state across restarts</p>
<p> TODO Document: Constraints and how they work</p>
<p> TODO Document: Custom Outputs and how they work</p>
<p> TODO Document: Debug flag on node</p>
<p> NOTE: To test automation without having to manually change state in home assistant send an input <code>payload</code> as an object which contains <code>entity_id</code>, <code>new_state</code>, and <code>old_state</code> properties. This will trigger the node as if the event came from home assistant.</p>

<h3>References</h3>
<ul>
<li><a href="https://home-assistant.io/docs/configuration/state_object">HA State Object</a></li>
</ul>
</script>

0 comments on commit 072be2b

Please sign in to comment.