ocpp-ws-io 1.0.0-alpha
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.
Potentially problematic release.
This version of ocpp-ws-io might be problematic. Click here for more details.
- package/.github/workflows/publish.yml +52 -0
- package/LICENSE +21 -0
- package/README.md +773 -0
- package/dist/adapters/redis.d.mts +73 -0
- package/dist/adapters/redis.d.ts +73 -0
- package/dist/adapters/redis.js +96 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/adapters/redis.mjs +71 -0
- package/dist/adapters/redis.mjs.map +1 -0
- package/dist/index.d.mts +268 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +38919 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +38855 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-6LVUoXof.d.mts +284 -0
- package/dist/types-6LVUoXof.d.ts +284 -0
- package/package.json +59 -0
- package/src/adapters/adapter.ts +40 -0
- package/src/adapters/redis.ts +144 -0
- package/src/client.ts +882 -0
- package/src/errors.ts +183 -0
- package/src/event-buffer.ts +73 -0
- package/src/index.ts +68 -0
- package/src/queue.ts +65 -0
- package/src/schemas/ocpp1_6.json +2376 -0
- package/src/schemas/ocpp2_0_1.json +11878 -0
- package/src/schemas/ocpp2_1.json +23176 -0
- package/src/server-client.ts +65 -0
- package/src/server.ts +374 -0
- package/src/standard-validators.ts +18 -0
- package/src/types.ts +316 -0
- package/src/util.ts +119 -0
- package/src/validator.ts +148 -0
- package/src/ws-util.ts +186 -0
- package/test/adapter.test.ts +88 -0
- package/test/client.test.ts +297 -0
- package/test/errors.test.ts +132 -0
- package/test/queue.test.ts +133 -0
- package/test/server.test.ts +274 -0
- package/test/util.test.ts +103 -0
- package/test/ws-util.test.ts +93 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +16 -0
- package/vitest.config.ts +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
# ocpp-ws-io
|
|
2
|
+
|
|
3
|
+
Type-safe OCPP WebSocket RPC client & server for Node.js.
|
|
4
|
+
|
|
5
|
+
Built with TypeScript from the ground up — supports OCPP 1.6, 2.0.1, and 2.1 with full JSON schema validation, all three security profiles, and optional Redis-based clustering.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ⚡ **Full OCPP-J RPC** — Call, CallResult, CallError message framing per OCPP-J spec
|
|
10
|
+
- 🔒 **Security Profiles 0–3** — Plain WS, Basic Auth, TLS + Basic Auth, Mutual TLS
|
|
11
|
+
- 📐 **Strict Mode** — Optional schema validation using built-in OCPP JSON schemas
|
|
12
|
+
- 🔁 **Auto-Reconnect** — Exponential backoff with configurable limits
|
|
13
|
+
- 🧩 **Framework Agnostic** — Use standalone, or attach to Express, Fastify, NestJS, etc.
|
|
14
|
+
- 📡 **Clustering** — Optional Redis adapter for multi-instance deployments
|
|
15
|
+
- 🎯 **Type-Safe** — Full TypeScript types for events, handlers, options, and messages
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install ocpp-ws-io
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Client (Charging Station Simulator)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { OCPPClient, SecurityProfile } from "ocpp-ws-io";
|
|
29
|
+
|
|
30
|
+
const client = new OCPPClient({
|
|
31
|
+
endpoint: "ws://localhost:3000",
|
|
32
|
+
identity: "CP001",
|
|
33
|
+
protocols: ["ocpp1.6"],
|
|
34
|
+
securityProfile: SecurityProfile.NONE,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Register a handler for incoming calls from the server
|
|
38
|
+
client.handle("Reset", ({ params }) => {
|
|
39
|
+
console.log("Reset requested:", params);
|
|
40
|
+
return { status: "Accepted" };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Connect and send a BootNotification
|
|
44
|
+
await client.connect();
|
|
45
|
+
|
|
46
|
+
const response = await client.call("BootNotification", {
|
|
47
|
+
chargePointVendor: "VendorX",
|
|
48
|
+
chargePointModel: "ModelY",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log("BootNotification response:", response);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Server (Central System)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
58
|
+
|
|
59
|
+
const server = new OCPPServer({
|
|
60
|
+
protocols: ["ocpp1.6", "ocpp2.0.1"],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Optional: Add authentication
|
|
64
|
+
server.auth((accept, reject, handshake) => {
|
|
65
|
+
console.log(
|
|
66
|
+
`Connection from ${handshake.identity} @ ${handshake.remoteAddress}`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (handshake.identity === "BLOCKED") {
|
|
70
|
+
return reject(401, "Not authorized");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
accept({ session: { authorized: true } });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Handle new client connections
|
|
77
|
+
server.on("client", (client) => {
|
|
78
|
+
console.log(`${client.identity} connected (protocol: ${client.protocol})`);
|
|
79
|
+
|
|
80
|
+
// Handle BootNotification from this client
|
|
81
|
+
client.handle("BootNotification", ({ params }) => {
|
|
82
|
+
console.log("BootNotification:", params);
|
|
83
|
+
return {
|
|
84
|
+
status: "Accepted",
|
|
85
|
+
currentTime: new Date().toISOString(),
|
|
86
|
+
interval: 300,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Send a call TO the client
|
|
91
|
+
client
|
|
92
|
+
.call("GetConfiguration", { key: ["HeartbeatInterval"] })
|
|
93
|
+
.then((result) => console.log("GetConfiguration result:", result))
|
|
94
|
+
.catch((err) => console.error("GetConfiguration failed:", err));
|
|
95
|
+
|
|
96
|
+
client.on("close", () => {
|
|
97
|
+
console.log(`${client.identity} disconnected`);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await server.listen(3000);
|
|
102
|
+
console.log("OCPP Server listening on port 3000");
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Security Profiles
|
|
108
|
+
|
|
109
|
+
`ocpp-ws-io` supports all OCPP security profiles out of the box.
|
|
110
|
+
|
|
111
|
+
### Profile 0 — No Security (Development)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const client = new OCPPClient({
|
|
115
|
+
endpoint: "ws://localhost:3000",
|
|
116
|
+
identity: "CP001",
|
|
117
|
+
protocols: ["ocpp1.6"],
|
|
118
|
+
securityProfile: SecurityProfile.NONE,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Profile 1 — Basic Auth (Unsecured WS)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const client = new OCPPClient({
|
|
126
|
+
endpoint: "ws://localhost:3000",
|
|
127
|
+
identity: "CP001",
|
|
128
|
+
protocols: ["ocpp1.6"],
|
|
129
|
+
securityProfile: SecurityProfile.BASIC_AUTH,
|
|
130
|
+
password: "my-secret-password",
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Server-side password verification:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
server.auth((accept, reject, handshake) => {
|
|
138
|
+
const expectedPassword = getPasswordForStation(handshake.identity);
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
!handshake.password ||
|
|
142
|
+
!handshake.password.equals(Buffer.from(expectedPassword))
|
|
143
|
+
) {
|
|
144
|
+
return reject(401, "Invalid credentials");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
accept();
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Profile 2 — TLS + Basic Auth
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import fs from "fs";
|
|
155
|
+
|
|
156
|
+
const client = new OCPPClient({
|
|
157
|
+
endpoint: "wss://csms.example.com",
|
|
158
|
+
identity: "CP001",
|
|
159
|
+
protocols: ["ocpp2.0.1"],
|
|
160
|
+
securityProfile: SecurityProfile.TLS_BASIC_AUTH,
|
|
161
|
+
password: "my-secret-password",
|
|
162
|
+
tls: {
|
|
163
|
+
ca: fs.readFileSync("./certs/ca.pem"),
|
|
164
|
+
rejectUnauthorized: true,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Server:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const server = new OCPPServer({
|
|
173
|
+
protocols: ["ocpp2.0.1"],
|
|
174
|
+
securityProfile: SecurityProfile.TLS_BASIC_AUTH,
|
|
175
|
+
tls: {
|
|
176
|
+
cert: fs.readFileSync("./certs/server.crt"),
|
|
177
|
+
key: fs.readFileSync("./certs/server.key"),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Profile 3 — Mutual TLS (Client Certificates)
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const client = new OCPPClient({
|
|
186
|
+
endpoint: "wss://csms.example.com",
|
|
187
|
+
identity: "CP001",
|
|
188
|
+
protocols: ["ocpp2.0.1"],
|
|
189
|
+
securityProfile: SecurityProfile.TLS_CLIENT_CERT,
|
|
190
|
+
tls: {
|
|
191
|
+
cert: fs.readFileSync("./certs/client.crt"),
|
|
192
|
+
key: fs.readFileSync("./certs/client.key"),
|
|
193
|
+
ca: fs.readFileSync("./certs/ca.pem"),
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Server-side certificate verification:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const server = new OCPPServer({
|
|
202
|
+
protocols: ["ocpp2.0.1"],
|
|
203
|
+
securityProfile: SecurityProfile.TLS_CLIENT_CERT,
|
|
204
|
+
tls: {
|
|
205
|
+
cert: fs.readFileSync("./certs/server.crt"),
|
|
206
|
+
key: fs.readFileSync("./certs/server.key"),
|
|
207
|
+
ca: fs.readFileSync("./certs/ca.pem"),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
server.auth((accept, reject, handshake) => {
|
|
212
|
+
const cert = handshake.clientCertificate;
|
|
213
|
+
if (!cert || cert.subject?.CN !== handshake.identity) {
|
|
214
|
+
return reject(401, "Certificate identity mismatch");
|
|
215
|
+
}
|
|
216
|
+
accept();
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Framework Integration
|
|
223
|
+
|
|
224
|
+
### Standalone
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const server = new OCPPServer({ protocols: ["ocpp1.6"] });
|
|
228
|
+
await server.listen(3000);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### With Express
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import express from "express";
|
|
235
|
+
import { createServer } from "http";
|
|
236
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
237
|
+
|
|
238
|
+
const app = express();
|
|
239
|
+
const httpServer = createServer(app);
|
|
240
|
+
|
|
241
|
+
const ocppServer = new OCPPServer({ protocols: ["ocpp1.6"] });
|
|
242
|
+
|
|
243
|
+
await ocppServer.listen(0, undefined, { server: httpServer });
|
|
244
|
+
|
|
245
|
+
ocppServer.on("client", (client) => {
|
|
246
|
+
console.log(`${client.identity} connected`);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
httpServer.listen(3000, () => {
|
|
250
|
+
console.log("Express + OCPP on port 3000");
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### With Fastify
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import Fastify from "fastify";
|
|
258
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
259
|
+
|
|
260
|
+
const app = Fastify();
|
|
261
|
+
const ocppServer = new OCPPServer({ protocols: ["ocpp2.0.1"] });
|
|
262
|
+
|
|
263
|
+
app.ready().then(async () => {
|
|
264
|
+
await ocppServer.listen(0, undefined, { server: app.server });
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
ocppServer.on("client", (client) => {
|
|
268
|
+
console.log(`${client.identity} connected`);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await app.listen({ port: 3000 });
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Manual `handleUpgrade`
|
|
275
|
+
|
|
276
|
+
For maximum control, use the `handleUpgrade` getter directly:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { createServer } from "http";
|
|
280
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
281
|
+
|
|
282
|
+
const httpServer = createServer();
|
|
283
|
+
const ocppServer = new OCPPServer({ protocols: ["ocpp1.6"] });
|
|
284
|
+
|
|
285
|
+
httpServer.on("upgrade", ocppServer.handleUpgrade);
|
|
286
|
+
|
|
287
|
+
httpServer.listen(3000);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Strict Mode (Schema Validation)
|
|
293
|
+
|
|
294
|
+
Enable strict mode to validate all inbound and outbound messages against the OCPP JSON schemas:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const client = new OCPPClient({
|
|
298
|
+
endpoint: "ws://localhost:3000",
|
|
299
|
+
identity: "CP001",
|
|
300
|
+
protocols: ["ocpp1.6"],
|
|
301
|
+
strictMode: true, // validates all calls against built-in schemas
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// If validation fails, an RPCError is thrown automatically
|
|
305
|
+
client.on("strictValidationFailure", ({ message, error }) => {
|
|
306
|
+
console.error("Validation failed:", error.message);
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
You can restrict strict mode to specific subprotocols:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const client = new OCPPClient({
|
|
314
|
+
protocols: ["ocpp1.6", "ocpp2.0.1"],
|
|
315
|
+
strictMode: ["ocpp2.0.1"], // only validate OCPP 2.0.1 messages
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Custom validators:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { createValidator } from "ocpp-ws-io";
|
|
323
|
+
import myCustomSchemas from "./my-schemas.json";
|
|
324
|
+
|
|
325
|
+
const myValidator = createValidator("custom-protocol", myCustomSchemas);
|
|
326
|
+
|
|
327
|
+
const client = new OCPPClient({
|
|
328
|
+
protocols: ["custom-protocol"],
|
|
329
|
+
strictMode: true,
|
|
330
|
+
strictModeValidators: [myValidator],
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Clustering with Redis
|
|
337
|
+
|
|
338
|
+
For multi-instance deployments behind a load balancer, use the `RedisAdapter` to distribute events across processes:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
342
|
+
import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
|
|
343
|
+
import Redis from "ioredis";
|
|
344
|
+
|
|
345
|
+
const server = new OCPPServer({ protocols: ["ocpp2.0.1"] });
|
|
346
|
+
|
|
347
|
+
server.setAdapter(
|
|
348
|
+
new RedisAdapter({
|
|
349
|
+
pubClient: new Redis(),
|
|
350
|
+
subClient: new Redis(),
|
|
351
|
+
prefix: "ocpp:", // optional, default: 'ocpp-ws-io:'
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
server.on("client", (client) => {
|
|
356
|
+
console.log(`${client.identity} connected`);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await server.listen(3000);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The adapter is generic — it works with `ioredis`, `redis` (node-redis), or any client implementing the `RedisLikeClient` interface.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// With node-redis
|
|
366
|
+
import { createClient } from "redis";
|
|
367
|
+
|
|
368
|
+
const pub = createClient();
|
|
369
|
+
const sub = pub.duplicate();
|
|
370
|
+
await pub.connect();
|
|
371
|
+
await sub.connect();
|
|
372
|
+
|
|
373
|
+
server.setAdapter(new RedisAdapter({ pubClient: pub, subClient: sub }));
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## API Reference
|
|
379
|
+
|
|
380
|
+
### `OCPPClient`
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { OCPPClient } from "ocpp-ws-io";
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### Constructor
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const client = new OCPPClient(options: ClientOptions);
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**`ClientOptions`**:
|
|
393
|
+
|
|
394
|
+
| Option | Type | Default | Description |
|
|
395
|
+
| --------------------------- | ------------------------ | ---------- | -------------------------------------------- |
|
|
396
|
+
| `identity` | `string` | _required_ | Charging station ID |
|
|
397
|
+
| `endpoint` | `string` | _required_ | WebSocket URL (`ws://` or `wss://`) |
|
|
398
|
+
| `protocols` | `string[]` | `[]` | OCPP subprotocols to negotiate |
|
|
399
|
+
| `securityProfile` | `SecurityProfile` | `NONE` | Security profile (0–3) |
|
|
400
|
+
| `password` | `string \| Buffer` | — | Password for Basic Auth (Profile 1 & 2) |
|
|
401
|
+
| `tls` | `TLSOptions` | — | TLS/SSL options (Profile 2 & 3) |
|
|
402
|
+
| `headers` | `Record<string, string>` | — | Additional WebSocket headers |
|
|
403
|
+
| `query` | `Record<string, string>` | — | Additional URL query parameters |
|
|
404
|
+
| `reconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
405
|
+
| `maxReconnects` | `number` | `Infinity` | Max reconnection attempts |
|
|
406
|
+
| `backoffMin` | `number` | `1000` | Initial reconnect delay (ms) |
|
|
407
|
+
| `backoffMax` | `number` | `30000` | Maximum reconnect delay (ms) |
|
|
408
|
+
| `callTimeoutMs` | `number` | `30000` | Default RPC call timeout (ms) |
|
|
409
|
+
| `pingIntervalMs` | `number` | `30000` | WebSocket ping interval (ms), `0` to disable |
|
|
410
|
+
| `deferPingsOnActivity` | `boolean` | `false` | Defer pings when messages received |
|
|
411
|
+
| `callConcurrency` | `number` | `1` | Max concurrent outbound calls |
|
|
412
|
+
| `strictMode` | `boolean \| string[]` | `false` | Enable/restrict schema validation |
|
|
413
|
+
| `strictModeValidators` | `Validator[]` | — | Custom validators for strict mode |
|
|
414
|
+
| `maxBadMessages` | `number` | `Infinity` | Close after N consecutive bad messages |
|
|
415
|
+
| `respondWithDetailedErrors` | `boolean` | `false` | Include error details in responses |
|
|
416
|
+
|
|
417
|
+
#### Properties
|
|
418
|
+
|
|
419
|
+
| Property | Type | Description |
|
|
420
|
+
| ------------------------ | --------------------- | ---------------------------------------------------- |
|
|
421
|
+
| `client.identity` | `string` | Charging station identity |
|
|
422
|
+
| `client.protocol` | `string \| undefined` | Negotiated subprotocol |
|
|
423
|
+
| `client.state` | `ConnectionState` | Connection state (CONNECTING, OPEN, CLOSING, CLOSED) |
|
|
424
|
+
| `client.securityProfile` | `SecurityProfile` | Active security profile |
|
|
425
|
+
|
|
426
|
+
#### Methods
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Connect to the OCPP server
|
|
430
|
+
await client.connect();
|
|
431
|
+
|
|
432
|
+
// Make an RPC call
|
|
433
|
+
const result = await client.call('BootNotification', { ... });
|
|
434
|
+
|
|
435
|
+
// Make a call with options
|
|
436
|
+
const result = await client.call('RemoteStartTransaction', params, {
|
|
437
|
+
timeoutMs: 5000,
|
|
438
|
+
signal: abortController.signal,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Register a handler for a specific method
|
|
442
|
+
client.handle('Reset', ({ params, method, messageId, signal }) => {
|
|
443
|
+
return { status: 'Accepted' };
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Register a wildcard handler (handles all unmatched methods)
|
|
447
|
+
client.handle((method, { params }) => {
|
|
448
|
+
console.log(`Unhandled method: ${method}`);
|
|
449
|
+
throw new RPCNotImplementedError();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Remove a handler
|
|
453
|
+
client.removeHandler('Reset');
|
|
454
|
+
client.removeAllHandlers();
|
|
455
|
+
|
|
456
|
+
// Close the connection
|
|
457
|
+
await client.close();
|
|
458
|
+
await client.close({ code: 1000, reason: 'Normal closure' });
|
|
459
|
+
await client.close({ awaitPending: true }); // wait for in-flight calls
|
|
460
|
+
await client.close({ force: true }); // immediate termination
|
|
461
|
+
|
|
462
|
+
// Reconfigure at runtime
|
|
463
|
+
client.reconfigure({ callTimeoutMs: 10000 });
|
|
464
|
+
|
|
465
|
+
// Send a raw message (advanced — use with caution)
|
|
466
|
+
client.sendRaw(JSON.stringify([2, 'uuid', 'Heartbeat', {}]));
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
#### Events
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
client.on("open", ({ response }) => {
|
|
473
|
+
/* connected */
|
|
474
|
+
});
|
|
475
|
+
client.on("close", ({ code, reason }) => {
|
|
476
|
+
/* disconnected */
|
|
477
|
+
});
|
|
478
|
+
client.on("error", (error) => {
|
|
479
|
+
/* error occurred */
|
|
480
|
+
});
|
|
481
|
+
client.on("connecting", ({ url }) => {
|
|
482
|
+
/* attempting connection */
|
|
483
|
+
});
|
|
484
|
+
client.on("reconnect", ({ attempt, delay }) => {
|
|
485
|
+
/* reconnecting */
|
|
486
|
+
});
|
|
487
|
+
client.on("message", (message) => {
|
|
488
|
+
/* raw OCPP message */
|
|
489
|
+
});
|
|
490
|
+
client.on("call", (call) => {
|
|
491
|
+
/* outbound call sent */
|
|
492
|
+
});
|
|
493
|
+
client.on("callResult", (result) => {
|
|
494
|
+
/* call result received */
|
|
495
|
+
});
|
|
496
|
+
client.on("callError", (error) => {
|
|
497
|
+
/* call error received */
|
|
498
|
+
});
|
|
499
|
+
client.on("badMessage", ({ message, error }) => {
|
|
500
|
+
/* malformed message */
|
|
501
|
+
});
|
|
502
|
+
client.on("ping", () => {
|
|
503
|
+
/* ping sent */
|
|
504
|
+
});
|
|
505
|
+
client.on("pong", () => {
|
|
506
|
+
/* pong received */
|
|
507
|
+
});
|
|
508
|
+
client.on("strictValidationFailure", ({ message, error }) => {
|
|
509
|
+
/* schema validation failure */
|
|
510
|
+
});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
### `OCPPServer`
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { OCPPServer } from "ocpp-ws-io";
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
#### Constructor
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const server = new OCPPServer(options?: ServerOptions);
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**`ServerOptions`**:
|
|
528
|
+
|
|
529
|
+
| Option | Type | Default | Description |
|
|
530
|
+
| --------------------------- | --------------------- | ---------- | ----------------------------------------- |
|
|
531
|
+
| `protocols` | `string[]` | `[]` | Accepted OCPP subprotocols |
|
|
532
|
+
| `securityProfile` | `SecurityProfile` | `NONE` | Security profile for auto-created servers |
|
|
533
|
+
| `tls` | `TLSOptions` | — | TLS options (Profile 2 & 3) |
|
|
534
|
+
| `callTimeoutMs` | `number` | `30000` | Inherited by server clients |
|
|
535
|
+
| `pingIntervalMs` | `number` | `30000` | Inherited by server clients |
|
|
536
|
+
| `callConcurrency` | `number` | `1` | Inherited by server clients |
|
|
537
|
+
| `strictMode` | `boolean \| string[]` | `false` | Inherited by server clients |
|
|
538
|
+
| `maxBadMessages` | `number` | `Infinity` | Inherited by server clients |
|
|
539
|
+
| `respondWithDetailedErrors` | `boolean` | `false` | Inherited by server clients |
|
|
540
|
+
|
|
541
|
+
#### Properties
|
|
542
|
+
|
|
543
|
+
| Property | Type | Description |
|
|
544
|
+
| ---------------- | ------------------------------- | --------------------- |
|
|
545
|
+
| `server.clients` | `ReadonlySet<OCPPServerClient>` | All connected clients |
|
|
546
|
+
|
|
547
|
+
#### Methods
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// Start listening on a port
|
|
551
|
+
const httpServer = await server.listen(port, host?, options?);
|
|
552
|
+
|
|
553
|
+
// Attach authentication handler
|
|
554
|
+
server.auth((accept, reject, handshake, signal) => {
|
|
555
|
+
// handshake contains: identity, remoteAddress, headers,
|
|
556
|
+
// protocols, endpoint, query, password, clientCertificate,
|
|
557
|
+
// securityProfile, request
|
|
558
|
+
accept({ session: { userId: 123 } });
|
|
559
|
+
// or: reject(401, 'Not authorized');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Close the server
|
|
563
|
+
await server.close();
|
|
564
|
+
await server.close({ awaitPending: true });
|
|
565
|
+
|
|
566
|
+
// Set an event adapter for clustering
|
|
567
|
+
server.setAdapter(adapter);
|
|
568
|
+
|
|
569
|
+
// Publish events across instances
|
|
570
|
+
await server.publish('firmware-update', { stationId: 'CP001' });
|
|
571
|
+
|
|
572
|
+
// Reconfigure at runtime
|
|
573
|
+
server.reconfigure({ callTimeoutMs: 60000 });
|
|
574
|
+
|
|
575
|
+
// Get the handleUpgrade function for manual use
|
|
576
|
+
const upgrade = server.handleUpgrade;
|
|
577
|
+
httpServer.on('upgrade', upgrade);
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Events
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
server.on("client", (client: OCPPServerClient) => {
|
|
584
|
+
/* new client connected */
|
|
585
|
+
});
|
|
586
|
+
server.on("error", (error) => {
|
|
587
|
+
/* server error */
|
|
588
|
+
});
|
|
589
|
+
server.on("upgradeError", ({ error, socket }) => {
|
|
590
|
+
/* upgrade failed */
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
### `OCPPServerClient`
|
|
597
|
+
|
|
598
|
+
Server-side client representation, created automatically when a charging station connects. Extends `OCPPClient` with additional properties:
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
server.on("client", (client) => {
|
|
602
|
+
client.identity; // string — station identity
|
|
603
|
+
client.protocol; // string — negotiated subprotocol
|
|
604
|
+
client.session; // Record<string, unknown> — session data from auth
|
|
605
|
+
client.handshake; // HandshakeInfo — connection handshake details
|
|
606
|
+
|
|
607
|
+
// All OCPPClient methods available:
|
|
608
|
+
client.handle("Heartbeat", () => ({ currentTime: new Date().toISOString() }));
|
|
609
|
+
const result = await client.call("GetConfiguration", { key: [] });
|
|
610
|
+
await client.close();
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
### `NOREPLY`
|
|
617
|
+
|
|
618
|
+
Return `NOREPLY` from a handler to suppress the automatic response:
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
import { NOREPLY } from "ocpp-ws-io";
|
|
622
|
+
|
|
623
|
+
client.handle("StatusNotification", ({ params }) => {
|
|
624
|
+
// Process the notification but don't send a response
|
|
625
|
+
return NOREPLY;
|
|
626
|
+
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
### Error Classes
|
|
632
|
+
|
|
633
|
+
All error classes are exported for `instanceof` checks:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
import {
|
|
637
|
+
// Base errors
|
|
638
|
+
TimeoutError, // Call timeout
|
|
639
|
+
UnexpectedHttpResponse, // Non-101 upgrade response
|
|
640
|
+
WebsocketUpgradeError, // WebSocket upgrade failure
|
|
641
|
+
|
|
642
|
+
// RPC errors (OCPP-J spec Section 4.3)
|
|
643
|
+
RPCGenericError,
|
|
644
|
+
RPCNotImplementedError,
|
|
645
|
+
RPCNotSupportedError,
|
|
646
|
+
RPCInternalError,
|
|
647
|
+
RPCProtocolError,
|
|
648
|
+
RPCSecurityError,
|
|
649
|
+
RPCFormatViolationError,
|
|
650
|
+
RPCFormationViolationError,
|
|
651
|
+
RPCPropertyConstraintViolationError,
|
|
652
|
+
RPCOccurrenceConstraintViolationError,
|
|
653
|
+
RPCTypeConstraintViolationError,
|
|
654
|
+
RPCMessageTypeNotSupportedError,
|
|
655
|
+
RPCFrameworkError,
|
|
656
|
+
} from "ocpp-ws-io";
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Throw typed errors from handlers:
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
client.handle("Reset", ({ params }) => {
|
|
663
|
+
if (params.type === "Hard") {
|
|
664
|
+
throw new RPCNotSupportedError("Hard reset not supported");
|
|
665
|
+
}
|
|
666
|
+
return { status: "Accepted" };
|
|
667
|
+
});
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
### Utility Functions
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
import {
|
|
676
|
+
createRPCError,
|
|
677
|
+
getErrorPlainObject,
|
|
678
|
+
getPackageIdent,
|
|
679
|
+
} from "ocpp-ws-io";
|
|
680
|
+
|
|
681
|
+
// Create an RPC error from a code string
|
|
682
|
+
const err = createRPCError("NotImplemented", "Not available");
|
|
683
|
+
|
|
684
|
+
// Serialize an error to a plain JSON-safe object
|
|
685
|
+
const plain = getErrorPlainObject(err);
|
|
686
|
+
|
|
687
|
+
// Get package identifier (for headers, logging)
|
|
688
|
+
const ident = getPackageIdent(); // "ocpp-ws-io/1.0.0"
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Constants
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
import { ConnectionState, MessageType, SecurityProfile } from "ocpp-ws-io";
|
|
697
|
+
|
|
698
|
+
// Connection states
|
|
699
|
+
ConnectionState.CONNECTING; // 0
|
|
700
|
+
ConnectionState.OPEN; // 1
|
|
701
|
+
ConnectionState.CLOSING; // 2
|
|
702
|
+
ConnectionState.CLOSED; // 3
|
|
703
|
+
|
|
704
|
+
// Also available as static properties
|
|
705
|
+
OCPPClient.CONNECTING;
|
|
706
|
+
OCPPClient.OPEN;
|
|
707
|
+
OCPPClient.CLOSING;
|
|
708
|
+
OCPPClient.CLOSED;
|
|
709
|
+
|
|
710
|
+
// Message types
|
|
711
|
+
MessageType.CALL; // 2
|
|
712
|
+
MessageType.CALLRESULT; // 3
|
|
713
|
+
MessageType.CALLERROR; // 4
|
|
714
|
+
|
|
715
|
+
// Security profiles
|
|
716
|
+
SecurityProfile.NONE; // 0
|
|
717
|
+
SecurityProfile.BASIC_AUTH; // 1
|
|
718
|
+
SecurityProfile.TLS_BASIC_AUTH; // 2
|
|
719
|
+
SecurityProfile.TLS_CLIENT_CERT; // 3
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## TypeScript Types
|
|
725
|
+
|
|
726
|
+
All types are exported for use in your application:
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
import type {
|
|
730
|
+
// OCPP message types
|
|
731
|
+
OCPPCall,
|
|
732
|
+
OCPPCallResult,
|
|
733
|
+
OCPPCallError,
|
|
734
|
+
OCPPMessage,
|
|
735
|
+
|
|
736
|
+
// Handler types
|
|
737
|
+
HandlerContext,
|
|
738
|
+
CallHandler,
|
|
739
|
+
WildcardHandler,
|
|
740
|
+
|
|
741
|
+
// Option types
|
|
742
|
+
ClientOptions,
|
|
743
|
+
ServerOptions,
|
|
744
|
+
CallOptions,
|
|
745
|
+
CloseOptions,
|
|
746
|
+
ListenOptions,
|
|
747
|
+
TLSOptions,
|
|
748
|
+
|
|
749
|
+
// Auth types
|
|
750
|
+
AuthAccept,
|
|
751
|
+
AuthCallback,
|
|
752
|
+
HandshakeInfo,
|
|
753
|
+
SessionData,
|
|
754
|
+
|
|
755
|
+
// Event types
|
|
756
|
+
ClientEvents,
|
|
757
|
+
ServerEvents,
|
|
758
|
+
|
|
759
|
+
// Adapter interface (for custom adapters)
|
|
760
|
+
EventAdapterInterface,
|
|
761
|
+
} from "ocpp-ws-io";
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## Requirements
|
|
767
|
+
|
|
768
|
+
- **Node.js** ≥ 18.0.0
|
|
769
|
+
- **TypeScript** ≥ 5.0 (optional, but recommended)
|
|
770
|
+
|
|
771
|
+
## License
|
|
772
|
+
|
|
773
|
+
[MIT](LICENSE)
|