ocpp-protocol-proxy 0.1.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 ADDED
@@ -0,0 +1,196 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/rohittiwari-dev/ocpp-ws-io/main/assets/ocpp-protocol-proxy.png" alt="ocpp-protocol-proxy" width="420" />
3
+ </p>
4
+
5
+
6
+ **Transport-agnostic OCPP version translation proxy** — translate any OCPP version to any other, with pluggable middleware, stateful session management, and spec-compliant presets.
7
+
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](../../LICENSE)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
10
+
11
+ Part of the [ocpp-ws-io](https://ocpp-ws-io.rohittiwari.me) ecosystem.
12
+
13
+ ---
14
+
15
+ ## Why?
16
+
17
+ Legacy OCPP 1.6 charge points can't speak to modern OCPP 2.1 central systems. Instead of rewriting firmware or maintaining dual-protocol backends, drop a translation proxy in between.
18
+
19
+ ```
20
+ ┌─────────┐ OCPP 1.6 ┌───────────┐ OCPP 2.1 ┌──────┐
21
+ │ EVSE │ ───────────────── │ PROXY │ ───────────────── │ CSMS │
22
+ │ (1.6) │ │ (translate)│ │(2.1) │
23
+ └─────────┘ └───────────┘ └──────┘
24
+ ```
25
+
26
+ ## Features
27
+
28
+ - ⚡ **Any-to-any translation** — 1.6 ↔ 2.0.1 ↔ 2.1
29
+ - 🔌 **Transport agnostic** — Core logic doesn't depend on WebSockets or Node.js
30
+ - 🧩 **Modular presets** — Import only the OCPP profiles you need
31
+ - 🔗 **Middleware pipeline** — Pre/post translation hooks for logging, validation, telemetry
32
+ - 💾 **Stateful sessions** — UUID ↔ integer transaction ID mapping across messages
33
+ - 📊 **Built-in telemetry** — Latency tracking middleware included
34
+ - ✅ **All 28 OCPP 1.6 messages** — Complete Core + optional profiles mapped to 2.1
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ npm install ocpp-protocol-proxy
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### Basic Usage with Presets
45
+
46
+ ```typescript
47
+ import { OCPPProtocolProxy, presets, OcppWsIoAdapter } from "ocpp-protocol-proxy";
48
+
49
+ const proxy = new OCPPProtocolProxy({
50
+ upstreamEndpoint: "ws://your-csms:9000",
51
+ upstreamProtocol: "ocpp2.1",
52
+ });
53
+
54
+ // Load all OCPP 1.6 → 2.1 translation presets
55
+ proxy.translate(presets.ocpp16_to_ocpp21);
56
+
57
+ const adapter = new OcppWsIoAdapter({
58
+ port: 9001,
59
+ protocols: ["ocpp1.6"],
60
+ });
61
+
62
+ await proxy.listenOnAdapter(adapter);
63
+ ```
64
+
65
+ ### Custom Overrides
66
+
67
+ Use presets as a base and override specific actions:
68
+
69
+ ```typescript
70
+ proxy.translate({
71
+ upstream: {
72
+ ...presets.ocpp16_to_ocpp21.upstream,
73
+
74
+ // Override with custom business logic
75
+ "ocpp1.6:StartTransaction": async (params, ctx) => {
76
+ console.log(`Custom StartTx for ${ctx.identity}`, params);
77
+ return {
78
+ action: "TransactionEvent",
79
+ payload: { /* your custom 2.1 mapping */ },
80
+ };
81
+ },
82
+ },
83
+ downstream: { ...presets.ocpp16_to_ocpp21.downstream },
84
+ responses: { ...presets.ocpp16_to_ocpp21.responses },
85
+ errors: { ...presets.ocpp16_to_ocpp21.errors },
86
+ });
87
+ ```
88
+
89
+ ## Selective Presets
90
+
91
+ Import only the OCPP profiles you need for tree-shaking:
92
+
93
+ ```typescript
94
+ import {
95
+ corePreset,
96
+ smartChargingPreset,
97
+ firmwarePreset,
98
+ reservationPreset,
99
+ localAuthPreset,
100
+ } from "ocpp-protocol-proxy";
101
+ ```
102
+
103
+ | Preset | Profile | Messages |
104
+ |:---|:---|:---|
105
+ | `corePreset` | Core (mandatory) | 16: Boot, Auth, Start/Stop Tx, MeterValues, StatusNotification, Reset, Unlock, TriggerMessage, and more |
106
+ | `smartChargingPreset` | Smart Charging | SetChargingProfile, ClearChargingProfile, GetCompositeSchedule |
107
+ | `firmwarePreset` | Firmware Mgmt | UpdateFirmware, FirmwareStatusNotification, GetLog→GetDiagnostics |
108
+ | `reservationPreset` | Reservation | ReserveNow, CancelReservation |
109
+ | `localAuthPreset` | Local Auth List | GetLocalListVersion, SendLocalList |
110
+
111
+ ## Middleware
112
+
113
+ Intercept messages before or after translation:
114
+
115
+ ```typescript
116
+ import type { ProxyMiddleware } from "ocpp-protocol-proxy";
117
+
118
+ const logger: ProxyMiddleware = async (message, context, direction, phase) => {
119
+ console.log(`[${phase}] ${direction} — ${context.identity}`, message);
120
+ return undefined; // pass through unchanged
121
+ };
122
+
123
+ const proxy = new OCPPProtocolProxy({
124
+ upstreamEndpoint: "ws://csms:9000",
125
+ upstreamProtocol: "ocpp2.1",
126
+ middlewares: [logger],
127
+ });
128
+ ```
129
+
130
+ Middleware runs at 4 lifecycle points:
131
+ | Phase | Direction | When |
132
+ |:---|:---|:---|
133
+ | `pre` | `upstream` | Before translating EVSE→CSMS calls |
134
+ | `post` | `upstream` | After translating, before forwarding to CSMS |
135
+ | `pre` | `response` | Before translating CSMS response back |
136
+ | `post` | `response` | After translating, before returning to EVSE |
137
+
138
+ ## Custom Session Store
139
+
140
+ The default `InMemorySessionStore` works for single-instance deployments. For clustered setups, implement `ISessionStore`:
141
+
142
+ ```typescript
143
+ import type { ISessionStore } from "ocpp-protocol-proxy";
144
+
145
+ class RedisSessionStore implements ISessionStore {
146
+ async set(identity: string, key: string, value: any) { /* ... */ }
147
+ async get<T>(identity: string, key: string): Promise<T | undefined> { /* ... */ }
148
+ async delete(identity: string, key: string) { /* ... */ }
149
+ async clear(identity: string) { /* ... */ }
150
+ }
151
+ ```
152
+
153
+ ## Events
154
+
155
+ ```typescript
156
+ proxy.on("connection", (identity, protocol) => { /* EVSE connected */ });
157
+ proxy.on("disconnect", (identity) => { /* EVSE disconnected */ });
158
+ proxy.on("translationError", (err, msg, ctx) => { /* translation failed */ });
159
+ proxy.on("middlewareError", (err, msg, ctx) => { /* middleware threw */ });
160
+ ```
161
+
162
+ ## Architecture
163
+
164
+ ```
165
+ src/
166
+ ├── core/
167
+ │ ├── types.ts # OCPPMessage, TranslationMap, ITransportAdapter
168
+ │ ├── translator.ts # Pure translation engine
169
+ │ └── session.ts # ISessionStore + InMemorySessionStore
170
+ ├── presets/
171
+ │ ├── index.ts # Merged preset + mergePresets utility
172
+ │ ├── core.ts # Core profile (16 messages)
173
+ │ ├── smart-charging.ts # Smart Charging (3 messages)
174
+ │ ├── firmware.ts # Firmware Management (4 messages)
175
+ │ ├── reservation.ts # Reservation (2 messages)
176
+ │ ├── local-auth.ts # Local Auth List (2 messages)
177
+ │ └── status-enums.ts # StatusNotification enum mapping tables
178
+ ├── adapters/
179
+ │ └── ocpp-ws-io.adapter.ts # WebSocket adapter using ocpp-ws-io
180
+ ├── middlewares/
181
+ │ └── telemetry.ts # Latency tracking middleware
182
+ ├── proxy.ts # OCPPProtocolProxy orchestrator
183
+ └── index.ts # Public API
184
+ ```
185
+
186
+ ## Related Packages
187
+
188
+ | Package | Description |
189
+ |:---|:---|
190
+ | [ocpp-ws-io](https://npmjs.com/ocpp-ws-io) | Core OCPP WebSocket RPC client & server |
191
+ | [ocpp-ws-cli](https://npmjs.com/ocpp-ws-cli) | CLI for simulation & testing |
192
+ | [voltlog-io](https://npmjs.com/voltlog-io) | Structured logger |
193
+
194
+ ## License
195
+
196
+ [MIT](../../LICENSE) © 2026 Rohit Tiwari
@@ -0,0 +1,251 @@
1
+ import { OCPPClient } from 'ocpp-ws-io';
2
+ import { EventEmitter } from 'node:events';
3
+
4
+ /**
5
+ * A generic interface for storing session state per connection/identity
6
+ * across asynchronous OCPP messages.
7
+ * E.g., mapping a numeric 1.6 transactionId to a 2.1 UUID string.
8
+ */
9
+ interface ISessionStore {
10
+ /**
11
+ * Set a key-value pair tied to a specific identity's session.
12
+ */
13
+ set(identity: string, key: string, value: any): Promise<void>;
14
+ /**
15
+ * Retrieve a value tied to a specific identity's session.
16
+ */
17
+ get<T = any>(identity: string, key: string): Promise<T | undefined>;
18
+ /**
19
+ * Delete a key tied to a specific identity's session.
20
+ */
21
+ delete(identity: string, key: string): Promise<void>;
22
+ /**
23
+ * Clear all session data for a specific identity (e.g. on disconnect).
24
+ */
25
+ clear(identity: string): Promise<void>;
26
+ }
27
+ declare class InMemorySessionStore implements ISessionStore {
28
+ private store;
29
+ set(identity: string, key: string, value: any): Promise<void>;
30
+ get<T = any>(identity: string, key: string): Promise<T | undefined>;
31
+ delete(identity: string, key: string): Promise<void>;
32
+ clear(identity: string): Promise<void>;
33
+ }
34
+
35
+ declare enum MessageType {
36
+ CALL = 2,
37
+ CALLRESULT = 3,
38
+ CALLERROR = 4
39
+ }
40
+ type OCPPMessage = {
41
+ type: MessageType.CALL;
42
+ messageId: string;
43
+ action: string;
44
+ payload: any;
45
+ } | {
46
+ type: MessageType.CALLRESULT;
47
+ messageId: string;
48
+ payload: any;
49
+ } | {
50
+ type: MessageType.CALLERROR;
51
+ messageId: string;
52
+ errorCode: string;
53
+ errorDescription: string;
54
+ errorDetails: any;
55
+ };
56
+ interface TranslationContext {
57
+ identity: string;
58
+ sourceProtocol: string;
59
+ targetProtocol: string;
60
+ session: ISessionStore;
61
+ }
62
+ type TranslationResult = {
63
+ action?: string;
64
+ payload: any;
65
+ };
66
+ type MiddlewarePhase = "pre" | "post";
67
+ type MiddlewareDirection = "upstream" | "downstream" | "response" | "error";
68
+ /**
69
+ * Middleware function signature.
70
+ * Return the (possibly mutated) message to pass it along,
71
+ * or return undefined to pass the original message unchanged.
72
+ */
73
+ type ProxyMiddleware = (message: OCPPMessage, context: TranslationContext, direction: MiddlewareDirection, phase: MiddlewarePhase) => Promise<OCPPMessage | undefined>;
74
+ type TranslationMap = {
75
+ /** EVSE -> CSMS call mappers, keyed by `sourceProtocol:Action` */
76
+ upstream: Record<string, (params: any, context: TranslationContext) => TranslationResult | Promise<TranslationResult>>;
77
+ /** CSMS -> EVSE call mappers, keyed by `targetProtocol:Action` */
78
+ downstream: Record<string, (params: any, context: TranslationContext) => TranslationResult | Promise<TranslationResult>>;
79
+ /** Response payload mappers, keyed by `targetProtocol:ActionResponse` */
80
+ responses?: Record<string, (params: any, context: TranslationContext) => any | Promise<any>>;
81
+ /** Error mappers, keyed by `sourceProtocol:Error` */
82
+ errors?: Record<string, (errorCode: string, errorDescription: string, errorDetails: any, context: TranslationContext) => {
83
+ errorCode: string;
84
+ errorDescription: string;
85
+ errorDetails: any;
86
+ } | Promise<{
87
+ errorCode: string;
88
+ errorDescription: string;
89
+ errorDetails: any;
90
+ }>>;
91
+ };
92
+ interface IConnection {
93
+ identity: string;
94
+ protocol: string;
95
+ send(message: OCPPMessage): Promise<OCPPMessage | undefined>;
96
+ onMessage(handler: (message: OCPPMessage) => Promise<OCPPMessage | undefined>): void;
97
+ onClose(handler: () => void): void;
98
+ }
99
+ interface ITransportAdapter {
100
+ listen(onConnection: (connection: IConnection) => void): Promise<void>;
101
+ close(): Promise<void>;
102
+ }
103
+
104
+ declare class OcppWsIoConnection implements IConnection {
105
+ private client;
106
+ identity: string;
107
+ protocol: string;
108
+ private messageHandler?;
109
+ constructor(client: OCPPClient);
110
+ private setupCatchAll;
111
+ send(message: OCPPMessage): Promise<OCPPMessage | undefined>;
112
+ onMessage(handler: (message: OCPPMessage) => Promise<OCPPMessage | undefined>): void;
113
+ onClose(handler: () => void): void;
114
+ }
115
+ interface WsAdapterOptions {
116
+ port: number;
117
+ protocols: string[];
118
+ }
119
+ declare class OcppWsIoAdapter implements ITransportAdapter {
120
+ private server;
121
+ private port;
122
+ private protocols;
123
+ httpServer?: any;
124
+ constructor(options: WsAdapterOptions);
125
+ listen(onConnection: (connection: IConnection) => void): Promise<void>;
126
+ close(): Promise<void>;
127
+ }
128
+
129
+ declare class OCPPTranslator {
130
+ private translationMap;
131
+ constructor(translationMap: TranslationMap);
132
+ updateMap(map: Partial<TranslationMap>): void;
133
+ translateUpstreamCall(message: Extract<OCPPMessage, {
134
+ type: MessageType.CALL;
135
+ }>, context: TranslationContext): Promise<Extract<OCPPMessage, {
136
+ type: MessageType.CALL;
137
+ }>>;
138
+ translateDownstreamCall(message: Extract<OCPPMessage, {
139
+ type: MessageType.CALL;
140
+ }>, context: TranslationContext): Promise<Extract<OCPPMessage, {
141
+ type: MessageType.CALL;
142
+ }>>;
143
+ translateCallResult(message: Extract<OCPPMessage, {
144
+ type: MessageType.CALLRESULT;
145
+ }>, originalAction: string, context: TranslationContext): Promise<Extract<OCPPMessage, {
146
+ type: MessageType.CALLRESULT;
147
+ }>>;
148
+ translateCallError(message: Extract<OCPPMessage, {
149
+ type: MessageType.CALLERROR;
150
+ }>, context: TranslationContext): Promise<Extract<OCPPMessage, {
151
+ type: MessageType.CALLERROR;
152
+ }>>;
153
+ }
154
+
155
+ /**
156
+ * Example built-in middleware that tracks translation latency.
157
+ * In production, push metrics to Datadog, Prometheus, or OpenTelemetry.
158
+ */
159
+ declare const TelemetryMiddleware: ProxyMiddleware;
160
+
161
+ /**
162
+ * Core profile preset — the only mandatory OCPP 1.6 profile.
163
+ * Covers all 16 Core messages.
164
+ */
165
+ declare const corePreset: Partial<TranslationMap>;
166
+
167
+ /**
168
+ * Firmware Management profile preset.
169
+ * Note: OCPP 2.1 renames GetDiagnostics→GetLog and
170
+ * DiagnosticsStatusNotification→LogStatusNotification.
171
+ */
172
+ declare const firmwarePreset: Partial<TranslationMap>;
173
+
174
+ /**
175
+ * Local Auth List Management profile preset.
176
+ */
177
+ declare const localAuthPreset: Partial<TranslationMap>;
178
+
179
+ /**
180
+ * Reservation profile preset.
181
+ */
182
+ declare const reservationPreset: Partial<TranslationMap>;
183
+
184
+ /**
185
+ * Smart Charging profile preset.
186
+ * Covers: SetChargingProfile, ClearChargingProfile, GetCompositeSchedule.
187
+ */
188
+ declare const smartChargingPreset: Partial<TranslationMap>;
189
+
190
+ /**
191
+ * OCPP 1.6 status → OCPP 2.1 connectorStatus enum mapping.
192
+ * Per OCPP 2.0.1 Part 2, Section K.10 — ConnectorStatusEnumType.
193
+ */
194
+ declare const statusMap16to21: Record<string, string>;
195
+ /**
196
+ * OCPP 2.1 connectorStatus → OCPP 1.6 status mapping (best-effort reverse).
197
+ * 2.1 has fewer granular statuses, so some information is lost.
198
+ */
199
+ declare const statusMap21to16: Record<string, string>;
200
+ /**
201
+ * OCPP 2.1 operationalStatus → OCPP 1.6 availability type mapping.
202
+ */
203
+ declare const availabilityMap21to16: Record<string, string>;
204
+ /**
205
+ * OCPP 1.6 availability type → OCPP 2.1 operationalStatus mapping.
206
+ */
207
+ declare const availabilityMap16to21: Record<string, string>;
208
+
209
+ /**
210
+ * Combined preset dictionary.
211
+ * `presets.ocpp16_to_ocpp21` includes ALL profiles merged.
212
+ *
213
+ * For selective use, import individual presets:
214
+ * ```ts
215
+ * import { corePreset, smartChargingPreset } from "ocpp-protocol-proxy";
216
+ * proxy.translate(corePreset);
217
+ * proxy.translate(smartChargingPreset);
218
+ * ```
219
+ */
220
+ declare const presets: {
221
+ ocpp16_to_ocpp21: TranslationMap;
222
+ };
223
+
224
+ interface OCPPProtocolProxyOptions {
225
+ upstreamEndpoint: string;
226
+ upstreamProtocol: string;
227
+ sessionStore?: ISessionStore;
228
+ middlewares?: ProxyMiddleware[];
229
+ }
230
+ declare class OCPPProtocolProxy extends EventEmitter {
231
+ private options;
232
+ private translator;
233
+ private clients;
234
+ private sessionStore;
235
+ private adapters;
236
+ constructor(options: OCPPProtocolProxyOptions);
237
+ /** Register translation maps (can be called multiple times to layer presets). */
238
+ translate(map: Partial<TranslationMap>): void;
239
+ /** Start listening on a transport adapter (WS, HTTP, etc.). */
240
+ listenOnAdapter(adapter: ITransportAdapter): Promise<void>;
241
+ /**
242
+ * Execute all registered middlewares sequentially.
243
+ * If a middleware returns a message, it replaces the current message.
244
+ */
245
+ private executeMiddlewares;
246
+ private handleNewConnection;
247
+ /** Gracefully close all upstream connections and adapters. */
248
+ close(): Promise<void>;
249
+ }
250
+
251
+ export { type IConnection, type ISessionStore, type ITransportAdapter, InMemorySessionStore, MessageType, type MiddlewareDirection, type MiddlewarePhase, type OCPPMessage, OCPPProtocolProxy, type OCPPProtocolProxyOptions, OCPPTranslator, OcppWsIoAdapter, OcppWsIoConnection, type ProxyMiddleware, TelemetryMiddleware, type TranslationContext, type TranslationMap, type TranslationResult, type WsAdapterOptions, availabilityMap16to21, availabilityMap21to16, corePreset, firmwarePreset, localAuthPreset, presets, reservationPreset, smartChargingPreset, statusMap16to21, statusMap21to16 };
package/dist/index.js ADDED
@@ -0,0 +1,871 @@
1
+ // src/core/types.ts
2
+ var MessageType = /* @__PURE__ */ ((MessageType2) => {
3
+ MessageType2[MessageType2["CALL"] = 2] = "CALL";
4
+ MessageType2[MessageType2["CALLRESULT"] = 3] = "CALLRESULT";
5
+ MessageType2[MessageType2["CALLERROR"] = 4] = "CALLERROR";
6
+ return MessageType2;
7
+ })(MessageType || {});
8
+
9
+ // src/adapters/ocpp-ws-io.adapter.ts
10
+ var OcppWsIoConnection = class {
11
+ constructor(client) {
12
+ this.client = client;
13
+ this.identity = client.identity;
14
+ this.protocol = client.protocol || "ocpp1.6";
15
+ this.setupCatchAll();
16
+ }
17
+ identity;
18
+ protocol;
19
+ messageHandler;
20
+ setupCatchAll() {
21
+ this.client.handle(async (action, ctx) => {
22
+ const incomingMessage = {
23
+ type: 2 /* CALL */,
24
+ messageId: ctx.messageId || `msg-${Date.now()}`,
25
+ action,
26
+ payload: ctx.params
27
+ };
28
+ if (!this.messageHandler) {
29
+ throw new Error("No message handler attached to connection");
30
+ }
31
+ const result = await this.messageHandler(incomingMessage);
32
+ if (result) {
33
+ if (result.type === 3 /* CALLRESULT */) {
34
+ return result.payload;
35
+ }
36
+ if (result.type === 4 /* CALLERROR */) {
37
+ const err = new Error(
38
+ result.errorDescription || "Unknown error occurred during proxy processing"
39
+ );
40
+ err.code = result.errorCode;
41
+ throw err;
42
+ }
43
+ }
44
+ return {};
45
+ });
46
+ }
47
+ async send(message) {
48
+ if (message.type === 2 /* CALL */) {
49
+ const rawResult = await this.client.call(message.action, message.payload);
50
+ return {
51
+ type: 3 /* CALLRESULT */,
52
+ messageId: message.messageId,
53
+ payload: rawResult
54
+ };
55
+ }
56
+ return void 0;
57
+ }
58
+ onMessage(handler) {
59
+ this.messageHandler = handler;
60
+ }
61
+ onClose(handler) {
62
+ this.client.on("close", handler);
63
+ }
64
+ };
65
+ var OcppWsIoAdapter = class {
66
+ server;
67
+ port;
68
+ protocols;
69
+ httpServer;
70
+ constructor(options) {
71
+ this.port = options.port;
72
+ this.protocols = options.protocols;
73
+ }
74
+ async listen(onConnection) {
75
+ const { OCPPServer } = await import("ocpp-ws-io");
76
+ this.server = new OCPPServer({ protocols: this.protocols });
77
+ this.server.on("client", (client) => {
78
+ const conn = new OcppWsIoConnection(client);
79
+ onConnection(conn);
80
+ });
81
+ this.httpServer = await this.server.listen(this.port);
82
+ }
83
+ async close() {
84
+ if (this.server) {
85
+ await this.server.close();
86
+ }
87
+ }
88
+ };
89
+
90
+ // src/core/session.ts
91
+ var InMemorySessionStore = class {
92
+ // Map<identity, Map<key, value>>
93
+ store = /* @__PURE__ */ new Map();
94
+ async set(identity, key, value) {
95
+ if (!this.store.has(identity)) {
96
+ this.store.set(identity, /* @__PURE__ */ new Map());
97
+ }
98
+ this.store.get(identity).set(key, value);
99
+ }
100
+ async get(identity, key) {
101
+ const session = this.store.get(identity);
102
+ return session ? session.get(key) : void 0;
103
+ }
104
+ async delete(identity, key) {
105
+ const session = this.store.get(identity);
106
+ if (session) {
107
+ session.delete(key);
108
+ }
109
+ }
110
+ async clear(identity) {
111
+ this.store.delete(identity);
112
+ }
113
+ };
114
+
115
+ // src/core/translator.ts
116
+ var OCPPTranslator = class {
117
+ constructor(translationMap) {
118
+ this.translationMap = translationMap;
119
+ }
120
+ updateMap(map) {
121
+ this.translationMap.upstream = {
122
+ ...this.translationMap.upstream,
123
+ ...map.upstream
124
+ };
125
+ this.translationMap.downstream = {
126
+ ...this.translationMap.downstream,
127
+ ...map.downstream
128
+ };
129
+ if (map.responses) {
130
+ this.translationMap.responses = {
131
+ ...this.translationMap.responses,
132
+ ...map.responses
133
+ };
134
+ }
135
+ if (map.errors) {
136
+ this.translationMap.errors = {
137
+ ...this.translationMap.errors,
138
+ ...map.errors
139
+ };
140
+ }
141
+ }
142
+ async translateUpstreamCall(message, context) {
143
+ const key = `${context.sourceProtocol}:${message.action}`;
144
+ const mapper = this.translationMap.upstream[key];
145
+ if (!mapper) {
146
+ return message;
147
+ }
148
+ const translated = await mapper(message.payload, context);
149
+ return {
150
+ type: 2 /* CALL */,
151
+ messageId: message.messageId,
152
+ action: translated.action || message.action,
153
+ payload: translated.payload
154
+ };
155
+ }
156
+ async translateDownstreamCall(message, context) {
157
+ const key = `${context.targetProtocol}:${message.action}`;
158
+ const mapper = this.translationMap.downstream[key];
159
+ if (!mapper) {
160
+ return message;
161
+ }
162
+ const translated = await mapper(message.payload, context);
163
+ return {
164
+ type: 2 /* CALL */,
165
+ messageId: message.messageId,
166
+ action: translated.action || message.action,
167
+ payload: translated.payload
168
+ };
169
+ }
170
+ async translateCallResult(message, originalAction, context) {
171
+ const responseKey = `${context.targetProtocol}:${originalAction}Response`;
172
+ const responseMapper = this.translationMap.responses?.[responseKey];
173
+ if (responseMapper) {
174
+ const translatedPayload = await responseMapper(message.payload, context);
175
+ return {
176
+ type: 3 /* CALLRESULT */,
177
+ messageId: message.messageId,
178
+ payload: translatedPayload
179
+ };
180
+ }
181
+ return message;
182
+ }
183
+ async translateCallError(message, context) {
184
+ const errorKey = `${context.sourceProtocol}:Error`;
185
+ const errorMapper = this.translationMap.errors?.[errorKey];
186
+ if (errorMapper) {
187
+ const translated = await errorMapper(
188
+ message.errorCode,
189
+ message.errorDescription,
190
+ message.errorDetails,
191
+ context
192
+ );
193
+ return {
194
+ type: 4 /* CALLERROR */,
195
+ messageId: message.messageId,
196
+ errorCode: translated.errorCode,
197
+ errorDescription: translated.errorDescription,
198
+ errorDetails: translated.errorDetails
199
+ };
200
+ }
201
+ return message;
202
+ }
203
+ };
204
+
205
+ // src/middlewares/telemetry.ts
206
+ var TelemetryMiddleware = async (message, context, _direction, phase) => {
207
+ if (phase === "pre") {
208
+ await context.session.set(
209
+ context.identity,
210
+ `telemetryStart_${message.messageId}`,
211
+ Date.now()
212
+ );
213
+ } else if (phase === "post") {
214
+ const startTime = await context.session.get(
215
+ context.identity,
216
+ `telemetryStart_${message.messageId}`
217
+ );
218
+ if (startTime) {
219
+ const _latency = Date.now() - startTime;
220
+ await context.session.delete(
221
+ context.identity,
222
+ `telemetryStart_${message.messageId}`
223
+ );
224
+ }
225
+ }
226
+ return void 0;
227
+ };
228
+
229
+ // src/presets/core.ts
230
+ import { randomUUID } from "crypto";
231
+
232
+ // src/presets/status-enums.ts
233
+ var statusMap16to21 = {
234
+ Available: "Available",
235
+ Preparing: "Occupied",
236
+ Charging: "Occupied",
237
+ SuspendedEVSE: "Occupied",
238
+ SuspendedEV: "Occupied",
239
+ Finishing: "Occupied",
240
+ Reserved: "Reserved",
241
+ Unavailable: "Unavailable",
242
+ Faulted: "Faulted"
243
+ };
244
+ var statusMap21to16 = {
245
+ Available: "Available",
246
+ Occupied: "Charging",
247
+ // Best guess — could be Preparing/SuspendedEV/etc.
248
+ Reserved: "Reserved",
249
+ Unavailable: "Unavailable",
250
+ Faulted: "Faulted"
251
+ };
252
+ var availabilityMap21to16 = {
253
+ Operative: "Available",
254
+ Inoperative: "Unavailable"
255
+ };
256
+ var availabilityMap16to21 = {
257
+ Available: "Operative",
258
+ Unavailable: "Inoperative"
259
+ };
260
+
261
+ // src/presets/core.ts
262
+ var txIdCounter = 1;
263
+ var corePreset = {
264
+ upstream: {
265
+ "ocpp1.6:BootNotification": (params) => ({
266
+ action: "BootNotification",
267
+ payload: {
268
+ reason: "PowerUp",
269
+ chargingStation: {
270
+ model: params.chargePointModel,
271
+ vendorName: params.chargePointVendor,
272
+ firmwareVersion: params.firmwareVersion,
273
+ serialNumber: params.chargePointSerialNumber
274
+ }
275
+ }
276
+ }),
277
+ "ocpp1.6:Heartbeat": () => ({
278
+ action: "Heartbeat",
279
+ payload: {}
280
+ }),
281
+ "ocpp1.6:StatusNotification": (params) => ({
282
+ action: "StatusNotification",
283
+ payload: {
284
+ timestamp: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
285
+ connectorStatus: statusMap16to21[params.status] || params.status,
286
+ evseId: params.connectorId,
287
+ connectorId: params.connectorId
288
+ }
289
+ }),
290
+ "ocpp1.6:Authorize": (params) => ({
291
+ action: "Authorize",
292
+ payload: {
293
+ idToken: {
294
+ idToken: params.idTag,
295
+ type: "ISO14443"
296
+ }
297
+ }
298
+ }),
299
+ "ocpp1.6:StartTransaction": async (params, ctx) => {
300
+ const proxyGeneratedTxId = randomUUID();
301
+ await ctx.session.set(ctx.identity, "pendingStartTx", {
302
+ uuid: proxyGeneratedTxId,
303
+ connectorId: params.connectorId
304
+ });
305
+ return {
306
+ action: "TransactionEvent",
307
+ payload: {
308
+ eventType: "Started",
309
+ timestamp: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
310
+ triggerReason: "Authorized",
311
+ seqNo: 0,
312
+ transactionInfo: {
313
+ transactionId: proxyGeneratedTxId
314
+ },
315
+ idToken: {
316
+ idToken: params.idTag,
317
+ type: "ISO14443"
318
+ },
319
+ evse: {
320
+ id: params.connectorId,
321
+ connectorId: params.connectorId
322
+ },
323
+ meterValue: [
324
+ {
325
+ timestamp: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
326
+ sampledValue: [
327
+ {
328
+ value: params.meterStart,
329
+ measurand: "Energy.Active.Import.Register"
330
+ }
331
+ ]
332
+ }
333
+ ]
334
+ }
335
+ };
336
+ },
337
+ "ocpp1.6:StopTransaction": async (params, ctx) => {
338
+ const numericId = params.transactionId;
339
+ const uuid = await ctx.session.get(
340
+ ctx.identity,
341
+ `txId_int2uuid_${numericId}`
342
+ );
343
+ return {
344
+ action: "TransactionEvent",
345
+ payload: {
346
+ eventType: "Ended",
347
+ timestamp: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
348
+ triggerReason: params.reason || "Local",
349
+ seqNo: 1,
350
+ transactionInfo: {
351
+ transactionId: uuid || randomUUID(),
352
+ stoppedReason: params.reason || "Local"
353
+ },
354
+ idToken: params.idTag ? {
355
+ idToken: params.idTag,
356
+ type: "ISO14443"
357
+ } : void 0,
358
+ meterValue: [
359
+ {
360
+ timestamp: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
361
+ sampledValue: [
362
+ {
363
+ value: params.meterStop,
364
+ measurand: "Energy.Active.Import.Register"
365
+ }
366
+ ]
367
+ }
368
+ ]
369
+ }
370
+ };
371
+ },
372
+ "ocpp1.6:MeterValues": async (params, ctx) => {
373
+ const numericId = params.transactionId;
374
+ const uuid = numericId ? await ctx.session.get(
375
+ ctx.identity,
376
+ `txId_int2uuid_${numericId}`
377
+ ) : void 0;
378
+ return {
379
+ action: "TransactionEvent",
380
+ payload: {
381
+ eventType: "Updated",
382
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
383
+ triggerReason: "MeterValuePeriodic",
384
+ seqNo: 0,
385
+ transactionInfo: uuid ? { transactionId: uuid } : void 0,
386
+ evse: {
387
+ id: params.connectorId,
388
+ connectorId: params.connectorId
389
+ },
390
+ meterValue: (params.meterValue || []).map((mv) => ({
391
+ timestamp: mv.timestamp,
392
+ sampledValue: (mv.sampledValue || []).map((sv) => ({
393
+ value: sv.value,
394
+ measurand: sv.measurand || "Energy.Active.Import.Register",
395
+ unit: sv.unit,
396
+ context: sv.context,
397
+ location: sv.location
398
+ }))
399
+ }))
400
+ }
401
+ };
402
+ }
403
+ },
404
+ downstream: {
405
+ "ocpp2.1:RemoteStartTransaction": (params) => ({
406
+ action: "RemoteStartTransaction",
407
+ payload: {
408
+ connectorId: params.evseId || 1,
409
+ idTag: params.idToken?.idToken
410
+ }
411
+ }),
412
+ "ocpp2.1:RemoteStopTransaction": async (params, ctx) => {
413
+ const uuid = params.transactionId;
414
+ const numericId = uuid ? await ctx.session.get(ctx.identity, `txId_uuid2int_${uuid}`) : void 0;
415
+ return {
416
+ action: "RemoteStopTransaction",
417
+ payload: {
418
+ transactionId: numericId ?? 0
419
+ }
420
+ };
421
+ },
422
+ "ocpp2.1:ChangeAvailability": (params) => ({
423
+ action: "ChangeAvailability",
424
+ payload: {
425
+ connectorId: params.evse?.connectorId || params.evse?.id || 0,
426
+ type: params.operationalStatus === "Operative" ? "Operative" : "Inoperative"
427
+ }
428
+ }),
429
+ "ocpp2.1:Reset": (params) => ({
430
+ action: "Reset",
431
+ payload: {
432
+ type: params.type === "OnIdle" ? "Soft" : params.type
433
+ }
434
+ }),
435
+ "ocpp2.1:UnlockConnector": (params) => ({
436
+ action: "UnlockConnector",
437
+ payload: {
438
+ connectorId: params.connectorId || params.evseId || 1
439
+ }
440
+ }),
441
+ "ocpp2.1:TriggerMessage": (params) => ({
442
+ action: "TriggerMessage",
443
+ payload: {
444
+ requestedMessage: params.requestedMessage,
445
+ connectorId: params.evse?.connectorId || params.evse?.id
446
+ }
447
+ })
448
+ },
449
+ responses: {
450
+ "ocpp2.1:BootNotificationResponse": (params) => ({
451
+ currentTime: params.currentTime,
452
+ interval: params.interval,
453
+ status: params.status
454
+ }),
455
+ "ocpp2.1:HeartbeatResponse": (params) => ({
456
+ currentTime: params.currentTime
457
+ }),
458
+ "ocpp2.1:StatusNotificationResponse": () => ({}),
459
+ "ocpp2.1:AuthorizeResponse": (params) => ({
460
+ idTagInfo: {
461
+ status: params.idTokenInfo?.status || "Accepted"
462
+ }
463
+ }),
464
+ "ocpp2.1:TransactionEventResponse": async (params, ctx) => {
465
+ const pending = await ctx.session.get(ctx.identity, "pendingStartTx");
466
+ const numericTxId = txIdCounter++;
467
+ if (pending) {
468
+ await ctx.session.set(
469
+ ctx.identity,
470
+ `txId_int2uuid_${numericTxId}`,
471
+ pending.uuid
472
+ );
473
+ await ctx.session.set(
474
+ ctx.identity,
475
+ `txId_uuid2int_${pending.uuid}`,
476
+ numericTxId
477
+ );
478
+ await ctx.session.delete(ctx.identity, "pendingStartTx");
479
+ }
480
+ return {
481
+ idTagInfo: {
482
+ status: params.idTokenInfo?.status || "Accepted"
483
+ },
484
+ transactionId: numericTxId
485
+ };
486
+ }
487
+ },
488
+ errors: {
489
+ "ocpp2.1:Error": (errorCode, errorDescription, errorDetails) => {
490
+ const errorMap = {
491
+ SecurityError: "InternalError",
492
+ FormatViolation: "FormationViolation",
493
+ MessageTypeNotSupported: "NotSupported",
494
+ PropertyConstraintViolation: "PropertyConstraintViolation",
495
+ OccurrenceConstraintViolation: "OccurenceConstraintViolation",
496
+ TypeConstraintViolation: "TypeConstraintViolation",
497
+ GenericError: "GenericError",
498
+ NotImplemented: "NotImplemented",
499
+ NotSupported: "NotSupported",
500
+ ProtocolError: "ProtocolError",
501
+ RpcFrameworkError: "InternalError"
502
+ };
503
+ return {
504
+ errorCode: errorMap[errorCode] || "InternalError",
505
+ errorDescription,
506
+ errorDetails
507
+ };
508
+ }
509
+ }
510
+ };
511
+
512
+ // src/presets/firmware.ts
513
+ var firmwarePreset = {
514
+ upstream: {
515
+ "ocpp1.6:FirmwareStatusNotification": (params) => ({
516
+ action: "FirmwareStatusNotification",
517
+ payload: {
518
+ status: params.status
519
+ }
520
+ }),
521
+ "ocpp1.6:DiagnosticsStatusNotification": (params) => ({
522
+ action: "LogStatusNotification",
523
+ payload: {
524
+ status: params.status === "Uploaded" ? "Uploaded" : params.status,
525
+ requestId: 0
526
+ }
527
+ })
528
+ },
529
+ downstream: {
530
+ "ocpp2.1:UpdateFirmware": (params) => ({
531
+ action: "UpdateFirmware",
532
+ payload: {
533
+ location: params.firmware?.location || params.location,
534
+ retrieveDate: params.firmware?.retrieveDateTime || params.retrieveDate,
535
+ retries: params.retries,
536
+ retryInterval: params.retryInterval
537
+ }
538
+ }),
539
+ "ocpp2.1:GetLog": (params) => ({
540
+ action: "GetDiagnostics",
541
+ payload: {
542
+ location: params.log?.remoteLocation || "",
543
+ startTime: params.log?.oldestTimestamp,
544
+ stopTime: params.log?.latestTimestamp,
545
+ retries: params.retries,
546
+ retryInterval: params.retryInterval
547
+ }
548
+ })
549
+ }
550
+ };
551
+
552
+ // src/presets/local-auth.ts
553
+ var localAuthPreset = {
554
+ downstream: {
555
+ "ocpp2.1:GetLocalListVersion": () => ({
556
+ action: "GetLocalListVersion",
557
+ payload: {}
558
+ }),
559
+ "ocpp2.1:SendLocalList": (params) => ({
560
+ action: "SendLocalList",
561
+ payload: {
562
+ listVersion: params.versionNumber,
563
+ updateType: params.updateType,
564
+ localAuthorizationList: (params.localAuthorizationList || []).map(
565
+ (entry) => ({
566
+ idTag: entry.idToken?.idToken,
567
+ idTagInfo: entry.idTokenInfo ? {
568
+ status: entry.idTokenInfo.status,
569
+ expiryDate: entry.idTokenInfo.cacheExpiryDateTime
570
+ } : void 0
571
+ })
572
+ )
573
+ }
574
+ })
575
+ }
576
+ };
577
+
578
+ // src/presets/reservation.ts
579
+ var reservationPreset = {
580
+ downstream: {
581
+ "ocpp2.1:ReserveNow": (params) => ({
582
+ action: "ReserveNow",
583
+ payload: {
584
+ connectorId: params.evseId || 0,
585
+ expiryDate: params.expiryDateTime,
586
+ idTag: params.idToken?.idToken,
587
+ reservationId: params.id
588
+ }
589
+ }),
590
+ "ocpp2.1:CancelReservation": (params) => ({
591
+ action: "CancelReservation",
592
+ payload: {
593
+ reservationId: params.reservationId
594
+ }
595
+ })
596
+ }
597
+ };
598
+
599
+ // src/presets/smart-charging.ts
600
+ var smartChargingPreset = {
601
+ downstream: {
602
+ "ocpp2.1:SetChargingProfile": (params) => ({
603
+ action: "SetChargingProfile",
604
+ payload: {
605
+ connectorId: params.evseId,
606
+ csChargingProfiles: params.chargingProfile
607
+ }
608
+ }),
609
+ "ocpp2.1:ClearChargingProfile": (params) => ({
610
+ action: "ClearChargingProfile",
611
+ payload: {
612
+ id: params.chargingProfileId,
613
+ connectorId: params.chargingProfileCriteria?.evseId,
614
+ chargingProfilePurpose: params.chargingProfileCriteria?.chargingProfilePurpose,
615
+ stackLevel: params.chargingProfileCriteria?.stackLevel
616
+ }
617
+ }),
618
+ "ocpp2.1:GetCompositeSchedule": (params) => ({
619
+ action: "GetCompositeSchedule",
620
+ payload: {
621
+ connectorId: params.evseId,
622
+ duration: params.duration,
623
+ chargingRateUnit: params.chargingRateUnit
624
+ }
625
+ })
626
+ }
627
+ };
628
+
629
+ // src/presets/index.ts
630
+ function mergePresets(...maps) {
631
+ const merged = {
632
+ upstream: {},
633
+ downstream: {},
634
+ responses: {},
635
+ errors: {}
636
+ };
637
+ for (const map of maps) {
638
+ if (map.upstream) Object.assign(merged.upstream, map.upstream);
639
+ if (map.downstream) Object.assign(merged.downstream, map.downstream);
640
+ if (map.responses) Object.assign(merged.responses, map.responses);
641
+ if (map.errors) Object.assign(merged.errors, map.errors);
642
+ }
643
+ return merged;
644
+ }
645
+ var presets = {
646
+ ocpp16_to_ocpp21: mergePresets(
647
+ corePreset,
648
+ smartChargingPreset,
649
+ firmwarePreset,
650
+ reservationPreset,
651
+ localAuthPreset
652
+ )
653
+ };
654
+
655
+ // src/proxy.ts
656
+ import { EventEmitter } from "events";
657
+ var OCPPProtocolProxy = class extends EventEmitter {
658
+ constructor(options) {
659
+ super();
660
+ this.options = options;
661
+ this.translator = new OCPPTranslator({ upstream: {}, downstream: {} });
662
+ this.sessionStore = options.sessionStore || new InMemorySessionStore();
663
+ }
664
+ translator;
665
+ clients = /* @__PURE__ */ new Map();
666
+ sessionStore;
667
+ adapters = [];
668
+ /** Register translation maps (can be called multiple times to layer presets). */
669
+ translate(map) {
670
+ this.translator.updateMap(map);
671
+ }
672
+ /** Start listening on a transport adapter (WS, HTTP, etc.). */
673
+ async listenOnAdapter(adapter) {
674
+ this.adapters.push(adapter);
675
+ await adapter.listen((connection) => this.handleNewConnection(connection));
676
+ }
677
+ /**
678
+ * Execute all registered middlewares sequentially.
679
+ * If a middleware returns a message, it replaces the current message.
680
+ */
681
+ async executeMiddlewares(message, context, direction, phase) {
682
+ let currentMsg = message;
683
+ for (const mw of this.options.middlewares || []) {
684
+ try {
685
+ const result = await mw(currentMsg, context, direction, phase);
686
+ if (result) {
687
+ currentMsg = result;
688
+ }
689
+ } catch (err) {
690
+ this.emit("middlewareError", err, currentMsg, context);
691
+ }
692
+ }
693
+ return currentMsg;
694
+ }
695
+ handleNewConnection(evseConnection) {
696
+ const identity = evseConnection.identity;
697
+ const sourceProtocol = evseConnection.protocol;
698
+ const targetProtocol = this.options.upstreamProtocol;
699
+ const context = {
700
+ identity,
701
+ sourceProtocol,
702
+ targetProtocol,
703
+ session: this.sessionStore
704
+ };
705
+ this.emit("connection", identity, sourceProtocol);
706
+ import("ocpp-ws-io").then(({ OCPPClient }) => {
707
+ const upstreamClient = new OCPPClient({
708
+ endpoint: this.options.upstreamEndpoint,
709
+ protocols: [targetProtocol],
710
+ identity,
711
+ strictMode: false
712
+ });
713
+ this.clients.set(identity, upstreamClient);
714
+ const connectionPromise = upstreamClient.connect();
715
+ evseConnection.onMessage(async (msg) => {
716
+ await connectionPromise;
717
+ if (msg.type === 2 /* CALL */) {
718
+ try {
719
+ const preMsg = await this.executeMiddlewares(
720
+ msg,
721
+ context,
722
+ "upstream",
723
+ "pre"
724
+ );
725
+ const translatedCall = await this.translator.translateUpstreamCall(
726
+ preMsg,
727
+ context
728
+ );
729
+ const postMsg = await this.executeMiddlewares(
730
+ translatedCall,
731
+ context,
732
+ "upstream",
733
+ "post"
734
+ );
735
+ const rawResponse = await upstreamClient.call(
736
+ postMsg.action,
737
+ postMsg.payload
738
+ );
739
+ const responseMsg = {
740
+ type: 3 /* CALLRESULT */,
741
+ messageId: msg.messageId,
742
+ payload: rawResponse
743
+ };
744
+ const preResMsg = await this.executeMiddlewares(
745
+ responseMsg,
746
+ context,
747
+ "response",
748
+ "pre"
749
+ );
750
+ const translatedResponse = await this.translator.translateCallResult(
751
+ preResMsg,
752
+ translatedCall.action,
753
+ context
754
+ );
755
+ return await this.executeMiddlewares(
756
+ translatedResponse,
757
+ context,
758
+ "response",
759
+ "post"
760
+ );
761
+ } catch (err) {
762
+ const errMessage = {
763
+ type: 4 /* CALLERROR */,
764
+ messageId: msg.messageId,
765
+ errorCode: err.code || "InternalError",
766
+ errorDescription: err.message,
767
+ errorDetails: {}
768
+ };
769
+ this.emit("translationError", err, msg, context);
770
+ return await this.executeMiddlewares(
771
+ errMessage,
772
+ context,
773
+ "error",
774
+ "post"
775
+ );
776
+ }
777
+ }
778
+ return void 0;
779
+ });
780
+ upstreamClient.handle(async (action, ctx) => {
781
+ const downstreamCall = {
782
+ type: 2 /* CALL */,
783
+ messageId: ctx.messageId || `csms-${Date.now()}`,
784
+ action,
785
+ payload: ctx.params
786
+ };
787
+ try {
788
+ const preMsg = await this.executeMiddlewares(
789
+ downstreamCall,
790
+ context,
791
+ "downstream",
792
+ "pre"
793
+ );
794
+ const translated = await this.translator.translateDownstreamCall(
795
+ preMsg,
796
+ context
797
+ );
798
+ const postMsg = await this.executeMiddlewares(
799
+ translated,
800
+ context,
801
+ "downstream",
802
+ "post"
803
+ );
804
+ const evseResponse = await evseConnection.send(postMsg);
805
+ if (evseResponse && evseResponse.type === 3 /* CALLRESULT */) {
806
+ const preResMsg = await this.executeMiddlewares(
807
+ evseResponse,
808
+ context,
809
+ "response",
810
+ "pre"
811
+ );
812
+ const mappedResponse = await this.translator.translateCallResult(
813
+ preResMsg,
814
+ downstreamCall.action,
815
+ context
816
+ );
817
+ const postResMsg = await this.executeMiddlewares(
818
+ mappedResponse,
819
+ context,
820
+ "response",
821
+ "post"
822
+ );
823
+ return postResMsg.payload;
824
+ }
825
+ } catch (err) {
826
+ this.emit("translationError", err, downstreamCall, context);
827
+ throw err;
828
+ }
829
+ });
830
+ evseConnection.onClose(() => {
831
+ upstreamClient.close();
832
+ this.sessionStore.clear(identity);
833
+ this.clients.delete(identity);
834
+ this.emit("disconnect", identity);
835
+ });
836
+ });
837
+ }
838
+ /** Gracefully close all upstream connections and adapters. */
839
+ async close() {
840
+ for (const client of this.clients.values()) {
841
+ try {
842
+ await client.close();
843
+ } catch {
844
+ }
845
+ }
846
+ this.clients.clear();
847
+ for (const adapter of this.adapters) {
848
+ await adapter.close();
849
+ }
850
+ this.adapters = [];
851
+ }
852
+ };
853
+ export {
854
+ InMemorySessionStore,
855
+ MessageType,
856
+ OCPPProtocolProxy,
857
+ OCPPTranslator,
858
+ OcppWsIoAdapter,
859
+ OcppWsIoConnection,
860
+ TelemetryMiddleware,
861
+ availabilityMap16to21,
862
+ availabilityMap21to16,
863
+ corePreset,
864
+ firmwarePreset,
865
+ localAuthPreset,
866
+ presets,
867
+ reservationPreset,
868
+ smartChargingPreset,
869
+ statusMap16to21,
870
+ statusMap21to16
871
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "ocpp-protocol-proxy",
3
+ "version": "0.1.1",
4
+ "description": "Transport-agnostic OCPP version translation proxy — translate any OCPP version to any other with pluggable middleware, stateful sessions, and spec-compliant presets.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./presets": {
16
+ "types": "./dist/presets/index.d.ts",
17
+ "import": "./dist/presets/index.mjs",
18
+ "require": "./dist/presets/index.js"
19
+ },
20
+ "./adapters": {
21
+ "types": "./dist/adapters/ocpp-ws-io.adapter.d.ts",
22
+ "import": "./dist/adapters/ocpp-ws-io.adapter.mjs",
23
+ "require": "./dist/adapters/ocpp-ws-io.adapter.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "test": "vitest run"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "keywords": [
35
+ "ocpp",
36
+ "ocpp1.6",
37
+ "ocpp2.0.1",
38
+ "ocpp2.1",
39
+ "protocol-proxy",
40
+ "protocol-translation",
41
+ "ev-charging",
42
+ "csms",
43
+ "evse",
44
+ "charge-point",
45
+ "middleware",
46
+ "version-translation",
47
+ "electric-vehicle",
48
+ "e-mobility",
49
+ "typescript"
50
+ ],
51
+ "author": "Rohit Tiwari <rohit@rohittiwari.me>",
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/rohittiwari-dev/ocpp-ws-io",
56
+ "directory": "packages/ocpp-protocol-proxy"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/rohittiwari-dev/ocpp-ws-io/issues"
60
+ },
61
+ "homepage": "https://ocpp-ws-io.rohittiwari.me/docs/protocol-proxy",
62
+ "dependencies": {
63
+ "ocpp-ws-io": "*"
64
+ },
65
+ "files": [
66
+ "dist",
67
+ "README.md",
68
+ "LICENSE"
69
+ ],
70
+ "devDependencies": {
71
+ "@types/node": "^20.19.35",
72
+ "tsup": "^8.5.1",
73
+ "typescript": "^5.9.3",
74
+ "vitest": "^3.2.4"
75
+ }
76
+ }