@mschaeffler/node-red-logic 4.3.0 → 4.3.1

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/math_filter.html CHANGED
@@ -7,15 +7,7 @@
7
7
  topic:{value:""},
8
8
  property:{value:"payload", required:true},
9
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},
10
+ alpha:{value:0.5, validate:RED.validators.number(), required:true },
19
11
  filter:{value:0, validate:RED.validators.number()},
20
12
  filterUnit:{value:"secs"},
21
13
  filterVal:{value:0, validate:RED.validators.number()},
@@ -42,23 +34,6 @@
42
34
  types:["msg","jsonata"],
43
35
  typeField: "#node-input-propertyType"
44
36
  });
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
37
  }
63
38
  });
64
39
  </script>
@@ -78,40 +53,9 @@
78
53
  <input type="hidden" id="node-input-propertyType">
79
54
  </div>
80
55
  <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">
56
+ <label for="node-input-alpha"> alpha</label>
57
+ <input type="text" id="node-input-alpha">
97
58
  </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
59
  <div class="form-row">
116
60
  <label for="node-input-filter"><i class="fa fa-filter"></i> filter time</label>
117
61
  <input type="number" id="node-input-filter" style="width:70px;" placeholder="Time">
package/math_filter.js CHANGED
@@ -6,27 +6,18 @@ 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 ?? "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 );
9
+ this.topic = config.topic ?? "";
10
+ this.property = config.property ?? "payload";
11
+ this.propertyType = config.propertyType ?? "msg";
12
+ this.alpha = Number( config.alpha ?? 0.5 );
13
+ this.filterTime = Number( config.filter ?? 0 ) * tools.timeUnits( config.filterUnit );
14
+ this.filterValue = Number( config.filterVal ?? 0 );
15
+ this.filterLongTime = this.filterTime * Number( config.filterMul ?? 10 );
16
+ this.zeroIsZero = Boolean( config.zeroIsZero );
17
+ this.round = config.decimals ? Math.pow( 10, Number( config.decimals ) ) : null;
18
+ this.showState = Boolean( config.showState );
27
19
  this.data = {};
28
20
  this.last = {};
29
- this.adaptation = {};
30
21
  if( this.propertyType === "jsonata" )
31
22
  {
32
23
  try {
@@ -38,27 +29,6 @@ module.exports = function(RED) {
38
29
  }
39
30
  }
40
31
  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
32
 
63
33
  function setStatus(color,text)
64
34
  {
@@ -75,10 +45,8 @@ module.exports = function(RED) {
75
45
  }
76
46
  else if( msg.reset || msg.topic==="init" )
77
47
  {
78
- node.data = {};
79
- node.last = {};
80
- node.adaptation = {};
81
- context.set( "data", node.data, node.contextStore );
48
+ node.data = {};
49
+ node.last = {};
82
50
  node.status( "" );
83
51
  done();
84
52
  }
@@ -114,55 +82,21 @@ module.exports = function(RED) {
114
82
  const payload = Number( value );
115
83
  if( ! isNaN( payload ) )
116
84
  {
117
- const control = RED.util.evaluateNodeProperty( node.control, node.controlType, node, msg );
118
- if( ! isNaN( control ) )
119
- {
120
85
  node.data[msg.topic] ??= {};
121
86
  const data = node.data[msg.topic];
122
87
  const now = Date.now();
123
88
 
124
- function kalmanFilter(measurement,control)
89
+ function lowPassFilter(measurement)
125
90
  {
126
- if( data.estimate === undefined )
91
+ if( data.last == undefined )
127
92
  {
128
- data.processNoise = node.processNoise;
129
- data.estimate = measurement / node.measurementVector;
130
- data.covariance = node.measurementNoise / node.measurementVector**2;
93
+ data.last = measurement;
131
94
  }
132
95
  else
133
96
  {
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;
97
+ data.last += node.alpha * ( measurement - data.last );
163
98
  }
164
- data.time = now;
165
- return data.estimate;
99
+ return data.last;
166
100
  }
167
101
 
168
102
  function sendValue(value)
@@ -180,7 +114,7 @@ module.exports = function(RED) {
180
114
  }
181
115
  else
182
116
  {
183
- let value = kalmanFilter( payload, control );
117
+ let value = lowPassFilter( payload );
184
118
  const help = node.last[msg.topic];
185
119
  if( help === undefined ||
186
120
  ( help.time + node.filterTime < now && tools.distance( help.value, value ) >= node.filterValue ) ||
@@ -198,12 +132,6 @@ module.exports = function(RED) {
198
132
  }
199
133
  }
200
134
  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
135
  }
208
136
  else
209
137
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mschaeffler/node-red-logic",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
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",