ocpp-ws-io 2.1.15 → 2.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/README.md +271 -170
- package/dist/adapters/redis.d.mts +2 -2
- package/dist/adapters/redis.d.ts +2 -2
- package/dist/adapters/redis.js +1 -1
- package/dist/adapters/redis.mjs +1 -1
- package/dist/browser.d.mts +11 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/{index-s9f97CmV.d.ts → index-BefjKqkS.d.ts} +1 -1
- package/dist/{index-C0mn42-8.d.mts → index-Defn9aOS.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -7
- package/dist/index.mjs +7 -7
- package/dist/plugins.d.mts +1017 -26
- package/dist/plugins.d.ts +1017 -26
- package/dist/plugins.js +1 -1
- package/dist/plugins.mjs +1 -1
- package/dist/{types-BZXEmDQ1.d.mts → types-BHIHsj__.d.mts} +144 -3
- package/dist/{types-BZXEmDQ1.d.ts → types-BHIHsj__.d.ts} +144 -3
- package/package.json +17 -13
package/README.md
CHANGED
|
@@ -1,25 +1,61 @@
|
|
|
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:
|
|
21
|
+
|
|
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
|
|
30
|
+
- 🧩 **19 Built-in Plugins** — PII redaction, circuit breakers, deduplication, Kafka streaming, OpenTelemetry, and more
|
|
31
|
+
|
|
32
|
+
## 🧩 Plugin Ecosystem
|
|
33
|
+
|
|
34
|
+
19 built-in plugins organized into a **4-level power hierarchy**. Register from highest to lowest:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Level 4 — Middleware pii-redactor · schema-versioning
|
|
38
|
+
Level 3 — Interceptor message-dedup · replay-buffer
|
|
39
|
+
Level 2 — Lifecycle Controller connection-guard · anomaly · circuit-breaker
|
|
40
|
+
Level 1 — Passive Hook kafka · webhook · metrics · otel · session-log · heartbeat · rate-limit-notifier
|
|
41
|
+
```
|
|
15
42
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
```typescript
|
|
44
|
+
import {
|
|
45
|
+
piiRedactorPlugin, messageDedupPlugin,
|
|
46
|
+
circuitBreakerPlugin, metricsPlugin, otelPlugin,
|
|
47
|
+
} from "ocpp-ws-io/plugins";
|
|
48
|
+
|
|
49
|
+
server.plugin(
|
|
50
|
+
piiRedactorPlugin(), // L4: transform payloads
|
|
51
|
+
messageDedupPlugin({ redis }), // L3: drop duplicates
|
|
52
|
+
circuitBreakerPlugin(), // L2: protect against flapping stations
|
|
53
|
+
metricsPlugin(), // L1: observe
|
|
54
|
+
otelPlugin(), // L1: trace
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> See the full [Plugin Documentation](https://ocpp-ws-io.rohittiwari.me/docs/ocpp-ws-io/plugins) for options, examples, and the `redisStyle` guide for Redis client compatibility.
|
|
23
59
|
|
|
24
60
|
## 📦 Installation
|
|
25
61
|
|
|
@@ -27,68 +63,72 @@ For full API reference, advanced usage, and guides, visit the **[Official Docume
|
|
|
27
63
|
npm install ocpp-ws-io
|
|
28
64
|
```
|
|
29
65
|
|
|
30
|
-
## 🚀 Quick Start
|
|
66
|
+
## 🚀 Quick Start: OCPP RPC Client & Server Examples
|
|
67
|
+
|
|
68
|
+
Get up and running with OCPP WebSocket RPC in minutes. Choose your role: EV charging station (client) or CSMS backend (server).
|
|
31
69
|
|
|
32
|
-
### Client
|
|
70
|
+
### OCPP RPC Client: Charge Point / Charging Station Simulator
|
|
33
71
|
|
|
34
72
|
```typescript
|
|
35
73
|
import { OCPPClient } from "ocpp-ws-io";
|
|
36
74
|
|
|
37
75
|
const client = new OCPPClient({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
76
|
+
endpoint: "ws://localhost:3000",
|
|
77
|
+
identity: "CP001",
|
|
78
|
+
protocols: ["ocpp1.6"],
|
|
41
79
|
});
|
|
42
80
|
|
|
43
81
|
await client.connect();
|
|
44
82
|
|
|
45
83
|
// Fully typed call
|
|
46
84
|
const response = await client.call("ocpp1.6", "BootNotification", {
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
chargePointVendor: "VendorX",
|
|
86
|
+
chargePointModel: "ModelY",
|
|
49
87
|
});
|
|
50
88
|
|
|
51
89
|
console.log("Status:", response.status);
|
|
52
90
|
```
|
|
53
91
|
|
|
54
|
-
### Server
|
|
92
|
+
### OCPP RPC Server: Central System / CSMS Backend
|
|
55
93
|
|
|
56
94
|
```typescript
|
|
57
95
|
import { OCPPServer } from "ocpp-ws-io";
|
|
58
96
|
|
|
59
97
|
const server = new OCPPServer({
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
protocols: ["ocpp1.6", "ocpp2.0.1"],
|
|
99
|
+
logging: { prettify: true, exchangeLog: true, level: "info" },
|
|
62
100
|
});
|
|
63
101
|
|
|
64
102
|
// Optional: Add authentication ringfence
|
|
65
103
|
server.auth((ctx) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
console.log(
|
|
105
|
+
`Connection from ${ctx.handshake.identity} at path ${ctx.handshake.pathname}`,
|
|
106
|
+
);
|
|
107
|
+
ctx.accept({ session: { authorized: true } });
|
|
70
108
|
});
|
|
71
109
|
|
|
72
110
|
server.on("client", (client) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
console.log(`${client.identity} connected`);
|
|
112
|
+
|
|
113
|
+
// Version-aware handler
|
|
114
|
+
client.handle("ocpp1.6", "BootNotification", ({ params }) => {
|
|
115
|
+
console.log("Boot from:", params.chargePointVendor);
|
|
116
|
+
return {
|
|
117
|
+
status: "Accepted",
|
|
118
|
+
currentTime: new Date().toISOString(),
|
|
119
|
+
interval: 300,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
84
122
|
});
|
|
85
123
|
|
|
86
124
|
await server.listen(3000);
|
|
87
125
|
```
|
|
88
126
|
|
|
89
|
-
## ⚙️ Configuration
|
|
127
|
+
## ⚙️ OCPP RPC Configuration & Options
|
|
128
|
+
|
|
129
|
+
Configure your OCPP WebSocket RPC client and server with extensive options for protocol negotiation, security, validation, and performance.
|
|
90
130
|
|
|
91
|
-
###
|
|
131
|
+
### OCPP WebSocket RPC Client Configuration
|
|
92
132
|
|
|
93
133
|
| Option | Type | Default | Description |
|
|
94
134
|
| ------------------- | --------------------- | ---------- | --------------------------------------- |
|
|
@@ -110,7 +150,7 @@ When invoking `client.call()` you can safely decouple dynamically generated mess
|
|
|
110
150
|
await client.call("ocpp1.6", "BootNotification", { ... }, { idempotencyKey: "unique-boot-123" });
|
|
111
151
|
```
|
|
112
152
|
|
|
113
|
-
###
|
|
153
|
+
### OCPP WebSocket RPC Server Configuration
|
|
114
154
|
|
|
115
155
|
| Option | Type | Default | Description |
|
|
116
156
|
| -------------------- | ------------------ | --------- | ------------------------------------------ |
|
|
@@ -123,28 +163,86 @@ await client.call("ocpp1.6", "BootNotification", { ... }, { idempotencyKey: "uni
|
|
|
123
163
|
| `rateLimit` | `RateLimitOptions` | — | Token bucket socket & method rate-limiter |
|
|
124
164
|
| `healthEndpoint` | `boolean` | `false` | Expose HTTP `/health` and `/metrics` APIs |
|
|
125
165
|
|
|
126
|
-
##
|
|
166
|
+
## � Message Observability & Event Handling (v3.0.0+)
|
|
167
|
+
|
|
168
|
+
Unified message event API with direction tracking and rich contextual metadata for complete visibility into OCPP message flow.
|
|
169
|
+
|
|
170
|
+
### Observe All Messages with Direction & Latency
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const client = new OCPPClient({ ... });
|
|
174
|
+
|
|
175
|
+
// Single unified "message" event with direction, context, and metadata
|
|
176
|
+
client.on("message", ({ message, direction, ctx }) => {
|
|
177
|
+
console.log({
|
|
178
|
+
direction, // "IN" (from server) | "OUT" (to server)
|
|
179
|
+
method: ctx.method, // "BootNotification", "MeterValues", etc.
|
|
180
|
+
type: ctx.type, // "incoming_call", "outgoing_call", etc.
|
|
181
|
+
messageId: ctx.messageId, // Unique message ID
|
|
182
|
+
timestamp: ctx.timestamp, // ISO 8601 timestamp
|
|
183
|
+
latencyMs: ctx.latencyMs, // Response latency (if available)
|
|
184
|
+
protocol: ctx.protocol, // "ocpp1.6", "ocpp2.0.1", etc.
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Server-side observation (for each connected client)
|
|
189
|
+
const server = new OCPPServer({ ... });
|
|
190
|
+
server.on("client", (client) => {
|
|
191
|
+
client.on("message", ({ direction, ctx }) => {
|
|
192
|
+
console.log(`[${client.identity}] ${direction} ${ctx.method} (${ctx.latencyMs}ms)`);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Message Event Payload Structure
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface MessageEventPayload {
|
|
201
|
+
message: OCPPMessage; // Original OCPP message tuple
|
|
202
|
+
direction: "IN" | "OUT"; // Message direction
|
|
203
|
+
ctx: MessageEventContext; // Enriched context with metadata
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface MessageEventContext {
|
|
207
|
+
type:
|
|
208
|
+
| "incoming_call"
|
|
209
|
+
| "outgoing_call"
|
|
210
|
+
| "incoming_result"
|
|
211
|
+
| "incoming_error";
|
|
212
|
+
messageId: string;
|
|
213
|
+
method?: string;
|
|
214
|
+
params?: unknown;
|
|
215
|
+
payload?: unknown;
|
|
216
|
+
timestamp: string; // ISO 8601
|
|
217
|
+
latencyMs?: number; // Response latency
|
|
218
|
+
protocol?: string;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## �🛠️ Advanced OCPP RPC Server Configuration & WebSocket Handshake
|
|
223
|
+
|
|
224
|
+
Build sophisticated OCPP server implementations with fine-tuned WebSocket upgrade handling, authentication, and message routing.
|
|
127
225
|
|
|
128
|
-
### Handshake &
|
|
226
|
+
### OCPP WebSocket Handshake, Upgrade & Authentication
|
|
129
227
|
|
|
130
228
|
You can fine-tune how the server handles the WebSocket upgrade process, including timeouts for custom auth logic.
|
|
131
229
|
|
|
132
230
|
```typescript
|
|
133
231
|
const server = new OCPPServer({
|
|
134
|
-
|
|
135
|
-
|
|
232
|
+
// ...
|
|
233
|
+
handshakeTimeoutMs: 5000, // Timeout if auth callback takes too long (default 30s)
|
|
136
234
|
});
|
|
137
235
|
|
|
138
236
|
server.on("upgradeAborted", ({ identity, reason, socket }) => {
|
|
139
|
-
|
|
237
|
+
console.warn(`Handshake aborted for ${identity}: ${reason}`);
|
|
140
238
|
});
|
|
141
239
|
|
|
142
240
|
server.on("upgradeError", ({ error, socket }) => {
|
|
143
|
-
|
|
241
|
+
console.error("Upgrade failed:", error);
|
|
144
242
|
});
|
|
145
243
|
```
|
|
146
244
|
|
|
147
|
-
### Server & Router Execution Flow
|
|
245
|
+
### OCPP Server & Router Execution Flow (Connection & Message Phases)
|
|
148
246
|
|
|
149
247
|
The `OCPPServer` and its internal `OCPPRouter` handle connections and messages in a strict, two-phase execution hierarchy:
|
|
150
248
|
|
|
@@ -160,7 +258,7 @@ Executes before the WebSocket connection is officially accepted.
|
|
|
160
258
|
|
|
161
259
|
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
260
|
|
|
163
|
-
### NOREPLY Suppression
|
|
261
|
+
### NOREPLY Suppression in OCPP RPC Message Handlers
|
|
164
262
|
|
|
165
263
|
Return `NOREPLY` directly from any message handler to safely suppress the automatic outbound `CALLRESULT` without violating strict internal tracking specifications.
|
|
166
264
|
|
|
@@ -168,53 +266,53 @@ Return `NOREPLY` directly from any message handler to safely suppress the automa
|
|
|
168
266
|
import { NOREPLY } from "ocpp-ws-io";
|
|
169
267
|
|
|
170
268
|
client.handle("StatusNotification", ({ params }) => {
|
|
171
|
-
|
|
269
|
+
return NOREPLY;
|
|
172
270
|
});
|
|
173
271
|
```
|
|
174
272
|
|
|
175
|
-
## 📝 Logging
|
|
273
|
+
## 📝 Structured Logging for OCPP RPC WebSocket Applications
|
|
176
274
|
|
|
177
|
-
`ocpp-ws-io`
|
|
275
|
+
`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
276
|
|
|
179
|
-
### Default Behavior
|
|
277
|
+
### Default JSON Logging Behavior
|
|
180
278
|
|
|
181
279
|
By default (`logging: true`), logs are output as structured JSON to `stdout`.
|
|
182
280
|
|
|
183
281
|
```json
|
|
184
282
|
{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
283
|
+
"level": 30,
|
|
284
|
+
"time": 1678900000000,
|
|
285
|
+
"msg": "Client connected",
|
|
286
|
+
"component": "OCPPServer",
|
|
287
|
+
"identity": "CP001"
|
|
190
288
|
}
|
|
191
289
|
```
|
|
192
290
|
|
|
193
|
-
### Pretty Printing & Exchange Logs
|
|
291
|
+
### Pretty Printing & OCPP Message Exchange Logs (Development Mode)
|
|
194
292
|
|
|
195
293
|
Enable `prettify` for development to see colored output with icons.
|
|
196
294
|
Enable `exchangeLog` to log all OCPP messages with direction (`IN`/`OUT`) and metadata.
|
|
197
295
|
|
|
198
296
|
```typescript
|
|
199
297
|
const client = new OCPPClient({
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
298
|
+
// ...
|
|
299
|
+
logging: {
|
|
300
|
+
enabled: true,
|
|
301
|
+
prettify: true, // 🌈 Colors & icons
|
|
302
|
+
exchangeLog: true, // ⚡ Log all OCPP messages
|
|
303
|
+
level: "debug", // Default: 'info'
|
|
304
|
+
},
|
|
207
305
|
});
|
|
208
306
|
```
|
|
209
307
|
|
|
210
308
|
**Output:**
|
|
211
309
|
|
|
212
|
-
```
|
|
310
|
+
```sh
|
|
213
311
|
⚡ CP-101 → BootNotification [OUT]
|
|
214
312
|
✅ CP-101 ← BootNotification [IN] { latencyMs: 45 }
|
|
215
313
|
```
|
|
216
314
|
|
|
217
|
-
### Custom Logger
|
|
315
|
+
### Custom Logger Integration (Pino, Winston, etc.)
|
|
218
316
|
|
|
219
317
|
You can bring your own logger (Pino, Winston, etc.) by implementing `LoggerLike`:
|
|
220
318
|
|
|
@@ -222,31 +320,33 @@ You can bring your own logger (Pino, Winston, etc.) by implementing `LoggerLike`
|
|
|
222
320
|
import pino from "pino";
|
|
223
321
|
|
|
224
322
|
const client = new OCPPClient({
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
323
|
+
logging: {
|
|
324
|
+
handler: pino(), // Use existing logger instance
|
|
325
|
+
},
|
|
228
326
|
});
|
|
229
327
|
```
|
|
230
328
|
|
|
231
|
-
## 🛡️ Safety &
|
|
329
|
+
## 🛡️ OCPP RPC Safety, Reliability & Error Handling
|
|
330
|
+
|
|
331
|
+
Build fault-tolerant EV charging applications with safe call patterns, automatic error handling, and idempotency support.
|
|
232
332
|
|
|
233
|
-
### Safe Calls (`safeCall`)
|
|
333
|
+
### Safe RPC Calls (`safeCall`) - Error Handling Without Try/Catch
|
|
234
334
|
|
|
235
335
|
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
336
|
|
|
237
337
|
```typescript
|
|
238
338
|
const result = await client.safeCall(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
339
|
+
"ocpp1.6",
|
|
340
|
+
"Heartbeat",
|
|
341
|
+
{},
|
|
342
|
+
{
|
|
343
|
+
timeoutMs: 15000, // Finely control the timeout specifically for this request
|
|
344
|
+
},
|
|
245
345
|
);
|
|
246
346
|
|
|
247
347
|
if (result) {
|
|
248
|
-
|
|
249
|
-
|
|
348
|
+
// Checked for undefined
|
|
349
|
+
console.log("Heartbeat accepted:", result.currentTime);
|
|
250
350
|
}
|
|
251
351
|
```
|
|
252
352
|
|
|
@@ -262,16 +362,16 @@ Throws an error if the client responds with a `CALLERROR` or if the timeout is r
|
|
|
262
362
|
|
|
263
363
|
```typescript
|
|
264
364
|
try {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
365
|
+
const result = await server.sendToClient(
|
|
366
|
+
"CP001",
|
|
367
|
+
"ocpp1.6",
|
|
368
|
+
"GetConfiguration",
|
|
369
|
+
{ key: ["ClockAlignedDataInterval"] },
|
|
370
|
+
{ timeoutMs: 10000 },
|
|
371
|
+
);
|
|
372
|
+
console.log("Configuration:", result);
|
|
273
373
|
} catch (error) {
|
|
274
|
-
|
|
374
|
+
console.error("Failed to get configuration:", error);
|
|
275
375
|
}
|
|
276
376
|
```
|
|
277
377
|
|
|
@@ -281,15 +381,15 @@ Returns the response on success, or `undefined` on error, automatically logging
|
|
|
281
381
|
|
|
282
382
|
```typescript
|
|
283
383
|
const result = await server.safeSendToClient(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
384
|
+
"CP001",
|
|
385
|
+
"ocpp1.6",
|
|
386
|
+
"GetConfiguration",
|
|
387
|
+
{ key: ["ClockAlignedDataInterval"] },
|
|
388
|
+
{ timeoutMs: 10000 },
|
|
289
389
|
);
|
|
290
390
|
|
|
291
391
|
if (result) {
|
|
292
|
-
|
|
392
|
+
console.log("Configuration:", result);
|
|
293
393
|
}
|
|
294
394
|
```
|
|
295
395
|
|
|
@@ -299,15 +399,15 @@ For intercepting HTTP WebSocket Upgrade requests before they become an OCPP Clie
|
|
|
299
399
|
|
|
300
400
|
```typescript
|
|
301
401
|
const rateLimiter = defineMiddleware(async (ctx) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
402
|
+
const ip = ctx.handshake.remoteAddress;
|
|
403
|
+
if (isRateLimited(ip)) {
|
|
404
|
+
// Instantly aborts the WebSocket connection with an HTTP 429 status
|
|
405
|
+
ctx.reject(429, "Too Many Requests");
|
|
406
|
+
} else {
|
|
407
|
+
// Or proceed down the execution chain. You can optionally pass an object
|
|
408
|
+
// to next(), which will automatically be shallow-merged into `ctx.state`.
|
|
409
|
+
await ctx.next({ rateLimitRemaining: 99 });
|
|
410
|
+
}
|
|
311
411
|
});
|
|
312
412
|
|
|
313
413
|
server.use(rateLimiter);
|
|
@@ -321,101 +421,102 @@ The Rate Limiter safely throttles connections without dropping the connection im
|
|
|
321
421
|
|
|
322
422
|
```typescript
|
|
323
423
|
const server = new OCPPServer({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
},
|
|
424
|
+
protocols: ["ocpp1.6"],
|
|
425
|
+
rateLimit: {
|
|
426
|
+
limit: 100, // Global limit
|
|
427
|
+
windowMs: 60000, // per 60 seconds
|
|
428
|
+
onLimitExceeded: "disconnect", // or "ignore", or a Custom Callback
|
|
429
|
+
methods: {
|
|
430
|
+
MeterValues: { limit: 10, windowMs: 60000 },
|
|
431
|
+
Heartbeat: { limit: 2, windowMs: 60000 },
|
|
333
432
|
},
|
|
433
|
+
},
|
|
334
434
|
});
|
|
335
435
|
```
|
|
336
436
|
|
|
337
|
-
## 🧩 Middleware
|
|
437
|
+
## 🧩 OCPP Message Middleware & Interceptor Pattern
|
|
338
438
|
|
|
339
|
-
Intercept and modify OCPP messages
|
|
439
|
+
Intercept, validate, and modify OCPP RPC messages across the middleware stack for logging, schema validation, and metric tracking.
|
|
340
440
|
|
|
341
441
|
```typescript
|
|
342
442
|
// Add logging middleware (enabled by default)
|
|
343
443
|
client.use(async (ctx, next) => {
|
|
344
|
-
|
|
345
|
-
|
|
444
|
+
console.log(`Processing ${ctx.method}`);
|
|
445
|
+
await next();
|
|
346
446
|
});
|
|
347
447
|
```
|
|
348
448
|
|
|
349
|
-
## 📡 Clustering
|
|
449
|
+
## 📡 Redis Clustering & Distributed CSMS Architecture
|
|
450
|
+
|
|
451
|
+
Scale OCPP RPC servers across multiple nodes with Redis pub/sub, Streams, and presence tracking for enterprise CSMS deployments.
|
|
350
452
|
|
|
351
|
-
|
|
453
|
+
### Set Up Redis Clustering
|
|
352
454
|
|
|
353
|
-
1. **Install Adapter**:
|
|
455
|
+
1. **Install Redis Adapter**:
|
|
354
456
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
457
|
+
```bash
|
|
458
|
+
npm install ioredis
|
|
459
|
+
```
|
|
358
460
|
|
|
359
|
-
2. **Configure Server**:
|
|
461
|
+
2. **Configure OCPP Server with Redis Adapter**:
|
|
360
462
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
463
|
+
```typescript
|
|
464
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
465
|
+
import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
|
|
466
|
+
import Redis from "ioredis";
|
|
365
467
|
|
|
366
|
-
|
|
367
|
-
|
|
468
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
469
|
+
const server = new OCPPServer({
|
|
368
470
|
protocols: ["ocpp1.6"],
|
|
369
|
-
|
|
471
|
+
});
|
|
370
472
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
473
|
+
// Uses Redis Streams for clustering reliability
|
|
474
|
+
await server.setAdapter(new RedisAdapter(redis));
|
|
475
|
+
```
|
|
374
476
|
|
|
375
|
-
**Features:**
|
|
477
|
+
**OCPP Redis Clustering Features:**
|
|
376
478
|
|
|
377
|
-
- **Unicast Routing**: Send messages to any
|
|
378
|
-
- **Presence**:
|
|
379
|
-
- **
|
|
380
|
-
- **Batch
|
|
479
|
+
- **Unicast Routing**: Send OCPP RPC messages to any charge point on any cluster node.
|
|
480
|
+
- **Presence Tracking**: Real-time discovery of connected clients across the cluster.
|
|
481
|
+
- **Durability**: Redis Streams for guaranteed message delivery.
|
|
482
|
+
- **Batch Operations**: Use `server.broadcastBatch` for efficient multi-node requests.
|
|
381
483
|
|
|
382
|
-
### Custom Adapters (
|
|
484
|
+
### Custom Clustering Adapters (RabbitMQ, Kafka, PubSub)
|
|
383
485
|
|
|
384
|
-
|
|
486
|
+
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
487
|
|
|
386
488
|
```typescript
|
|
387
489
|
import type { EventAdapterInterface } from "ocpp-ws-io";
|
|
388
490
|
|
|
389
491
|
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
|
-
|
|
492
|
+
async publish(channel: string, data: unknown): Promise<void> {
|
|
493
|
+
/* ... */
|
|
494
|
+
}
|
|
495
|
+
async subscribe(
|
|
496
|
+
channel: string,
|
|
497
|
+
handler: (data: unknown) => void,
|
|
498
|
+
): Promise<void> {
|
|
499
|
+
/* ... */
|
|
500
|
+
}
|
|
501
|
+
async unsubscribe(channel: string): Promise<void> {
|
|
502
|
+
/* ... */
|
|
503
|
+
}
|
|
504
|
+
async disconnect(): Promise<void> {
|
|
505
|
+
/* ... */
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Optional primitives for advanced routing:
|
|
509
|
+
async setPresence?(
|
|
510
|
+
identity: string,
|
|
511
|
+
nodeId: string,
|
|
512
|
+
ttl: number,
|
|
513
|
+
): Promise<void>;
|
|
514
|
+
async getPresence?(identity: string): Promise<string | null>;
|
|
515
|
+
async removePresence?(identity: string): Promise<void>;
|
|
516
|
+
async getPresenceBatch?(identities: string[]): Promise<(string | null)[]>;
|
|
517
|
+
async publishBatch?(
|
|
518
|
+
messages: { channel: string; data: unknown }[],
|
|
519
|
+
): Promise<void>;
|
|
520
|
+
async metrics?(): Promise<Record<string, unknown>>;
|
|
419
521
|
}
|
|
420
522
|
```
|
|
421
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import '../types-
|
|
2
|
-
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-
|
|
1
|
+
import '../types-BHIHsj__.mjs';
|
|
2
|
+
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-Defn9aOS.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-BHIHsj__.js';
|
|
2
|
+
export { a as RedisAdapter, b as RedisAdapterOptions } from '../index-BefjKqkS.js';
|
|
3
3
|
import 'ws';
|
|
4
4
|
import 'node:https';
|
|
5
5
|
import 'node:http';
|