@mschaeffler/node-red-logic 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/logic_and.js +1 -1
- package/logic_blinker.js +1 -1
- package/logic_bool.js +1 -1
- package/logic_not.js +1 -1
- package/logic_or.js +1 -1
- package/logic_timerelay.js +1 -1
- package/math_3point.js +1 -1
- package/math_falling.js +1 -1
- package/math_filter.html +194 -0
- package/math_filter.js +223 -0
- package/math_hysteresis.js +1 -1
- package/math_mean.html +3 -3
- package/math_mean.js +6 -6
- package/math_reduce.js +1 -1
- package/math_rising.js +1 -1
- package/math_statistics.html +0 -1
- package/math_statistics.js +2 -2
- package/package.json +4 -3
- package/logic_blinker.html.save +0 -159
package/logic_and.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports = function(RED) {
|
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
9
|
this.topic = config.topic ?? "";
|
|
10
|
-
this.property = config.property
|
|
10
|
+
this.property = config.property ?? "payload";
|
|
11
11
|
this.minData = Number( config.minData ?? 1 );
|
|
12
12
|
this.filter = Boolean( config.filter );
|
|
13
13
|
this.last = null;
|
package/logic_blinker.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = function(RED) {
|
|
|
5
5
|
RED.nodes.createNode(this,config);
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
|
-
this.property = config.property
|
|
8
|
+
this.property = config.property ?? "payload";
|
|
9
9
|
this.onTime = Number( config.onTime ?? 1 ) * tools.timeUnits( config.onTimeUnit );
|
|
10
10
|
this.offTime = Number( config.offTime ?? 1 ) * tools.timeUnits( config.offTimeUnit );
|
|
11
11
|
this.firstType = config.outputFirstType ?? "bool";
|
package/logic_bool.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
this.topic = config.topic ?? "";
|
|
9
|
-
this.property = config.property
|
|
9
|
+
this.property = config.property ?? "payload";
|
|
10
10
|
this.showState = Boolean( config.showState );
|
|
11
11
|
this.filter = Boolean( config.filter );
|
|
12
12
|
this.last = null;
|
package/logic_not.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = function(RED) {
|
|
|
5
5
|
RED.nodes.createNode(this,config);
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
|
-
this.property = config.property
|
|
8
|
+
this.property = config.property ?? "payload";
|
|
9
9
|
this.showState = Boolean( config.showState );
|
|
10
10
|
this.filter = Boolean( config.filter );
|
|
11
11
|
this.last = null;
|
package/logic_or.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports = function(RED) {
|
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
9
|
this.topic = config.topic ?? "";
|
|
10
|
-
this.property = config.property
|
|
10
|
+
this.property = config.property ?? "payload";
|
|
11
11
|
this.minData = Number( config.minData ?? 1 );
|
|
12
12
|
this.filter = Boolean( config.filter );
|
|
13
13
|
this.last = null;
|
package/logic_timerelay.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = function(RED) {
|
|
|
5
5
|
RED.nodes.createNode(this,config);
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
|
-
this.property = config.property
|
|
8
|
+
this.property = config.property ?? "payload";
|
|
9
9
|
this.delay = Number( config.delay ?? 0 ) * tools.timeUnits( config.delayUnit );
|
|
10
10
|
this.postrun = Number( config.postrun ?? 0 ) * tools.timeUnits( config.postrunUnit );
|
|
11
11
|
this.minOn = Number( config.minOn ?? 0 ) * tools.timeUnits( config.minOnUnit );
|
package/math_3point.js
CHANGED
|
@@ -8,7 +8,7 @@ module.exports = function(RED) {
|
|
|
8
8
|
//this.config = config;
|
|
9
9
|
var node = this;
|
|
10
10
|
var context = this.context();
|
|
11
|
-
this.topic = config.topic
|
|
11
|
+
this.topic = config.topic ?? "";
|
|
12
12
|
this.property = config.property ?? "payload";
|
|
13
13
|
this.propertyType = config.propertyType ?? "msg";
|
|
14
14
|
this.thresholdUpRise = Number( config.thresholdUpRise );
|
package/math_falling.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
|
-
this.topic = config.topic
|
|
9
|
+
this.topic = config.topic ?? "";
|
|
10
10
|
this.property = config.property ?? "payload";
|
|
11
11
|
this.propertyType = config.propertyType ?? "msg";
|
|
12
12
|
this.threshold = Number( config.threshold );
|
package/math_filter.html
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('filter',{
|
|
3
|
+
category: 'logic',
|
|
4
|
+
color: "#C0DEED",
|
|
5
|
+
defaults: {
|
|
6
|
+
name:{value:""},
|
|
7
|
+
topic:{value:""},
|
|
8
|
+
property:{value:"payload", required:true},
|
|
9
|
+
propertyType:{value:"msg"},
|
|
10
|
+
control:{value:0, required:true},
|
|
11
|
+
controlType:{value:"num"},
|
|
12
|
+
processNoise:{value:1, validate:RED.validators.number(), required:true },
|
|
13
|
+
measurementNoise:{value:1, validate:RED.validators.number(), required:true },
|
|
14
|
+
stateVector:{value:1, validate:RED.validators.number(), required:true },
|
|
15
|
+
controlVector:{value:0, validate:RED.validators.number(), required:true },
|
|
16
|
+
measurementVector:{value:1, validate:RED.validators.number(), required:true },
|
|
17
|
+
adaptionCount:{value:0, validate:RED.validators.number(), required:true },
|
|
18
|
+
contextStore:{value:"none",required:true},
|
|
19
|
+
filter:{value:0, validate:RED.validators.number()},
|
|
20
|
+
filterUnit:{value:"secs"},
|
|
21
|
+
filterVal:{value:0, validate:RED.validators.number()},
|
|
22
|
+
filterMul:{value:10, validate:RED.validators.number()},
|
|
23
|
+
zeroIsZero:{value:false},
|
|
24
|
+
decimals:{validate:RED.validators.number()},
|
|
25
|
+
showState:{value:false}
|
|
26
|
+
},
|
|
27
|
+
inputs:1,
|
|
28
|
+
outputs:1,
|
|
29
|
+
icon:"font-awesome/fa-area-chart",
|
|
30
|
+
label: function() {
|
|
31
|
+
return this.name||this.topic||"filter";
|
|
32
|
+
},
|
|
33
|
+
labelStyle: function() {
|
|
34
|
+
return this.name?"node_label_italic":"";
|
|
35
|
+
},
|
|
36
|
+
inputLabels: 'input',
|
|
37
|
+
outputLabels: 'output',
|
|
38
|
+
oneditprepare: function() {
|
|
39
|
+
const node = this;
|
|
40
|
+
$("#node-input-property").typedInput({
|
|
41
|
+
type:"msg",
|
|
42
|
+
types:["msg","jsonata"],
|
|
43
|
+
typeField: "#node-input-propertyType"
|
|
44
|
+
});
|
|
45
|
+
$("#node-input-control").typedInput({
|
|
46
|
+
type:"num",
|
|
47
|
+
types:["msg","num"],
|
|
48
|
+
typeField: "#node-input-controlType"
|
|
49
|
+
});
|
|
50
|
+
// populate store names
|
|
51
|
+
$("#node-input-contextStore").empty();
|
|
52
|
+
const defaultStore = RED.settings.hasOwnProperty("context") ? RED.settings.context.default : '';
|
|
53
|
+
RED.settings.context.stores.forEach(function(item){
|
|
54
|
+
if( item ) {
|
|
55
|
+
const opt = $(`<option value="${item}">${item}${item===defaultStore?" (default)":""}</option>`);
|
|
56
|
+
$("#node-input-contextStore").append(opt);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const currentStore = node.contextStore || '';
|
|
60
|
+
// select the current option
|
|
61
|
+
$("#node-input-contextStore").val(currentStore);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<script type="text/html" data-template-name="filter">
|
|
67
|
+
<div class="form-row">
|
|
68
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> name</label>
|
|
69
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
70
|
+
</div>
|
|
71
|
+
<div class="form-row">
|
|
72
|
+
<label for="node-input-topic"><i class="fa fa-envelope-o"></i> topic</label>
|
|
73
|
+
<input type="text" id="node-input-topic" placeholder="Topic">
|
|
74
|
+
</div>
|
|
75
|
+
<div class="form-row">
|
|
76
|
+
<label for="node-input-property"> measured value</label>
|
|
77
|
+
<input type="text" id="node-input-property">
|
|
78
|
+
<input type="hidden" id="node-input-propertyType">
|
|
79
|
+
</div>
|
|
80
|
+
<div class="form-row">
|
|
81
|
+
<label for="node-input-control"> control value</label>
|
|
82
|
+
<input type="text" id="node-input-control">
|
|
83
|
+
<input type="hidden" id="node-input-controlType">
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="form-row">
|
|
87
|
+
<label for="node-input-processNoise"> process noise</label>
|
|
88
|
+
<input type="text" id="node-input-processNoise">
|
|
89
|
+
</div>
|
|
90
|
+
<div class="form-row">
|
|
91
|
+
<label for="node-input-measurementNoise"> measure noise</label>
|
|
92
|
+
<input type="text" id="node-input-measurementNoise">
|
|
93
|
+
</div>
|
|
94
|
+
<div class="form-row">
|
|
95
|
+
<label for="node-input-stateVector"> process factor</label>
|
|
96
|
+
<input type="text" id="node-input-stateVector">
|
|
97
|
+
</div>
|
|
98
|
+
<div class="form-row">
|
|
99
|
+
<label for="node-input-controlVector"> control factor</label>
|
|
100
|
+
<input type="text" id="node-input-controlVector">
|
|
101
|
+
</div>
|
|
102
|
+
<div class="form-row">
|
|
103
|
+
<label for="node-input-measurementVector"> measur. factor</label>
|
|
104
|
+
<input type="text" id="node-input-measurementVector">
|
|
105
|
+
</div>
|
|
106
|
+
<div class="form-row">
|
|
107
|
+
<label for="node-input-adaptionCount"> adaption counts</label>
|
|
108
|
+
<input type="number" id="node-input-adaptionCount">
|
|
109
|
+
</div>
|
|
110
|
+
<div class="form-row">
|
|
111
|
+
<label for="node-input-contextStore"><i class="fa fa-database"></i> context store</label>
|
|
112
|
+
<select type="text" id="node-input-contextStore"></select>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="form-row">
|
|
116
|
+
<label for="node-input-filter"><i class="fa fa-filter"></i> filter time</label>
|
|
117
|
+
<input type="number" id="node-input-filter" style="width:70px;" placeholder="Time">
|
|
118
|
+
<select id="node-input-filterUnit" style="width:140px;">
|
|
119
|
+
<option value="msecs">MilliSeconds</option>
|
|
120
|
+
<option value="secs">Seconds</option>
|
|
121
|
+
<option value="mins">Minutes</option>
|
|
122
|
+
<option value="hours">Hours</option>
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="form-row">
|
|
126
|
+
<label for="node-input-filterVal"><i class="fa fa-filter"></i> filter value</label>
|
|
127
|
+
<input type="text" id="node-input-filterVal">
|
|
128
|
+
</div>
|
|
129
|
+
<div class="form-row">
|
|
130
|
+
<label for="node-input-filterMul"><i class="fa fa-filter"></i> filter repetition</label>
|
|
131
|
+
<input type="text" id="node-input-filterMul">
|
|
132
|
+
</div>
|
|
133
|
+
<div class="form-row">
|
|
134
|
+
<label for="node-input-decimals"><i class="fa fa-sort-numeric-asc"></i> decimals</label>
|
|
135
|
+
<input type="number" id="node-input-decimals">
|
|
136
|
+
</div>
|
|
137
|
+
<div class="form-row">
|
|
138
|
+
<label for="node-input-zeroIsZero"><i class="fa fa-exclamation-circle"></i> null is null</label>
|
|
139
|
+
<input type="checkbox" id="node-input-zeroIsZero" style="display:inline-block; width:20px; vertical-align:baseline;">
|
|
140
|
+
</div>
|
|
141
|
+
<div class="form-row">
|
|
142
|
+
<label for="node-input-showStatus"><i class="fa fa-star-o"></i> status</label>
|
|
143
|
+
<input type="checkbox" id="node-input-showState" style="display:inline-block; width:20px; vertical-align:baseline;">
|
|
144
|
+
</div>
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<script type="text/html" data-help-name="filter">
|
|
148
|
+
|
|
149
|
+
<p>Filter the values of one or more data streams wuth an adaptive Kalman filter.</p>
|
|
150
|
+
<p>If the msg property <code>invalid</code> is present in the message, all nodes ignore the message.</p>
|
|
151
|
+
|
|
152
|
+
<h3>Input</h3>
|
|
153
|
+
|
|
154
|
+
<p>The different inputs are differentiated by topics.</p>
|
|
155
|
+
<p>The message property to be used as payload can be defined with the <code>Property</code> property.</p>
|
|
156
|
+
|
|
157
|
+
<dl class="message-properties">
|
|
158
|
+
<dt>topic <span class="property-type">string</span></dt>
|
|
159
|
+
<dd> name of the input channel.</dd>
|
|
160
|
+
<dt>payload <span class="property-type">number</span></dt>
|
|
161
|
+
<dd> input value for <code>topic</code>.</dd>
|
|
162
|
+
<dt>control <span class="property-type">number</span></dt>
|
|
163
|
+
<dd> control value, THe message property can be selected with the <code>Control</code> property.</dd>
|
|
164
|
+
</dl>
|
|
165
|
+
|
|
166
|
+
<h3>Output</h3>
|
|
167
|
+
|
|
168
|
+
<dl class="message-properties">
|
|
169
|
+
<dt>payload <span class="property-type">number</span></dt>
|
|
170
|
+
<dd> filtered value.</dd>
|
|
171
|
+
</dl>
|
|
172
|
+
|
|
173
|
+
<h3>Parameter</h3>
|
|
174
|
+
|
|
175
|
+
<dl class="message-properties">
|
|
176
|
+
<dt>Topic <span class="property-type">string</span></dt>
|
|
177
|
+
<dd> defines the topic for the output message, "" is <code>msg.topic</code>.</dd>
|
|
178
|
+
<dt>Property <span class="property-type">string</span></dt>
|
|
179
|
+
<dd> defines the message property to be used as payload.</dd>
|
|
180
|
+
|
|
181
|
+
<dt>Filterzeit <span class="property-type">number</span></dt>
|
|
182
|
+
<dd> send output only after this time.</dd>
|
|
183
|
+
<dt>Filter Wert <span class="property-type">number</span></dt>
|
|
184
|
+
<dd> send output only if change is bigger.</dd>
|
|
185
|
+
<dt>Filter Wiederholungsfaktor <span class="property-type">number</span></dt>
|
|
186
|
+
<dd> do send output if change is lower then <code>Filter Wert</code> after n times <code>Filterzeit</code>.</dd>
|
|
187
|
+
<dt>Nachkommastellen <span class="property-type">number</span></dt>
|
|
188
|
+
<dd> Anzahl an Nachkommastellen..</dd>
|
|
189
|
+
<dt>Null ist Null <span class="property-type">boolean</span></dt>
|
|
190
|
+
<dd> If 0 is received, 0 is send immediately.</dd>
|
|
191
|
+
<dt>Status <span class="property-type">boolean</span></dt>
|
|
192
|
+
<dd> shows the actual value as a node status.</dd>
|
|
193
|
+
</dl>
|
|
194
|
+
</script>
|
package/math_filter.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
var tools = require('./tools.js');
|
|
3
|
+
|
|
4
|
+
function FilterNode(config) {
|
|
5
|
+
RED.nodes.createNode(this,config);
|
|
6
|
+
//this.config = config;
|
|
7
|
+
var node = this;
|
|
8
|
+
var context = this.context();
|
|
9
|
+
this.topic = config.topic ?? "";
|
|
10
|
+
this.property = config.property ?? "payload";
|
|
11
|
+
this.propertyType = config.propertyType ?? "msg";
|
|
12
|
+
this.control = config.control ?? 0;
|
|
13
|
+
this.controlType = config.controlType ?? "num";
|
|
14
|
+
this.processNoise = Number( config.processNoise ?? 1 );
|
|
15
|
+
this.measurementNoise = Number( config.measurementNoise ?? 1 );
|
|
16
|
+
this.stateVector = Number( config.stateVector ?? 1 );
|
|
17
|
+
this.controlVector = Number( config.controlVector ?? 0 );
|
|
18
|
+
this.measurementVector = Number( config.measurementVector ?? 1 );
|
|
19
|
+
this.adaptionCount = Number( config.adaptionCount ?? 0 );
|
|
20
|
+
this.contextStore = config.contextStore ?? "";
|
|
21
|
+
this.filterTime = Number( config.filter ?? 0 ) * tools.timeUnits( config.filterUnit );
|
|
22
|
+
this.filterValue = Number( config.filterVal ?? 0 );
|
|
23
|
+
this.filterLongTime = this.filterTime * Number( config.filterMul ?? 10 );
|
|
24
|
+
this.zeroIsZero = Boolean( config.zeroIsZero );
|
|
25
|
+
this.round = config.decimals ? Math.pow( 10, Number( config.decimals ) ) : null;
|
|
26
|
+
this.showState = Boolean( config.showState );
|
|
27
|
+
this.data = {};
|
|
28
|
+
this.last = {};
|
|
29
|
+
this.adaptation = {};
|
|
30
|
+
if( this.propertyType === "jsonata" )
|
|
31
|
+
{
|
|
32
|
+
try {
|
|
33
|
+
this.propertyPrepared = RED.util.prepareJSONataExpression( this.property, this );
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
node.error(RED._("debug.invalid-exp", {error: this.property}));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
node.status( "" );
|
|
41
|
+
context.get( "data", node.contextStore, function(err,value)
|
|
42
|
+
{
|
|
43
|
+
if( err )
|
|
44
|
+
{
|
|
45
|
+
node.error( err );
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
{
|
|
49
|
+
//console.log( "context read", value );
|
|
50
|
+
if( value !== undefined )
|
|
51
|
+
{
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
for( const i in value )
|
|
54
|
+
{
|
|
55
|
+
value[i].time = now;
|
|
56
|
+
value[i].processNoise = node.processNoise;
|
|
57
|
+
}
|
|
58
|
+
node.data = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} );
|
|
62
|
+
|
|
63
|
+
function setStatus(color,text)
|
|
64
|
+
{
|
|
65
|
+
if( node.showState )
|
|
66
|
+
{
|
|
67
|
+
node.status({ fill:color, shape:"dot", text:text });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
node.on('input', function(msg,send,done) {
|
|
72
|
+
if( msg.invalid )
|
|
73
|
+
{
|
|
74
|
+
done();
|
|
75
|
+
}
|
|
76
|
+
else if( msg.reset || msg.topic==="init" )
|
|
77
|
+
{
|
|
78
|
+
node.data = {};
|
|
79
|
+
node.last = {};
|
|
80
|
+
node.adaptation = {};
|
|
81
|
+
context.set( "data", node.data, node.contextStore );
|
|
82
|
+
node.status( "" );
|
|
83
|
+
done();
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
{
|
|
87
|
+
function getPayload(callback)
|
|
88
|
+
{
|
|
89
|
+
if( node.propertyPrepared )
|
|
90
|
+
{
|
|
91
|
+
RED.util.evaluateJSONataExpression( node.propertyPrepared, msg, function(err, value)
|
|
92
|
+
{
|
|
93
|
+
if( err )
|
|
94
|
+
{
|
|
95
|
+
done( err.message );
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
{
|
|
99
|
+
callback( value );
|
|
100
|
+
}
|
|
101
|
+
} );
|
|
102
|
+
}
|
|
103
|
+
else
|
|
104
|
+
{
|
|
105
|
+
callback( RED.util.getMessageProperty( msg, node.property ) );
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
getPayload( function(value)
|
|
109
|
+
{
|
|
110
|
+
if( node.topic )
|
|
111
|
+
{
|
|
112
|
+
msg.topic = node.topic;
|
|
113
|
+
}
|
|
114
|
+
const payload = Number( value );
|
|
115
|
+
if( ! isNaN( payload ) )
|
|
116
|
+
{
|
|
117
|
+
const control = RED.util.evaluateNodeProperty( node.control, node.controlType, node, msg );
|
|
118
|
+
if( ! isNaN( control ) )
|
|
119
|
+
{
|
|
120
|
+
node.data[msg.topic] ??= {};
|
|
121
|
+
const data = node.data[msg.topic];
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
|
|
124
|
+
function kalmanFilter(measurement,control)
|
|
125
|
+
{
|
|
126
|
+
if( data.estimate === undefined )
|
|
127
|
+
{
|
|
128
|
+
data.processNoise = node.processNoise;
|
|
129
|
+
data.estimate = measurement / node.measurementVector;
|
|
130
|
+
data.covariance = node.measurementNoise / node.measurementVector**2;
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
{
|
|
134
|
+
// measurement time
|
|
135
|
+
const deltaTime = ( now - data.time ) / 1000;
|
|
136
|
+
const stateVector = 1 + ( node.stateVector - 1 ) * deltaTime;
|
|
137
|
+
const controlVector = node.controlVector * deltaTime;
|
|
138
|
+
// prediction
|
|
139
|
+
const predX = stateVector * data.estimate + controlVector * control;
|
|
140
|
+
const predCov = stateVector**2 * data.covariance + node.processNoise;
|
|
141
|
+
// innovation
|
|
142
|
+
const innovation = measurement - node.measurementVector * predX;
|
|
143
|
+
// adaptation
|
|
144
|
+
if( node.adaptionCount )
|
|
145
|
+
{
|
|
146
|
+
const adaptation = node.adaptation[msg.topic] ?? [];
|
|
147
|
+
adaptation.push( innovation );
|
|
148
|
+
if( adaptation.length > node.adaptionCount )
|
|
149
|
+
{
|
|
150
|
+
adaptation.shift();
|
|
151
|
+
const mean = adaptation.reduce( (a,b) => a + b ) / adaptation.length;
|
|
152
|
+
const variance = adaptation.reduce( (a,b) => a + ( b - mean ) ** 2, 0 ) / ( adaptation.length - 1 );
|
|
153
|
+
data.r = Math.max( variance, 1e-6 );
|
|
154
|
+
data.r2 = ( 2 * variance + node.processNoise ) / 3;
|
|
155
|
+
}
|
|
156
|
+
node.adaptation[msg.topic] = adaptation;
|
|
157
|
+
}
|
|
158
|
+
// kalman gain
|
|
159
|
+
const gain = predCov * node.measurementVector / ( predCov * node.measurementVector**2 + node.measurementNoise );
|
|
160
|
+
// correction
|
|
161
|
+
data.estimate = predX + gain * innovation;
|
|
162
|
+
data.covariance = ( 1 - gain * node.measurementVector ) * predCov;
|
|
163
|
+
}
|
|
164
|
+
data.time = now;
|
|
165
|
+
return data.estimate;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sendValue(value)
|
|
169
|
+
{
|
|
170
|
+
msg.payload = value;
|
|
171
|
+
node.last[msg.topic] = {value:value,time:now};
|
|
172
|
+
setStatus( "green", tools.formatNumber(value) );
|
|
173
|
+
send( msg );
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if( node.zeroIsZero && payload === 0 )
|
|
177
|
+
{
|
|
178
|
+
data.estimate = 0;
|
|
179
|
+
sendValue( 0 );
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
{
|
|
183
|
+
let value = kalmanFilter( payload, control );
|
|
184
|
+
const help = node.last[msg.topic];
|
|
185
|
+
if( help === undefined ||
|
|
186
|
+
( help.time + node.filterTime < now && tools.distance( help.value, value ) >= node.filterValue ) ||
|
|
187
|
+
( node.filterLongTime > 0 && help.time + node.filterLongTime < now ) )
|
|
188
|
+
{
|
|
189
|
+
if( node.round )
|
|
190
|
+
{
|
|
191
|
+
value = Math.round( value * node.round ) / node.round;
|
|
192
|
+
}
|
|
193
|
+
sendValue( value );
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
{
|
|
197
|
+
setStatus( "gray", tools.formatNumber(value) );
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
context.set( "data", node.data, node.contextStore );
|
|
201
|
+
}
|
|
202
|
+
else
|
|
203
|
+
{
|
|
204
|
+
setStatus( "red", "control is NaN" );
|
|
205
|
+
node.warn( `control is NaN (${msg.topic}=${control})` );
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
{
|
|
210
|
+
if( value !== undefined )
|
|
211
|
+
{
|
|
212
|
+
setStatus( "red", "payload is NaN" );
|
|
213
|
+
node.warn( `payload is NaN (${msg.topic}=${value})` );
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
done();
|
|
217
|
+
} );
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
RED.nodes.registerType("filter",FilterNode);
|
|
223
|
+
}
|
package/math_hysteresis.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
|
-
this.topic = config.topic
|
|
9
|
+
this.topic = config.topic ?? "";
|
|
10
10
|
this.property = config.property ?? "payload";
|
|
11
11
|
this.propertyType = config.propertyType ?? "msg";
|
|
12
12
|
this.threshold_rise = Number( config.threshold_raise );
|
package/math_mean.html
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
<input type="text" id="node-input-topic" placeholder="Topic">
|
|
62
62
|
</div>
|
|
63
63
|
<div class="form-row">
|
|
64
|
-
<label for="node-input-
|
|
64
|
+
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> Eigenschaft</label>
|
|
65
65
|
<input type="text" id="node-input-property">
|
|
66
66
|
<input type="hidden" id="node-input-propertyType">
|
|
67
67
|
</div>
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
</div>
|
|
100
100
|
<div class="form-row">
|
|
101
101
|
<label for="node-input-decimals"><i class="fa fa-sort-numeric-asc"></i> Nachkommastellen</label>
|
|
102
|
-
<input type="
|
|
102
|
+
<input type="number" id="node-input-decimals">
|
|
103
103
|
</div>
|
|
104
104
|
<div class="form-row">
|
|
105
105
|
<label for="node-input-zeroIsZero"><i class="fa fa-exclamation-circle"></i> Null ist Null</label>
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
<dd> do send output if change is lower then <code>Filter Wert</code> after n times <code>Filterzeit</code>.</dd>
|
|
155
155
|
<dt>Nachkommastellen <span class="property-type">number</span></dt>
|
|
156
156
|
<dd> Anzahl an Nachkommastellen..</dd>
|
|
157
|
-
<dt>Null ist
|
|
157
|
+
<dt>Null ist Null <span class="property-type">boolean</span></dt>
|
|
158
158
|
<dd> If 0 is received, 0 is send immediately.</dd>
|
|
159
159
|
<dt>Status <span class="property-type">boolean</span></dt>
|
|
160
160
|
<dd> shows the actual value as a node status.</dd>
|
package/math_mean.js
CHANGED
|
@@ -6,8 +6,8 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
|
-
this.topic = config.topic
|
|
10
|
-
this.property = config.property
|
|
9
|
+
this.topic = config.topic ?? "";
|
|
10
|
+
this.property = config.property ?? "payload";
|
|
11
11
|
this.propertyType = config.propertyType ?? "msg";
|
|
12
12
|
this.deltaTime = Number( config.deltaTime ?? 60 ) * tools.timeUnits( config.deltaUnit );
|
|
13
13
|
this.minData = Number( config.minData ?? 1 );
|
|
@@ -75,6 +75,10 @@ module.exports = function(RED) {
|
|
|
75
75
|
}
|
|
76
76
|
getPayload( function(value)
|
|
77
77
|
{
|
|
78
|
+
if( node.topic )
|
|
79
|
+
{
|
|
80
|
+
msg.topic = node.topic;
|
|
81
|
+
}
|
|
78
82
|
const payload = Number( value );
|
|
79
83
|
if( ! isNaN( payload ) )
|
|
80
84
|
{
|
|
@@ -95,10 +99,6 @@ module.exports = function(RED) {
|
|
|
95
99
|
msg.count = count;
|
|
96
100
|
last[msg.topic] = {value:value,time:now};
|
|
97
101
|
context.set( "last", last );
|
|
98
|
-
if( node.topic )
|
|
99
|
-
{
|
|
100
|
-
msg.topic = node.topic;
|
|
101
|
-
}
|
|
102
102
|
node.status({fill:"green",shape:"dot",text:`${item.length} / ${tools.formatNumber(value)}`});
|
|
103
103
|
send( msg );
|
|
104
104
|
}
|
package/math_reduce.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports = function(RED) {
|
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
9
|
this.topic = config.topic ?? "";
|
|
10
|
-
this.property = config.property
|
|
10
|
+
this.property = config.property ?? "payload";
|
|
11
11
|
this.propertyType = config.propertyType ?? "msg";
|
|
12
12
|
this.minMean = Number( config.minMean ?? 1 );
|
|
13
13
|
this.maxMean = Number( config.maxMean ?? 1 );
|
package/math_rising.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
|
-
this.topic = config.topic
|
|
9
|
+
this.topic = config.topic ?? "";
|
|
10
10
|
this.property = config.property ?? "payload";
|
|
11
11
|
this.propertyType = config.propertyType ?? "msg";
|
|
12
12
|
this.threshold = Number( config.threshold );
|
package/math_statistics.html
CHANGED
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
});
|
|
41
41
|
// populate store names
|
|
42
42
|
$("#node-input-contextStore").empty();
|
|
43
|
-
$("#node-input-contextStore").append(`<option value="none">None</option>`);
|
|
44
43
|
const defaultStore = RED.settings.hasOwnProperty('context') ? RED.settings.context.default : ''
|
|
45
44
|
RED.settings.context.stores.forEach(function(item){
|
|
46
45
|
if( item ) {
|
package/math_statistics.js
CHANGED
|
@@ -6,11 +6,11 @@ module.exports = function(RED) {
|
|
|
6
6
|
//this.config = config;
|
|
7
7
|
var node = this;
|
|
8
8
|
var context = this.context();
|
|
9
|
-
this.property = config.property
|
|
9
|
+
this.property = config.property ?? "payload";
|
|
10
10
|
this.propertyType = config.propertyType ?? "msg";
|
|
11
11
|
this.deltaTime = Number( config.deltaTime ?? 60 ) * tools.timeUnits( config.deltaUnit );
|
|
12
12
|
this.minData = Number( config.minData ?? 1 );
|
|
13
|
-
this.contextStore = config.contextStore ?? "
|
|
13
|
+
this.contextStore = config.contextStore ?? "";
|
|
14
14
|
this.showState = Boolean( config.showState );
|
|
15
15
|
if( this.propertyType === "jsonata" )
|
|
16
16
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mschaeffler/node-red-logic",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "nodes for simple operations.",
|
|
5
5
|
"license": "LGPL-2.1",
|
|
6
6
|
"homepage": "https://github.com/m-schaeffler/node-red-my-nodes/tree/main/node-red-logic",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"hysteresisEdge": "math_hysteresis.js",
|
|
33
33
|
"threePoint": "math_3point.js",
|
|
34
34
|
"statistics": "math_statistics.js",
|
|
35
|
-
"mean": "math_mean.js"
|
|
35
|
+
"mean": "math_mean.js",
|
|
36
|
+
"filter": "math_filter.js"
|
|
36
37
|
}
|
|
37
38
|
},
|
|
38
39
|
"author": {
|
|
@@ -40,6 +41,6 @@
|
|
|
40
41
|
"url": "https://github.com/m-schaeffler"
|
|
41
42
|
},
|
|
42
43
|
"scripts": {
|
|
43
|
-
"test": "mocha \"test
|
|
44
|
+
"test": "mocha \"test/math_filter_spec.js\""
|
|
44
45
|
}
|
|
45
46
|
}
|
package/logic_blinker.html.save
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
<script type="text/javascript">
|
|
2
|
-
RED.nodes.registerType('blinker',{
|
|
3
|
-
category: 'logic',
|
|
4
|
-
color: "#C0DEED",
|
|
5
|
-
defaults: {
|
|
6
|
-
name:{value:""},
|
|
7
|
-
property:{value:"payload",required:true},
|
|
8
|
-
onTime:{value:"1",required:true},
|
|
9
|
-
onTimeUnit:{value:"secs"},
|
|
10
|
-
offTime:{value:"1",required:true},
|
|
11
|
-
offTimeUnit:{value:"secs"},
|
|
12
|
-
outputOn:{value:true,required:true},
|
|
13
|
-
outputOnType:{value:"bool"},
|
|
14
|
-
outputPause:{value:false,required:true},
|
|
15
|
-
outputPauseType:{value:"bool"},
|
|
16
|
-
outputOff:{value:false,required:true},
|
|
17
|
-
outputOffType:{value:"bool"},
|
|
18
|
-
showState:{value:false}
|
|
19
|
-
},
|
|
20
|
-
inputs:1,
|
|
21
|
-
outputs:1,
|
|
22
|
-
icon: "font-awesome/fa-refresh",
|
|
23
|
-
label: function() {
|
|
24
|
-
return this.name||"blinker";
|
|
25
|
-
},
|
|
26
|
-
labelStyle: function() {
|
|
27
|
-
return this.name?"node_label_italic":"";
|
|
28
|
-
},
|
|
29
|
-
paletteLabel: "blinker",
|
|
30
|
-
inputLabels: 'input',
|
|
31
|
-
outputLabels: 'blinken',
|
|
32
|
-
oneditprepare: function() {
|
|
33
|
-
$("#node-input-property").typedInput({
|
|
34
|
-
type:"msg",
|
|
35
|
-
types:["msg"],
|
|
36
|
-
typeField: "#node-input-property-type"
|
|
37
|
-
})
|
|
38
|
-
$("#node-input-outputOn").typedInput({
|
|
39
|
-
type:"bool",
|
|
40
|
-
types:["str","num","bool","json"],
|
|
41
|
-
typeField: "#node-input-outputOnType"
|
|
42
|
-
})
|
|
43
|
-
$("#node-input-outputOff").typedInput({
|
|
44
|
-
type:"bool",
|
|
45
|
-
types:["str","num","bool","json"],
|
|
46
|
-
typeField: "#node-input-outputOffType"
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
</script>7
|
|
51
|
-
|
|
52
|
-
<script type="text/html" data-template-name="blinker">
|
|
53
|
-
<div class="form-row">
|
|
54
|
-
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
55
|
-
<input type="text" id="node-input-name" placeholder="Name">
|
|
56
|
-
</div>
|
|
57
|
-
<div class="form-row">
|
|
58
|
-
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> Eigenschaft</label>
|
|
59
|
-
<input type="text" id="node-input-property">
|
|
60
|
-
<input type="hidden" id="node-input-property-type">
|
|
61
|
-
</div>
|
|
62
|
-
<div class="form-row">
|
|
63
|
-
<label for="node-input-onTime"><i class="fa fa-hourglass-start"></i> on time</label>
|
|
64
|
-
<input type="number" id="node-input-onTime" style="width:70px;" placeholder="Time">
|
|
65
|
-
<select id="node-input-onTimeUnit" style="width:140px;">
|
|
66
|
-
<option value="msecs">MilliSeconds</option>
|
|
67
|
-
<option value="secs">Seconds</option>
|
|
68
|
-
<option value="mins">Minutes</option>
|
|
69
|
-
<option value="hours">Hours</option>
|
|
70
|
-
</select>
|
|
71
|
-
</div>
|
|
72
|
-
<div class="form-row">
|
|
73
|
-
<label for="node-input-pauseTime"><i class="fa fa-hourglass-end"></i> pause time</label>
|
|
74
|
-
<input type="number" id="node-input-ffTime" style="width:70px;" placeholder="Time">
|
|
75
|
-
<select id="node-input-offTimeUnit" style="width:140px;">
|
|
76
|
-
<option value="msecs">MilliSeconds</option>
|
|
77
|
-
<option value="secs">Seconds</option>
|
|
78
|
-
<option value="mins">Minutes</option>
|
|
79
|
-
<option value="hours">Hours</option>
|
|
80
|
-
</select>
|
|
81
|
-
</div>
|
|
82
|
-
<div class="form-row">
|
|
83
|
-
<label for="node-input-outputOn"><i class="fa fa-sign-out"></i> on Output</label>
|
|
84
|
-
<input type="text" id="node-input-outputOn">
|
|
85
|
-
<input type="hidden" id="node-input-outputOnType">
|
|
86
|
-
</div>
|
|
87
|
-
<div class="form-row">
|
|
88
|
-
<label for="node-input-outputPause"><i class="fa fa-sign-out"></i> pause Output</label>
|
|
89
|
-
<input type="text" id="node-input-outputPause">
|
|
90
|
-
<input type="hidden" id="node-input-outputPauseType">
|
|
91
|
-
</div>
|
|
92
|
-
<div class="form-row">
|
|
93
|
-
<label for="node-input-outputOff"><i class="fa fa-sign-out"></i> off Output</label>
|
|
94
|
-
<input type="text" id="node-input-outputOff">
|
|
95
|
-
<input type="hidden" id="node-input-outputOffType">
|
|
96
|
-
</div>
|
|
97
|
-
<div class="form-row">
|
|
98
|
-
<label for="node-input-howStatus"><i class="fa fa-star-o"></i> Status</label>
|
|
99
|
-
<input type="checkbox" id="node-input-showState" style="display:inline-block; width:20px; vertical-align:baseline;">
|
|
100
|
-
</div>
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
<script type="text/html" data-help-name="blinker">
|
|
104
|
-
|
|
105
|
-
<p>Sends the payload converted to boolean delayed. The delay time can de controlled for on and off switching individually.</p>
|
|
106
|
-
<p>If the msg property <code>invalid</code> is present in the message, all nodes ignore the message.</p>
|
|
107
|
-
|
|
108
|
-
<table>
|
|
109
|
-
<tr> <th>type</th> <th>input</th> <th>output</th> </tr>
|
|
110
|
-
<tr> <td>boolean</td> <td>false</td> <td>false</td> </tr>
|
|
111
|
-
<tr> <td></td> <td>true</td> <td>true</td> </tr>
|
|
112
|
-
<tr> <td>number</td> <td>0</td> <td>false</td> </tr>
|
|
113
|
-
<tr> <td></td> <td>1</td> <td>true</td> </tr>
|
|
114
|
-
<tr> <td>string</td> <td>false</td> <td>false</td> </tr>
|
|
115
|
-
<tr> <td></td> <td>true</td> <td>true</td> </tr>
|
|
116
|
-
<tr> <td></td> <td>0</td> <td>false</td> </tr>
|
|
117
|
-
<tr> <td></td> <td>1</td> <td>true</td> </tr>
|
|
118
|
-
<tr> <td></td> <td>off</td> <td>false</td> </tr>
|
|
119
|
-
<tr> <td></td> <td>on</td> <td>true</td> </tr>
|
|
120
|
-
</table>
|
|
121
|
-
|
|
122
|
-
<h3>Input</h3>
|
|
123
|
-
|
|
124
|
-
<p>The message property to be used as payload can be defined with the <code>Property</code> property.</p>
|
|
125
|
-
|
|
126
|
-
<dl class="message-properties">
|
|
127
|
-
<dt>payload <span class="property-type"></span></dt>
|
|
128
|
-
<dd> input value.</dd>
|
|
129
|
-
</dl>
|
|
130
|
-
|
|
131
|
-
<h3>Output</h3>
|
|
132
|
-
|
|
133
|
-
<dl class="message-properties">
|
|
134
|
-
<dt>payload <span class="property-type">boolean</span></dt>
|
|
135
|
-
<dd> bool value of payload.</dd>
|
|
136
|
-
</dl>
|
|
137
|
-
|
|
138
|
-
<h3>Parameter</h3>
|
|
139
|
-
|
|
140
|
-
<dl class="message-properties">
|
|
141
|
-
<dt>Property <span class="property-type">string</span></dt>
|
|
142
|
-
<dd> defines the message property to be used as payload.</dd>
|
|
143
|
-
<dt>Delay <span class="property-type">number</span></dt>
|
|
144
|
-
<dd> delay time in msec for switching from off to on.</dd>
|
|
145
|
-
<dt>Postrun <span class="property-type">number</span></dt>
|
|
146
|
-
<dd> delay time in msec for switching from on to off.</dd>
|
|
147
|
-
<dt>min on time <span class="property-type">number</span></dt>
|
|
148
|
-
<dd> minimal time in msec to be in on state.</dd>
|
|
149
|
-
<dt>max on time <span class="property-type">number</span></dt>
|
|
150
|
-
<dd> maximal time in msec to be in on state; 0 is infinite.</dd>
|
|
151
|
-
<dt>on Output <span class="property-type">multi</span></dt>
|
|
152
|
-
<dd> Value to be sent in on case.</dd>
|
|
153
|
-
<dt>off Output <span class="property-type">multi</span></dt>
|
|
154
|
-
<dd> Value to be sent in off case.</dd>
|
|
155
|
-
<dt>Status <span class="property-type">boolean</span></dt>
|
|
156
|
-
<dd> shows the actual value as a node status.</dd>
|
|
157
|
-
</dl>
|
|
158
|
-
|
|
159
|
-
</script>
|