@blokcert/node-red-contrib-ocpp 1.1.1 → 1.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.2.0] - 2026-02-12
6
+
7
+ ### Changed
8
+ - **BREAKING**: `ocpp-command` node now has single output (device response only)
9
+ - `ocpp-command` now subscribes to Redis Pub/Sub to receive responses directly
10
+ - Removed "Command Sent" output - node only outputs when device responds or times out
11
+
12
+ ### Removed
13
+ - **Removed `ocpp-response` node** - No longer needed, `ocpp-command` handles responses directly
14
+
15
+ ### Fixed
16
+ - Responses from chargers now properly route back to `ocpp-command` node via WS Server
17
+
5
18
  ## [1.1.1] - 2026-02-12
6
19
 
7
20
  ### Fixed
package/README.md CHANGED
@@ -89,7 +89,7 @@ msg.ocpp.errorDescription = "Action not supported";
89
89
 
90
90
  ### ocpp command
91
91
 
92
- Sends CSMS-initiated commands to charging stations.
92
+ Sends CSMS-initiated commands to charging stations and receives responses.
93
93
 
94
94
  **Supported Commands:**
95
95
  - Reset
@@ -103,9 +103,29 @@ Sends CSMS-initiated commands to charging stations.
103
103
  - ClearCache
104
104
  - And more...
105
105
 
106
- **Outputs:**
107
- - Output 1: Command sent confirmation
108
- - Output 2: Device response or timeout error (when "Wait for Response" is enabled)
106
+ **Output:**
107
+ Single output that emits when the device responds (or when a timeout occurs).
108
+
109
+ **Response Message:**
110
+ ```javascript
111
+ {
112
+ payload: { /* device response payload */ },
113
+ ocpp: {
114
+ messageId: "abc123",
115
+ action: "GetConfiguration", // Original command
116
+ tenant: "operator1",
117
+ deviceId: "charger001",
118
+ identity: "operator1:charger001",
119
+ response: true,
120
+ error: false, // true for CALLERROR or timeout
121
+ errorCode: null, // Set for errors
122
+ errorDescription: null, // Set for errors
123
+ sentAt: 1699999999999,
124
+ receivedAt: 1699999999150,
125
+ latency: 150 // Round-trip time in ms
126
+ }
127
+ }
128
+ ```
109
129
 
110
130
  **Example:**
111
131
  ```javascript
@@ -120,37 +140,6 @@ msg.payload = {
120
140
  return msg;
121
141
  ```
122
142
 
123
- ### ocpp response
124
-
125
- Routes CALLRESULT/CALLERROR responses back to the originating ocpp-command nodes.
126
-
127
- **Features:**
128
- - Automatically matches responses to pending commands
129
- - Notifies ocpp-command nodes of responses
130
- - Calculates round-trip latency
131
-
132
- **Setup:**
133
- Connect this node to an `ocpp in` node (optionally filtered by `_Response`) to receive device responses:
134
-
135
- ```
136
- [ocpp in (filter: _Response)] → [ocpp response] → [debug]
137
- ```
138
-
139
- **Output:**
140
- ```javascript
141
- {
142
- payload: { /* device response */ },
143
- ocpp: {
144
- messageId: "abc123",
145
- action: "GetConfiguration", // Original command
146
- response: true,
147
- error: false,
148
- latency: 150, // Round-trip time in ms
149
- handled: true // Whether an ocpp-command node was notified
150
- }
151
- }
152
- ```
153
-
154
143
  ## Example Flow
155
144
 
