ocpp-ws-io 2.1.15 → 2.2.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/README.md +242 -170
- package/dist/adapters/redis.d.mts +2 -2
- package/dist/adapters/redis.d.ts +2 -2
- package/dist/browser.js +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/{index-C0mn42-8.d.mts → index-B98n5Et3.d.mts} +1 -1
- package/dist/{index-s9f97CmV.d.ts → index-xx7uU8pY.d.ts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.mjs +5 -5
- package/dist/plugins.d.mts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/{types-BZXEmDQ1.d.mts → types-BunMs45p.d.mts} +37 -1
- package/dist/{types-BZXEmDQ1.d.ts → types-BunMs45p.d.ts} +37 -1
- package/package.json +113 -109
package/README.md
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/rohittiwari-dev/ocpp-ws-io/main/assets/banner.svg" alt="ocpp-ws-io" width="420" />
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rohittiwari-dev/ocpp-ws-io/main/assets/banner.svg" alt="ocpp-ws-io - OCPP RPC WebSocket Client and Server" width="420" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# 🔌 OCPP RPC WebSocket Client & Server Library
|
|
6
|
+
|
|
7
|
+
The **production-ready OCPP RPC implementation** for building type-safe EV charging platforms, CSMS backends, charge points, and simulators. Supports OCPP 1.6J, 2.0.1, and 2.1 with full protocol compliance, all security profiles, Redis clustering, strict JSON schema validation, and structured logging.
|
|
8
|
+
|
|
9
|
+
> **Built with TypeScript** for EV charging, CSMS, CPO/EMSP platforms. Zero-dependency WebSocket RPC framework supporting OCPP versions 1.6, 2.0.1, and 2.1 with optional JSON schema validation, bidirectional messaging, all security profiles (0–3), Redis pub/sub clustering, and structured logging powered by [voltlog-io](https://ocpp-ws-io.rohittiwari.me/docs/voltlog-io).
|
|
6
10
|
|
|
7
11
|
[](https://www.npmjs.com/package/ocpp-ws-io)
|
|
8
12
|
[](https://github.com/rohittiwari-dev/ocpp-ws-io/blob/main/LICENSE)
|
|
9
13
|
|
|
10
|
-
## 📚 Documentation
|
|
14
|
+
## 📚 Full Documentation & API Reference
|
|
11
15
|
|
|
12
16
|
For full API reference, advanced usage, and guides, visit the **[Official Documentation](https://ocpp-ws-io.rohittiwari.me)**.
|
|
13
17
|
|
|
14
|
-
## ✨ Features
|
|
18
|
+
## ✨ Key Features for OCPP RPC WebSocket Development
|
|
19
|
+
|
|
20
|
+
The library provides enterprise-grade features for building OCPP-compliant EV charging platforms:
|
|
15
21
|
|
|
16
|
-
- ⚡ **Full OCPP-J RPC** —
|
|
17
|
-
- 🔒 **Security Profiles
|
|
18
|
-
- 🎯 **Type-Safe** — Auto-generated
|
|
19
|
-
- 📐 **Strict
|
|
20
|
-
- 📡 **Clustering** — Redis adapter
|
|
21
|
-
- 🌐 **Browser Client** — Zero-dependency client for simulators
|
|
22
|
-
- ⚡ **CLI
|
|
22
|
+
- ⚡ **Full OCPP-J RPC Support** — Complete JSON-RPC 2.0 OCPP message framing for 1.6J, 2.0.1, and 2.1
|
|
23
|
+
- 🔒 **All Security Profiles** — Supports Plain WS, Basic Authentication, TLS/SSL, and mutual TLS (mTLS)
|
|
24
|
+
- 🎯 **Type-Safe TypeScript** — Auto-generated, fully-typed interfaces for all OCPP versions and methods
|
|
25
|
+
- 📐 **Strict Schema Validation** — Optional JSON schema validation with AJV for data integrity
|
|
26
|
+
- 📡 **Redis Clustering & Pub/Sub** — Built-in Redis adapter for distributed CSMS deployments
|
|
27
|
+
- 🌐 **Browser-Ready Client** — Zero-dependency WebSocket client for EV charging simulators
|
|
28
|
+
- ⚡ **CLI Toolkit** — `ocpp-ws-cli` for type generation, load testing, fuzzing, and virtual charge point simulation
|
|
29
|
+
- 🛡️ **DDoS & Rate Limiting** — Token bucket rate limiting and adaptive throttling for charging station protection
|
|
23
30
|
|
|
24
31
|
## 📦 Installation
|
|
25
32
|
|
|
@@ -27,68 +34,72 @@ For full API reference, advanced usage, and guides, visit the **[Official Docume
|
|
|
27
34
|
npm install ocpp-ws-io
|
|
28
35
|
```
|
|
29
36
|
|
|
30
|
-
## 🚀 Quick Start
|
|
37
|
+
## 🚀 Quick Start: OCPP RPC Client & Server Examples
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Get up and running with OCPP WebSocket RPC in minutes. Choose your role: EV charging station (client) or CSMS backend (server).
|
|
40
|
+
|
|
41
|
+
### OCPP RPC Client: Charge Point / Charging Station Simulator
|
|
33
42
|
|
|
34
43
|
```typescript
|
|
35
44
|
import { OCPPClient } from "ocpp-ws-io";
|
|
36
45
|
|
|
37
46
|
const client = new OCPPClient({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
endpoint: "ws://localhost:3000",
|
|
48
|
+
identity: "CP001",
|
|
49
|
+
protocols: ["ocpp1.6"],
|
|
41
50
|
});
|
|
42
51
|
|
|
43
52
|
await client.connect();
|
|
44
53
|
|
|
45
54
|
// Fully typed call
|
|
46
55
|
const response = await client.call("ocpp1.6", "BootNotification", {
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
chargePointVendor: "VendorX",
|
|
57
|
+
chargePointModel: "ModelY",
|
|
49
58
|
});
|
|
50
59
|
|
|
51
60
|
console.log("Status:", response.status);
|
|
52
61
|
```
|
|
53
62
|
|
|
54
|
-
### Server
|
|
63
|
+
### OCPP RPC Server: Central System / CSMS Backend
|
|
55
64
|
|
|
56
65
|
```typescript
|
|
57
66
|
import { OCPPServer } from "ocpp-ws-io";
|
|
58
67
|
|
|
59
68
|
const server = new OCPPServer({
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
protocols: ["ocpp1.6", "ocpp2.0.1"],
|
|
70
|
+
logging: { prettify: true, exchangeLog: true, level: "info" },
|
|
62
71
|
});
|
|
63
72
|
|
|
64
73
|
// Optional: Add authentication ringfence
|
|
65
74
|
server.auth((ctx) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
console.log(
|
|
76
|
+
`Connection from ${ctx.handshake.identity} at path ${ctx.handshake.pathname}`,
|
|
77
|
+
);
|
|
78
|
+
ctx.accept({ session: { authorized: true } });
|
|
70
79
|
});
|
|
71
80
|
|
|
72
81
|
server.on("client", (client) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
console.log(`${client.identity} connected`);
|
|
83
|
+
|
|
84
|
+
// Version-aware handler
|
|
85
|
+
client.handle("ocpp1.6", "BootNotification", ({ params }) => {
|
|
86
|
+
console.log("Boot from:", params.chargePointVendor);
|
|
87
|
+
return {
|
|
88
|
+
status: "Accepted",
|
|
89
|
+
currentTime: new Date().toISOString(),
|
|
90
|
+
interval: 300,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
84
93
|
});
|
|
85
94
|
|
|
86
95
|
await server.listen(3000);
|
|
87
96
|
```
|
|
88
97
|
|
|
89
|
-
## ⚙️ Configuration
|
|
98
|
+
## ⚙️ OCPP RPC Configuration & Options
|
|
99
|
+
|
|
100
|
+
Configure your OCPP WebSocket RPC client and server with extensive options for protocol negotiation, security, validation, and performance.
|
|
90
101
|
|
|
91
|
-
###
|
|
102
|
+
### OCPP WebSocket RPC Client Configuration
|
|
92
103
|
|
|
93
104
|
| Option | Type | Default | Description |
|
|
94
105
|
| ------------------- | --------------------- | ---------- | --------------------------------------- |
|
|
@@ -110,7 +121,7 @@ When invoking `client.call()` you can safely decouple dynamically generated mess
|
|
|
110
121
|
await client.call("ocpp1.6", "BootNotification", { ... }, { idempotencyKey: "unique-boot-123" });
|
|
111
122
|
```
|
|
112
123
|
|
|
113
|
-
###
|
|
124
|
+
### OCPP WebSocket RPC Server Configuration
|
|
114
125
|
|
|
115
126
|
| Option | Type | Default | Description |
|
|
116
127
|
| -------------------- | ------------------ | --------- | ------------------------------------------ |
|
|
@@ -123,28 +134,86 @@ await client.call("ocpp1.6", "BootNotification", { ... }, { idempotencyKey: "uni
|
|
|
123
134
|
| `rateLimit` | `RateLimitOptions` | — | Token bucket socket & method rate-limiter |
|
|
124
135
|
| `healthEndpoint` | `boolean` | `false` | Expose HTTP `/health` and `/metrics` APIs |
|
|
125
136
|
|
|
126
|
-
##
|
|
137
|
+
## � Message Observability & Event Handling (v3.0.0+)
|
|
138
|
+
|
|
139
|
+
Unified message event API with direction tracking and rich contextual metadata for complete visibility into OCPP message flow.
|
|
140
|
+
|
|
141
|
+
### Observe All Messages with Direction & Latency
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const client = new OCPPClient({ ... });
|
|
145
|
+
|
|
146
|
+
// Single unified "message" event with direction, context, and metadata
|
|
147
|
+
client.on("message", ({ message, direction, ctx }) => {
|
|
148
|
+
console.log({
|
|
149
|
+
direction, // "IN" (from server) | "OUT" (to server)
|
|
150
|
+
method: ctx.method, // "BootNotification", "MeterValues", etc.
|
|
151
|
+
type: ctx.type, // "incoming_call", "outgoing_call", etc.
|
|
152
|
+
messageId: ctx.messageId, // Unique message ID
|
|
153
|
+
timestamp: ctx.timestamp, // ISO 8601 timestamp
|
|
154
|
+
latencyMs: ctx.latencyMs, // Response latency (if available)
|
|
155
|
+
protocol: ctx.protocol, // "ocpp1.6", "ocpp2.0.1", etc.
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Server-side observation (for each connected client)
|
|
160
|
+
const server = new OCPPServer({ ... });
|
|
161
|
+
server.on("client", (client) => {
|
|
162
|
+
client.on("message", ({ direction, ctx }) => {
|
|
163
|
+
console.log(`[${client.identity}] ${direction} ${ctx.method} (${ctx.latencyMs}ms)`);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Message Event Payload Structure
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
interface MessageEventPayload {
|
|
172
|
+
message: OCPPMessage; // Original OCPP message tuple
|
|
173
|
+
direction: "IN" | "OUT"; // Message direction
|
|
174
|
+
ctx: MessageEventContext; // Enriched context with metadata
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface MessageEventContext {
|
|
178
|
+
type:
|
|
179
|
+
| "incoming_call"
|
|
180
|
+
| "outgoing_call"
|
|
181
|
+
| "incoming_result"
|
|
182
|
+
| "incoming_error";
|
|
183
|
+
messageId: string;
|
|
184
|
+
method?: string;
|
|
185
|
+
params?: unknown;
|
|
186
|
+
payload?: unknown;
|
|
187
|
+
timestamp: string; // ISO 8601
|
|
188
|
+
latencyMs?: number; // Response latency
|
|
189
|
+
protocol?: string;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## �🛠️ Advanced OCPP RPC Server Configuration & WebSocket Handshake
|
|
127
194
|
|
|
128
|
-
|
|
195
|
+
Build sophisticated OCPP server implementations with fine-tuned WebSocket upgrade handling, authentication, and message routing.
|
|
196
|
+
|
|
197
|
+
### OCPP WebSocket Handshake, Upgrade & Authentication
|
|
129
198
|
|
|
130
199
|
You can fine-tune how the server handles the WebSocket upgrade process, including timeouts for custom auth logic.
|
|
131
200
|
|
|
132
201
|
```typescript
|
|
133
202
|
const server = new OCPPServer({
|
|
134
|
-
|
|
135
|
-
|
|
203
|
+
// ...
|
|
204
|
+
handshakeTimeoutMs: 5000, // Timeout if auth callback takes too long (default 30s)
|
|
136
205
|
});
|
|
137
206
|
|
|
138
207
|
server.on("upgradeAborted", ({ identity, reason, socket }) => {
|
|
139
|
-
|
|
208
|
+
console.warn(`Handshake aborted for ${identity}: ${reason}`);
|
|
140
209
|
});
|
|
141
210
|
|
|
142
211
|
server.on("upgradeError", ({ error, socket }) => {
|
|
143
|
-
|
|
212
|
+
console.error("Upgrade failed:", error);
|
|
144
213
|
});
|
|
145
214
|
```
|
|
146
215
|
|
|
147
|
-
### Server & Router Execution Flow
|
|
216
|
+
### OCPP Server & Router Execution Flow (Connection & Message Phases)
|
|
148
217
|
|
|
149
218
|
The `OCPPServer` and its internal `OCPPRouter` handle connections and messages in a strict, two-phase execution hierarchy:
|
|
150
219
|
|
|
@@ -160,7 +229,7 @@ Executes before the WebSocket connection is officially accepted.
|
|
|
160
229
|
|
|
161
230
|
Executes after the connection is accepted and messages start flowing. 4. **Message Middleware (`client.use` / `server.use`)**: Intercepts every outgoing/incoming message for logging, schema validation, or metric tracking. 5. **Message Handlers (`client.handle` / `server.handle`)**: The **final piece of business logic** where the system reacts to a specific OCPP action (e.g., `BootNotification`).
|
|
162
231
|
|
|
163
|
-
### NOREPLY Suppression
|
|
232
|
+
### NOREPLY Suppression in OCPP RPC Message Handlers
|
|
164
233
|
|
|
165
234
|
Return `NOREPLY` directly from any message handler to safely suppress the automatic outbound `CALLRESULT` without violating strict internal tracking specifications.
|
|
166
235
|
|
|
@@ -168,53 +237,53 @@ Return `NOREPLY` directly from any message handler to safely suppress the automa
|
|
|
168
237
|
import { NOREPLY } from "ocpp-ws-io";
|
|
169
238
|
|
|
170
239
|
client.handle("StatusNotification", ({ params }) => {
|
|
171
|
-
|
|
240
|
+
return NOREPLY;
|
|
172
241
|
});
|
|
173
242
|
```
|
|
174
243
|
|
|
175
|
-
## 📝 Logging
|
|
244
|
+
## 📝 Structured Logging for OCPP RPC WebSocket Applications
|
|
176
245
|
|
|
177
|
-
`ocpp-ws-io`
|
|
246
|
+
`ocpp-ws-io` provides **battle-tested structured JSON logging** optimized for high-throughput EV charging and OCPP WebSocket environments, powered by [voltlog-io](https://ocpp-ws-io.rohittiwari.me/docs/voltlog-io).
|
|
178
247
|
|
|
179
|
-
### Default Behavior
|
|
248
|
+
### Default JSON Logging Behavior
|
|
180
249
|
|
|
181
250
|
By default (`logging: true`), logs are output as structured JSON to `stdout`.
|
|
182
251
|
|
|
183
252
|
```json
|
|
184
253
|
{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
254
|
+
"level": 30,
|
|
255
|
+
"time": 1678900000000,
|
|
256
|
+
"msg": "Client connected",
|
|
257
|
+
"component": "OCPPServer",
|
|
258
|
+
"identity": "CP001"
|
|
190
259
|
}
|
|
191
260
|
```
|
|
192
261
|
|
|
193
|
-
### Pretty Printing & Exchange Logs
|
|
262
|
+
### Pretty Printing & OCPP Message Exchange Logs (Development Mode)
|
|
194
263
|
|
|
195
264
|
Enable `prettify` for development to see colored output with icons.
|
|
196
265
|
Enable `exchangeLog` to log all OCPP messages with direction (`IN`/`OUT`) and metadata.
|
|
197
266
|
|
|
198
267
|
```typescript
|
|
199
268
|
const client = new OCPPClient({
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
269
|
+
// ...
|
|
270
|
+
logging: {
|
|
271
|
+
enabled: true,
|
|
272
|
+
prettify: true, // 🌈 Colors & icons
|
|
273
|
+
exchangeLog: true, // ⚡ Log all OCPP messages
|
|
274
|
+
level: "debug", // Default: 'info'
|
|
275
|
+
},
|
|
207
276
|
});
|
|
208
277
|
```
|
|
209
278
|
|
|
210
279
|
**Output:**
|
|
211
280
|
|
|
212
|
-
```
|
|
281
|
+
```sh
|
|
213
282
|
⚡ CP-101 → BootNotification [OUT]
|
|
214
283
|
✅ CP-101 ← BootNotification [IN] { latencyMs: 45 }
|
|
215
284
|
```
|
|
216
285
|
|
|
217
|
-
### Custom Logger
|
|
286
|
+
### Custom Logger Integration (Pino, Winston, etc.)
|
|
218
287
|
|
|
219
288
|
You can bring your own logger (Pino, Winston, etc.) by implementing `LoggerLike`:
|
|
220
289
|
|
|
@@ -222,31 +291,33 @@ You can bring your own logger (Pino, Winston, etc.) by implementing `LoggerLike`
|
|
|
222
291
|
import pino from "pino";
|
|
223
292
|
|
|
224
293
|
const client = new OCPPClient({
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
294
|
+
logging: {
|
|
295
|
+
handler: pino(), // Use existing logger instance
|
|
296
|
+
},
|
|
228
297
|
});
|
|
229
298
|
```
|
|
230
299
|
|
|
231
|
-
## 🛡️ Safety &
|
|
300
|
+
## 🛡️ OCPP RPC Safety, Reliability & Error Handling
|
|
301
|
+
|
|
302
|
+
Build fault-tolerant EV charging applications with safe call patterns, automatic error handling, and idempotency support.
|
|
232
303
|
|
|
233
|
-
### Safe Calls (`safeCall`)
|
|
304
|
+
### Safe RPC Calls (`safeCall`) - Error Handling Without Try/Catch
|
|
234
305
|
|
|
235
306
|
Perform RPC calls without `try/catch` blocks. Returns the response data on success, or `undefined` on failure while automatically logging the error. You can also pass per-call config options like timeouts.
|
|
236
307
|
|
|
237
308
|
```typescript
|
|
238
309
|
const result = await client.safeCall(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
310
|
+
"ocpp1.6",
|
|
311
|
+
"Heartbeat",
|
|
312
|
+
{},
|
|
313
|
+
{
|
|
314
|
+
timeoutMs: 15000, // Finely control the timeout specifically for this request
|
|
315
|
+
},
|
|
245
316
|
);
|
|
246
317
|
|
|
247
318
|
if (result) {
|
|
248
|
-
|
|
249
|
-
|
|
319
|
+
// Checked for undefined
|
|
320
|
+
console.log("Heartbeat accepted:", result.currentTime);
|
|
250
321
|
}
|
|
251
322
|
```
|
|
252
323
|
|
|
@@ -262,16 +333,16 @@ Throws an error if the client responds with a `CALLERROR` or if the timeout is r
|
|
|
262
333
|
|
|
263
334
|
```typescript
|
|
264
335
|
try {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
336
|
+
const result = await server.sendToClient(
|
|
337
|
+
"CP001",
|
|
338
|
+
"ocpp1.6",
|
|
339
|
+
"GetConfiguration",
|
|
340
|
+
{ key: ["ClockAlignedDataInterval"] },
|
|
341
|
+
{ timeoutMs: 10000 },
|
|
342
|
+
);
|
|
343
|
+
console.log("Configuration:", result);
|
|
273
344
|
} catch (error) {
|
|
274
|
-
|
|
345
|
+
console.error("Failed to get configuration:", error);
|
|
275
346
|
}
|
|
276
347
|
```
|
|
277
348
|
|
|
@@ -281,15 +352,15 @@ Returns the response on success, or `undefined` on error, automatically logging
|
|
|
281
352
|
|
|
282
353
|
```typescript
|
|
283
354
|
const result = await server.safeSendToClient(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
355
|
+
"CP001",
|
|
356
|
+
"ocpp1.6",
|
|
357
|
+
"GetConfiguration",
|
|
358
|
+
{ key: ["ClockAlignedDataInterval"] },
|
|
359
|
+
{ timeoutMs: 10000 },
|
|
289
360
|
);
|
|
290
361
|
|
|
291
362
|
if (result) {
|
|
292
|
-
|
|
363
|
+
console.log("Configuration:", result);
|
|
293
364
|
}
|
|
294
365
|
```
|
|
295
366
|
|
|
@@ -299,15 +370,15 @@ For intercepting HTTP WebSocket Upgrade requests before they become an OCPP Clie
|
|
|
299
370
|
|
|
300
371
|
```typescript
|
|
301
372
|
const rateLimiter = defineMiddleware(async (ctx) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
373
|
+
const ip = ctx.handshake.remoteAddress;
|
|
374
|
+
if (isRateLimited(ip)) {
|
|
375
|
+
// Instantly aborts the WebSocket connection with an HTTP 429 status
|
|
376
|
+
ctx.reject(429, "Too Many Requests");
|
|
377
|
+
} else {
|
|
378
|
+
// Or proceed down the execution chain. You can optionally pass an object
|
|
379
|
+
// to next(), which will automatically be shallow-merged into `ctx.state`.
|
|
380
|
+
await ctx.next({ rateLimitRemaining: 99 });
|
|
381
|
+
}
|
|
311
382
|
});
|
|
312
383
|
|
|
313
384
|
server.use(rateLimiter);
|
|
@@ -321,101 +392,102 @@ The Rate Limiter safely throttles connections without dropping the connection im
|
|
|
321
392
|
|
|
322
393
|
```typescript
|
|
323
394
|
const server = new OCPPServer({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
},
|
|
395
|
+
protocols: ["ocpp1.6"],
|
|
396
|
+
rateLimit: {
|
|
397
|
+
limit: 100, // Global limit
|
|
398
|
+
windowMs: 60000, // per 60 seconds
|
|
399
|
+
onLimitExceeded: "disconnect", // or "ignore", or a Custom Callback
|
|
400
|
+
methods: {
|
|
401
|
+
MeterValues: { limit: 10, windowMs: 60000 },
|
|
402
|
+
Heartbeat: { limit: 2, windowMs: 60000 },
|
|
333
403
|
},
|
|
404
|
+
},
|
|
334
405
|
});
|
|
335
406
|
```
|
|
336
407
|
|
|
337
|
-
## 🧩 Middleware
|
|
408
|
+
## 🧩 OCPP Message Middleware & Interceptor Pattern
|
|
338
409
|
|
|
339
|
-
Intercept and modify OCPP messages
|
|
410
|
+
Intercept, validate, and modify OCPP RPC messages across the middleware stack for logging, schema validation, and metric tracking.
|
|
340
411
|
|
|
341
412
|
```typescript
|
|
342
413
|
// Add logging middleware (enabled by default)
|
|
343
414
|
client.use(async (ctx, next) => {
|
|
344
|
-
|
|
345
|
-
|
|
415
|
+
console.log(`Processing ${ctx.method}`);
|
|
416
|
+
await next();
|
|
346
417
|
});
|
|
347
418
|
```
|
|
348
419
|
|
|
349
|
-
## 📡 Clustering
|
|
420
|
+
## 📡 Redis Clustering & Distributed CSMS Architecture
|
|
350
421
|
|
|
351
|
-
Scale
|
|
422
|
+
Scale OCPP RPC servers across multiple nodes with Redis pub/sub, Streams, and presence tracking for enterprise CSMS deployments.
|
|
352
423
|
|
|
353
|
-
|
|
424
|
+
### Set Up Redis Clustering
|
|
354
425
|
|
|
355
|
-
|
|
356
|
-
npm install ioredis
|
|
357
|
-
```
|
|
426
|
+
1. **Install Redis Adapter**:
|
|
358
427
|
|
|
359
|
-
|
|
428
|
+
```bash
|
|
429
|
+
npm install ioredis
|
|
430
|
+
```
|
|
360
431
|
|
|
361
|
-
|
|
362
|
-
import { OCPPServer } from "ocpp-ws-io";
|
|
363
|
-
import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
|
|
364
|
-
import Redis from "ioredis";
|
|
432
|
+
2. **Configure OCPP Server with Redis Adapter**:
|
|
365
433
|
|
|
366
|
-
|
|
367
|
-
|
|
434
|
+
```typescript
|
|
435
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
436
|
+
import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
|
|
437
|
+
import Redis from "ioredis";
|
|
438
|
+
|
|
439
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
440
|
+
const server = new OCPPServer({
|
|
368
441
|
protocols: ["ocpp1.6"],
|
|
369
|
-
|
|
442
|
+
});
|
|
370
443
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
444
|
+
// Uses Redis Streams for clustering reliability
|
|
445
|
+
await server.setAdapter(new RedisAdapter(redis));
|
|
446
|
+
```
|
|
374
447
|
|
|
375
|
-
**Features:**
|
|
448
|
+
**OCPP Redis Clustering Features:**
|
|
376
449
|
|
|
377
|
-
- **Unicast Routing**: Send messages to any
|
|
378
|
-
- **Presence**:
|
|
379
|
-
- **
|
|
380
|
-
- **Batch
|
|
450
|
+
- **Unicast Routing**: Send OCPP RPC messages to any charge point on any cluster node.
|
|
451
|
+
- **Presence Tracking**: Real-time discovery of connected clients across the cluster.
|
|
452
|
+
- **Durability**: Redis Streams for guaranteed message delivery.
|
|
453
|
+
- **Batch Operations**: Use `server.broadcastBatch` for efficient multi-node requests.
|
|
381
454
|
|
|
382
|
-
### Custom Adapters (
|
|
455
|
+
### Custom Clustering Adapters (RabbitMQ, Kafka, PubSub)
|
|
383
456
|
|
|
384
|
-
|
|
457
|
+
Build custom OCPP clustering solutions beyond Redis by implementing the `EventAdapterInterface`. This enables distributed CSMS architectures with your preferred message broker or database backend.
|
|
385
458
|
|
|
386
459
|
```typescript
|
|
387
460
|
import type { EventAdapterInterface } from "ocpp-ws-io";
|
|
388
461
|
|
|
389
462
|
export class CustomAdapter implements EventAdapterInterface {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
463
|
+
async publish(channel: string, data: unknown): Promise<void> {
|
|
464
|
+
/* ... */
|
|
465
|
+
}
|
|
466
|
+
async subscribe(
|
|
467
|
+
channel: string,
|
|
468
|
+
handler: (data: unknown) => void,
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
/* ... */
|
|
471
|
+
}
|
|
472
|
+
async unsubscribe(channel: string): Promise<void> {
|
|
473
|
+
/* ... */
|
|
474
|
+
}
|
|
475
|
+
async disconnect(): Promise<void> {
|
|
476
|
+
/* ... */
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Optional primitives for advanced routing:
|
|
480
|
+
async setPresence?(
|
|
481
|
+
identity: string,
|
|
482
|
+
nodeId: string,
|
|
483
|
+
ttl: number,
|
|
484
|
+
): Promise<void>;
|
|
485
|
+
async getPresence?(identity: string): Promise<string | null>;
|
|
486
|
+
async removePresence?(identity: string): Promise<void>;
|
|
487
|
+
async getPresenceBatch?(identities: string[]): Promise<(string | null)[]>;
|
|
488
|
+
async publishBatch?(
|
|
489
|
+
messages: { channel: string; data: unknown }[],
|
|
490
|
+
): Promise<void>;
|
|
491
|
+
async metrics?(): Promise<Record<string, unknown>>;
|
|
419
492
|
}
|
|
420
493
|
```
|
|
421
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import '../types-
|
|
2
|
-
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-
|
|
1
|
+
import '../types-BunMs45p.mjs';
|
|
2
|
+
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-B98n5Et3.mjs';
|
|
3
3
|
import 'ws';
|
|
4
4
|
import 'node:https';
|
|
5
5
|
import 'node:http';
|
package/dist/adapters/redis.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import '../types-
|
|
2
|
-
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-
|
|
1
|
+
import '../types-BunMs45p.js';
|
|
2
|
+
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-xx7uU8pY.js';
|
|
3
3
|
import 'ws';
|
|
4
4
|
import 'node:https';
|
|
5
5
|
import 'node:http';
|