156
145
  ```
@@ -8,11 +8,10 @@
8
8
  command: { value: '' },
9
9
  timeout: { value: 30000, validate: RED.validators.number() },
10
10
  streamMaxLen: { value: 1000, validate: RED.validators.number() },
11
- waitForResponse: { value: true },
12
11
  },
13
12
  inputs: 1,
14
- outputs: 2,
15
- outputLabels: ['sent', 'response/error'],
13
+ outputs: 1,
14
+ outputLabels: ['response'],
16
15
  icon: 'font-awesome/fa-bolt',
17
16
  paletteLabel: 'ocpp command',
18
17
  label: function () {
@@ -75,11 +74,6 @@
75
74
  <label for="node-input-timeout"><i class="fa fa-clock-o"></i> Timeout (ms)</label>
76
75
  <input type="number" id="node-input-timeout" placeholder="30000">
77
76
  </div>
78
- <div class="form-row">
79
- <label for="node-input-waitForResponse"><i class="fa fa-reply"></i> Wait Response</label>
80
- <input type="checkbox" id="node-input-waitForResponse" style="width: auto; margin-left: 0;">
81
- <span style="margin-left: 10px;">Wait for device response (enables second output)</span>
82
- </div>
83
77
  <div class="form-row">
84
78
  <label for="node-input-streamMaxLen"><i class="fa fa-list-ol"></i> Stream Max Len</label>
85
79
  <input type="number" id="node-input-streamMaxLen" placeholder="1000">
@@ -87,7 +81,7 @@
87
81
  </script>
88
82
 
89
83
  <script type="text/html" data-help-name="ocpp-command">
90
- <p>Sends CSMS-initiated commands to charging stations.</p>
84
+ <p>Sends CSMS-initiated commands to charging stations and outputs the device response.</p>
91
85
 
92
86
  <h3>Inputs</h3>
93
87
  <dl class="message-properties">
@@ -107,31 +101,23 @@
107
101
  <dd>Custom message ID (auto-generated if not provided).</dd>
108
102
  </dl>
109
103
 
110
- <h3>Outputs</h3>
111
- <ol class="node-ports">
112
- <li>Command Sent
113
- <dl class="message-properties">
114
- <dt>payload <span class="property-type">object</span></dt>
115
- <dd>Original payload</dd>
116
- <dt>ocpp.messageId <span class="property-type">string</span></dt>
117
- <dd>Generated/provided message ID</dd>
118
- <dt>ocpp.sentAt <span class="property-type">number</span></dt>
119
- <dd>Timestamp when command was sent</dd>
120
- </dl>
121
- </li>
122
- <li>Response/Error (when Wait Response is enabled)
123
- <dl class="message-properties">
124
- <dt>payload <span class="property-type">object</span></dt>
125
- <dd>Response payload from device</dd>
126
- <dt>ocpp.response <span class="property-type">boolean</span></dt>
127
- <dd>True if this is a response</dd>
128
- <dt>ocpp.error <span class="property-type">boolean</span></dt>
129
- <dd>True if this is an error/timeout</dd>
130
- <dt>ocpp.receivedAt <span class="property-type">number</span></dt>
131
- <dd>Timestamp when response was received</dd>
132
- </dl>
133
- </li>
134
- </ol>
104
+ <h3>Output</h3>
105
+ <dl class="message-properties">
106
+ <dt>payload <span class="property-type">object</span></dt>
107
+ <dd>Response payload from device (or null on timeout)</dd>
108
+ <dt>ocpp.messageId <span class="property-type">string</span></dt>
109
+ <dd>Message ID of the command</dd>
110
+ <dt>ocpp.action <span class="property-type">string</span></dt>
111
+ <dd>Command action that was sent</dd>
112
+ <dt>ocpp.response <span class="property-type">boolean</span></dt>
113
+ <dd>Always true for output messages</dd>
114
+ <dt>ocpp.error <span class="property-type">boolean</span></dt>
115
+ <dd>True if this is an error or timeout</dd>
116
+ <dt>ocpp.errorCode <span class="property-type">string</span></dt>
117
+ <dd>Error code (if error)</dd>
118
+ <dt>ocpp.latency <span class="property-type">number</span></dt>
119
+ <dd>Round-trip time in milliseconds</dd>
120
+ </dl>
135
121
 
136
122
  <h3>Properties</h3>
137
123
  <dl class="message-properties">
@@ -139,16 +125,14 @@
139
125
  <dd>OCPP command to send. Can be overridden via <code>msg.ocpp.command</code>.</dd>
140
126
 
141
127
  <dt>Timeout <span class="property-type">number</span></dt>
142
- <dd>How long to wait for device response (ms).</dd>
143
-
144
- <dt>Wait Response <span class="property-type">boolean</span></dt>
145
- <dd>If enabled, waits for device response and outputs on second port.</dd>
128
+ <dd>How long to wait for device response (ms). Default: 30000</dd>
146
129
  </dl>
147
130
 
148
131
  <h3>Common Commands</h3>
149
132
  <table>
150
133
  <tr><th>Command</th><th>Description</th></tr>
151
134
  <tr><td>Reset</td><td>Reboot the charging station</td></tr>
135
+ <tr><td>GetConfiguration</td><td>Get device configuration keys</td></tr>
152
136
  <tr><td>RemoteStartTransaction</td><td>Start charging remotely (1.6)</td></tr>
153
137
  <tr><td>RequestStartTransaction</td><td>Start charging remotely (2.0.1)</td></tr>
154
138
  <tr><td>UnlockConnector</td><td>Unlock a connector</td></tr>
@@ -156,34 +140,18 @@
156
140
  <tr><td>ChangeAvailability</td><td>Set connector availability</td></tr>
157
141
  </table>
158
142
 
159
- <h3>Example: Remote Start</h3>
143
+ <h3>Example: GetConfiguration</h3>
160
144
  <pre>
161
- // For OCPP 1.6
162
145
  msg.ocpp = {
163
- tenant: "operator1",
146
+ tenant: "test",
164
147
  deviceId: "charger001",
165
- command: "RemoteStartTransaction"
166
- };
167
- msg.payload = {
168
- connectorId: 1,
169
- idTag: "USER123"
148
+ command: "GetConfiguration"
170
149
  };
150
+ msg.payload = {}; // Empty = get all keys
171
151
  return msg;
172
152
 
173
- // For OCPP 2.0.1
174
- msg.ocpp = {
175
- tenant: "operator1",
176
- deviceId: "station001",
177
- command: "RequestStartTransaction"
178
- };
179
- msg.payload = {
180
- evseId: 1,
181
- idToken: {
182
- idToken: "USER123",
183
- type: "ISO14443"
184
- }
185
- };
186
- return msg;
153
+ // Response will contain:
154
+ // msg.payload.configurationKey = [...]
187
155
  </pre>
188
156
 
189
157
  <h3>Example: Reset</h3>
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * OCPP Command Node
3
3
  * Sends CSMS-initiated commands to charging stations
4
+ *
5
+ * Output: Device response (or timeout error)
4
6
  */
5
7
  const { Keys } = require('../lib/redis-client');
6
8
  const { buildCall } = require('../lib/ocpp-normalizer');
@@ -23,13 +25,16 @@ module.exports = function (RED) {
23
25
  node.command = config.command || ''; // Can be overridden by msg
24
26
  node.timeout = parseInt(config.timeout, 10) || 30000;
25
27
  node.streamMaxLen = parseInt(config.streamMaxLen, 10) || 1000;
26
- node.waitForResponse = config.waitForResponse !== false; // Default true
27
28
 
28
29
  const redis = node.server.redis;
29
30
 
30
- // Pending commands waiting for response
31
+ // Create a separate Redis connection for Pub/Sub
32
+ let subRedis = null;
31
33
  const pendingCommands = new Map();
32
34
 
35
+ // Generate unique server ID for this node instance
36
+ const nodeServerId = `nodered-${node.id.slice(0, 8)}-${Date.now()}`;
37
+
33
38
  /**
34
39
  * Generate unique message ID
35
40
  */
@@ -37,6 +42,110 @@ module.exports = function (RED) {
37
42
  return crypto.randomUUID();
38
43
  }
39
44
 
45
+ /**
46
+ * Initialize Pub/Sub subscriber
47
+ */
48
+ async function initSubscriber() {
49
+ try {
50
+ // Duplicate the Redis connection for Pub/Sub
51
+ subRedis = redis.duplicate();
52
+
53
+ subRedis.on('error', (err) => {
54
+ node.error(`Subscriber Redis error: ${err.message}`);
55
+ });
56
+
57
+ // Subscribe to our response channel
58
+ const respChannel = `ws:resp:${nodeServerId}`;
59
+ await subRedis.subscribe(respChannel);
60
+ node.log(`Subscribed to ${respChannel}`);
61
+
62
+ subRedis.on('subscribe', (channel, count) => {
63
+ node.log(`Subscribe confirmed: ${channel} (total: ${count})`);
64
+ });
65
+
66
+ // Handle incoming messages
67
+ subRedis.on('message', (channel, message) => {
68
+ node.log(`Pub/Sub message received on ${channel}`);
69
+ try {
70
+ const data = JSON.parse(message);
71
+ const messageId = data.messageId || data.uniqueID;
72
+
73
+ if (!messageId) {
74
+ node.warn('Received response without messageId');
75
+ return;
76
+ }
77
+
78
+ node.log(`Looking up pending for messageId: ${messageId}`);
79
+ const pending = pendingCommands.get(messageId);
80
+ if (!pending) {
81
+ node.log(`No pending command found for ${messageId}, pendingCommands size: ${pendingCommands.size}`);
82
+ // Not our command, ignore
83
+ return;
84
+ }
85
+
86
+ // Clear timeout
87
+ clearTimeout(pending.timeoutId);
88
+ pendingCommands.delete(messageId);
89
+
90
+ // Remove from Redis pending hash
91
+ const pendingKey = Keys.pending(pending.tenant, pending.deviceId);
92
+ redis.hdel(pendingKey, messageId).catch(() => {});
93
+
94
+ // Determine if error
95
+ const isError = data.messageType === 4 || data.error === true;
96
+
97
+ // Update status
98
+ node.status({
99
+ fill: isError ? 'red' : 'green',
100
+ shape: 'dot',
101
+ text: `${pending.action}: ${isError ? 'error' : 'ok'}`,
102
+ });
103
+
104
+ node.log(`Found pending command for ${messageId}, sending to output`);
105
+
106
+ // Build output message with device response
107
+ const responseMsg = {
108
+ payload: data.payload || data,
109
+ ocpp: {
110
+ messageId,
111
+ action: pending.action,
112
+ tenant: pending.tenant,
113
+ deviceId: pending.deviceId,
114
+ identity: `${pending.tenant}:${pending.deviceId}`,
115
+ response: true,
116
+ error: isError,
117
+ errorCode: data.errorCode,
118
+ errorDescription: data.errorDescription,
119
+ sentAt: pending.sentAt,
120
+ receivedAt: Date.now(),
121
+ latency: Date.now() - pending.sentAt,
122
+ },
123
+ };
124
+
125
+ node.send(responseMsg);
126
+
127
+ } catch (err) {
128
+ node.error(`Error processing response: ${err.message}`);
129
+ }
130
+ });
131
+
132
+ node.status({ fill: 'green', shape: 'ring', text: 'ready' });
133
+
134
+ } catch (err) {
135
+ node.error(`Failed to initialize subscriber: ${err.message}`);
136
+ node.status({ fill: 'red', shape: 'ring', text: 'sub error' });
137
+ }
138
+ }
139
+
140
+ // Initialize subscriber when Redis is ready
141
+ if (redis.status === 'ready') {
142
+ initSubscriber();
143
+ } else {
144
+ redis.once('ready', () => {
145
+ initSubscriber();
146
+ });
147
+ }
148
+
40
149
  /**
41
150
  * Handle incoming messages
42
151
  */
@@ -65,6 +174,22 @@ module.exports = function (RED) {
65
174
  // Build OCPP CALL message
66
175
  const callMessage = buildCall(messageId, action, payload);
67
176
 
177
+ // Store pending command for response matching
178
+ const pendingKey = Keys.pending(tenant, deviceId);
179
+ const sentAt = Date.now();
180
+
181
+ // Store command info in Redis for response matching
182
+ // Include our serverId so WS Server knows where to send response
183
+ await redis.hset(pendingKey,
184
+ messageId, JSON.stringify({
185
+ action,
186
+ sentAt,
187
+ timeout: node.timeout,
188
+ serverId: nodeServerId, // This tells WS Server where to send response
189
+ })
190
+ );
191
+ await redis.expire(pendingKey, Math.ceil(node.timeout / 1000) + 10);
192
+
68
193
  // Send to device
69
194
  const outboundKey = Keys.outbound(tenant, deviceId);
70
195
  await redis.xadd(
@@ -73,10 +198,11 @@ module.exports = function (RED) {
73
198
  '*',
74
199
  'id', messageId,
75
200
  'payload', callMessage,
76
- 'timestamp', Date.now().toString()
201
+ 'timestamp', sentAt.toString(),
202
+ 'serverId', nodeServerId // Include serverId in outbound message
77
203
  );
78
204
 
79
- node.log(`Sent command ${action} to ${tenant}:${deviceId} (${messageId})`);
205
+ node.log(`Sent ${action} to ${tenant}:${deviceId} (${messageId})`);
80
206
 
81
207
  // Update status
82
208
  node.status({
@@ -85,82 +211,52 @@ module.exports = function (RED) {
85
211
  text: `${action} → ${tenant}:${deviceId}`,
86
212
  });
87
213
 
88
- // Prepare output message
89
- const outMsg = {
90
- ...msg,
91
- ocpp: {
92
- ...msg.ocpp,
93
- messageId,
94
- action,
95
- tenant,
96
- deviceId,
97
- identity: `${tenant}:${deviceId}`,
98
- sentAt: Date.now(),
99
- },
100
- };
101
-
102
- if (node.waitForResponse) {
103
- // Store pending command for response matching
104
- const pendingKey = Keys.pending(tenant, deviceId);
105
-
106
- // Store command info in Redis for response matching
107
- await redis.hset(pendingKey,
108
- messageId, JSON.stringify({
109
- action,
110
- sentAt: Date.now(),
111
- timeout: node.timeout,
112
- })
113
- );
114
- await redis.expire(pendingKey, Math.ceil(node.timeout / 1000) + 10);
115
-
116
- // Set up timeout
117
- const timeoutId = setTimeout(async () => {
118
- pendingCommands.delete(messageId);
119
-
120
- // Remove from Redis
121
- await redis.hdel(pendingKey, messageId).catch(() => {});
122
-
123
- node.status({
124
- fill: 'yellow',
125
- shape: 'dot',
126
- text: `timeout: ${action}`,
127
- });
128
-
129
- // Send timeout error on second output
130
- const timeoutMsg = {
131
- ...outMsg,
132
- ocpp: {
133
- ...outMsg.ocpp,
134
- error: true,
135
- errorCode: 'Timeout',
136
- errorDescription: `Command ${action} timed out after ${node.timeout}ms`,
137
- },
138
- };
214
+ // Set up timeout
215
+ const timeoutId = setTimeout(async () => {
216
+ pendingCommands.delete(messageId);
139
217
 
140
- node.send([null, timeoutMsg]);
141
- }, node.timeout);
218
+ // Remove from Redis
219
+ await redis.hdel(pendingKey, messageId).catch(() => {});
142
220
 
143
- // Store for cleanup
144
- pendingCommands.set(messageId, {
145
- timeoutId,
146
- msg: outMsg,
147
- action,
221
+ node.status({
222
+ fill: 'yellow',
223
+ shape: 'dot',
224
+ text: `timeout: ${action}`,
148
225
  });
149
226
 
150
- // Send on first output (command sent)
151
- if (send) {
152
- send([outMsg, null]);
153
- } else {
154
- node.send([outMsg, null]);
155
- }
156
- } else {
157
- // Don't wait for response, just send confirmation
227
+ // Send timeout error
228
+ const timeoutMsg = {
229
+ payload: null,
230
+ ocpp: {
231
+ messageId,
232
+ action,
233
+ tenant,
234
+ deviceId,
235
+ identity: `${tenant}:${deviceId}`,
236
+ response: true,
237
+ error: true,
238
+ errorCode: 'Timeout',
239
+ errorDescription: `Command ${action} timed out after ${node.timeout}ms`,
240
+ sentAt,
241
+ receivedAt: Date.now(),
242
+ },
243
+ };
244
+
158
245
  if (send) {
159
- send(outMsg);
246
+ send(timeoutMsg);
160
247
  } else {
161
- node.send(outMsg);
248
+ node.send(timeoutMsg);
162
249
  }
163
- }
250
+ }, node.timeout);
251
+
252
+ // Store for response matching
253
+ pendingCommands.set(messageId, {
254
+ timeoutId,
255
+ action,
256
+ tenant,
257
+ deviceId,
258
+ sentAt,
259
+ });
164
260
 
165
261
  if (done) {
166
262
  done();
@@ -176,63 +272,27 @@ module.exports = function (RED) {
176
272
  }
177
273
  });
178
274
 
179
- /**
180
- * Handle response messages (can be connected from another ocpp-in node filtering for responses)
181
- */
182
- node.handleResponse = function (messageId, response, isError) {
183
- const pending = pendingCommands.get(messageId);
184
- if (!pending) {
185
- return false; // Not our command
186
- }
187
-
188
- // Clear timeout
189
- clearTimeout(pending.timeoutId);
190
- pendingCommands.delete(messageId);
191
-
192
- // Update status
193
- node.status({
194
- fill: isError ? 'red' : 'green',
195
- shape: 'dot',
196
- text: `${pending.action}: ${isError ? 'error' : 'response'}`,
197
- });
198
-
199
- // Send response on second output
200
- const responseMsg = {
201
- ...pending.msg,
202
- payload: response,
203
- ocpp: {
204
- ...pending.msg.ocpp,
205
- response: true,
206
- error: isError,
207
- receivedAt: Date.now(),
208
- },
209
- };
210
-
211
- node.send([null, responseMsg]);
212
- return true;
213
- };
214
-
215
275
  // Initial status
216
- node.status({ fill: 'grey', shape: 'ring', text: 'ready' });
217
-
218
- // Update status when Redis is ready
219
- if (redis) {
220
- redis.once('ready', () => {
221
- node.status({ fill: 'green', shape: 'ring', text: 'connected' });
222
- });
223
-
224
- if (redis.status === 'ready') {
225
- node.status({ fill: 'green', shape: 'ring', text: 'connected' });
226
- }
227
- }
276
+ node.status({ fill: 'grey', shape: 'ring', text: 'initializing' });
228
277
 
229
278
  // Cleanup on close
230
- node.on('close', (done) => {
279
+ node.on('close', async (done) => {
231
280
  // Clear all pending timeouts
232
281
  for (const [, pending] of pendingCommands) {
233
282
  clearTimeout(pending.timeoutId);
234
283
  }
235
284
  pendingCommands.clear();
285
+
286
+ // Cleanup subscriber
287
+ if (subRedis) {
288
+ try {
289
+ await subRedis.unsubscribe();
290
+ await subRedis.quit();
291
+ } catch (e) {
292
+ // Ignore cleanup errors
293
+ }
294
+ }
295
+
236
296
  done();
237
297
  });
238
298
  }
@@ -10,6 +10,7 @@
10
10
  autoAck: { value: true },
11
11
  normalizePayload: { value: true },
12
12
  filterAction: { value: '' },
13
+ includeResponses: { value: false },
13
14
  },
14
15
  inputs: 0,
15
16
  outputs: 1,
@@ -58,6 +59,11 @@
58
59
  <label for="node-input-filterAction"><i class="fa fa-filter"></i> Filter Action</label>
59
60
  <input type="text" id="node-input-filterAction" placeholder="(all actions)">
60
61
  </div>
62
+ <div class="form-row">
63
+ <label for="node-input-includeResponses"><i class="fa fa-reply"></i> Include Responses</label>
64
+ <input type="checkbox" id="node-input-includeResponses" style="width: auto; margin-left: 0;">
65
+ <span style="margin-left: 10px;">Include CallResult/CallError (normally handled by ocpp-command)</span>
66
+ </div>
61
67
  </script>
62
68
 
63
69
  <script type="text/html" data-help-name="ocpp-in">
@@ -108,12 +114,21 @@
108
114
  <dt>Filter Action <span class="property-type">string</span></dt>
109
115
  <dd>Optional. Only output messages matching this filter.
110
116
  Leave empty to receive all messages.</dd>
117
+
118
+ <dt>Include Responses <span class="property-type">boolean</span></dt>
119
+ <dd>If enabled, includes CallResult (3) and CallError (4) messages.
120
+ By default, these are skipped because <code>ocpp-command</code> nodes handle responses via Pub/Sub.
121
+ Enable this only if you need to monitor or log all response traffic.</dd>
111
122
  </dl>
112
123
 
113
124
  <h3>Details</h3>
114
125
  <p>This node reads from the <code>ws:inbound</code> Redis Stream using consumer groups,
115
126
  allowing multiple Node-RED instances to share the message load.</p>
116
127
 
128
+ <p><strong>Note:</strong> By default, this node only outputs CALL messages (requests from charging stations).
129
+ Response messages (CallResult/CallError) are handled by <code>ocpp-command</code> nodes via Redis Pub/Sub.
130
+ Enable "Include Responses" only if you need to monitor or log all traffic.</p>
131
+
117
132
  <h3>Filter Options</h3>
118
133
  <p>The Filter Action field supports these values:</p>
119
134
  <ul>
package/nodes/ocpp-in.js CHANGED
@@ -24,6 +24,7 @@ module.exports = function (RED) {
24
24
  node.autoAck = config.autoAck !== false; // Default true
25
25
  node.normalizePayload = config.normalizePayload !== false; // Default true
26
26
  node.filterAction = config.filterAction || ''; // Optional action filter
27
+ node.includeResponses = config.includeResponses === true; // Default false - skip CallResult/CallError
27
28
 
28
29
  // State
29
30
  let running = false;
@@ -111,6 +112,21 @@ module.exports = function (RED) {
111
112
  let msg = parseStreamMessage(data);
112
113
  msg.streamId = streamId;
113
114
 
115
+ // Skip CallResult (3) and CallError (4) messages by default
116
+ // These responses should be handled by ocpp-command nodes via Pub/Sub
117
+ // Only allow them through if explicitly configured with includeResponses option
118
+ // Check both: ocppMessageType (numeric: 3, 4) and messageType (string: 'result', 'error')
119
+ const isResponse = msg.ocppMessageType === 3 || msg.ocppMessageType === 4 ||
120
+ msg.messageType === 'result' || msg.messageType === 'error';
121
+
122
+ if (isResponse && !node.includeResponses) {
123
+ // Auto-ack response messages silently
124
+ if (node.autoAck) {
125
+ await redis.xack(streamKey, consumerGroup, streamId);
126
+ }
127
+ return;
128
+ }
129
+
114
130
  // Apply action filter if configured
115
131
  // Special filter values:
116
132
  // - "_CallResult" - only CALLRESULT responses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blokcert/node-red-contrib-ocpp",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Node-RED nodes for OCPP (Open Charge Point Protocol) message handling via Redis Streams",
5
5
  "keywords": [
6
6
  "node-red",
@@ -22,7 +22,6 @@
22
22
  "ocpp-in": "nodes/ocpp-in.js",
23
23
  "ocpp-out": "nodes/ocpp-out.js",
24
24
  "ocpp-command": "nodes/ocpp-command.js",
25
- "ocpp-response": "nodes/ocpp-response.js",
26
25
  "ocpp-config": "nodes/ocpp-config.js"
27
26
  }
28
27
  },