kirby-ocpp 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +192 -88
- package/THIRD_PARTY_NOTICES.md +6 -2
- package/bin/kirby-ocpp.js +77 -0
- package/package.json +23 -44
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -72
- package/dist/config.d.ts +0 -2
- package/dist/config.js +0 -53
- package/dist/ocpp/client.d.ts +0 -59
- package/dist/ocpp/client.js +0 -299
- package/dist/ocpp/schema-validator.d.ts +0 -15
- package/dist/ocpp/schema-validator.js +0 -75
- package/dist/ocpp/schemas/json/Authorize.json +0 -16
- package/dist/ocpp/schemas/json/AuthorizeResponse.json +0 -40
- package/dist/ocpp/schemas/json/BootNotification.json +0 -49
- package/dist/ocpp/schemas/json/BootNotificationResponse.json +0 -30
- package/dist/ocpp/schemas/json/CancelReservation.json +0 -15
- package/dist/ocpp/schemas/json/CancelReservationResponse.json +0 -20
- package/dist/ocpp/schemas/json/ChangeAvailability.json +0 -24
- package/dist/ocpp/schemas/json/ChangeAvailabilityResponse.json +0 -21
- package/dist/ocpp/schemas/json/ChangeConfiguration.json +0 -21
- package/dist/ocpp/schemas/json/ChangeConfigurationResponse.json +0 -22
- package/dist/ocpp/schemas/json/ClearCache.json +0 -8
- package/dist/ocpp/schemas/json/ClearCacheResponse.json +0 -20
- package/dist/ocpp/schemas/json/ClearChargingProfile.json +0 -27
- package/dist/ocpp/schemas/json/ClearChargingProfileResponse.json +0 -20
- package/dist/ocpp/schemas/json/DataTransfer.json +0 -23
- package/dist/ocpp/schemas/json/DataTransferResponse.json +0 -25
- package/dist/ocpp/schemas/json/DiagnosticsStatusNotification.json +0 -22
- package/dist/ocpp/schemas/json/DiagnosticsStatusNotificationResponse.json +0 -8
- package/dist/ocpp/schemas/json/FirmwareStatusNotification.json +0 -25
- package/dist/ocpp/schemas/json/FirmwareStatusNotificationResponse.json +0 -8
- package/dist/ocpp/schemas/json/GetCompositeSchedule.json +0 -27
- package/dist/ocpp/schemas/json/GetCompositeScheduleResponse.json +0 -79
- package/dist/ocpp/schemas/json/GetConfiguration.json +0 -16
- package/dist/ocpp/schemas/json/GetConfigurationResponse.json +0 -40
- package/dist/ocpp/schemas/json/GetDiagnostics.json +0 -30
- package/dist/ocpp/schemas/json/GetDiagnosticsResponse.json +0 -13
- package/dist/ocpp/schemas/json/GetLocalListVersion.json +0 -8
- package/dist/ocpp/schemas/json/GetLocalListVersionResponse.json +0 -15
- package/dist/ocpp/schemas/json/Heartbeat.json +0 -8
- package/dist/ocpp/schemas/json/HeartbeatResponse.json +0 -16
- package/dist/ocpp/schemas/json/MeterValues.json +0 -151
- package/dist/ocpp/schemas/json/MeterValuesResponse.json +0 -8
- package/dist/ocpp/schemas/json/RemoteStartTransaction.json +0 -127
- package/dist/ocpp/schemas/json/RemoteStartTransactionResponse.json +0 -20
- package/dist/ocpp/schemas/json/RemoteStopTransaction.json +0 -15
- package/dist/ocpp/schemas/json/RemoteStopTransactionResponse.json +0 -20
- package/dist/ocpp/schemas/json/ReserveNow.json +0 -33
- package/dist/ocpp/schemas/json/ReserveNowResponse.json +0 -23
- package/dist/ocpp/schemas/json/Reset.json +0 -20
- package/dist/ocpp/schemas/json/ResetResponse.json +0 -20
- package/dist/ocpp/schemas/json/SendLocalList.json +0 -68
- package/dist/ocpp/schemas/json/SendLocalListResponse.json +0 -22
- package/dist/ocpp/schemas/json/SetChargingProfile.json +0 -124
- package/dist/ocpp/schemas/json/SetChargingProfileResponse.json +0 -21
- package/dist/ocpp/schemas/json/StartTransaction.json +0 -32
- package/dist/ocpp/schemas/json/StartTransactionResponse.json +0 -44
- package/dist/ocpp/schemas/json/StatusNotification.json +0 -70
- package/dist/ocpp/schemas/json/StatusNotificationResponse.json +0 -8
- package/dist/ocpp/schemas/json/StopTransaction.json +0 -177
- package/dist/ocpp/schemas/json/StopTransactionResponse.json +0 -37
- package/dist/ocpp/schemas/json/TriggerMessage.json +0 -27
- package/dist/ocpp/schemas/json/TriggerMessageResponse.json +0 -21
- package/dist/ocpp/schemas/json/UnlockConnector.json +0 -15
- package/dist/ocpp/schemas/json/UnlockConnectorResponse.json +0 -21
- package/dist/ocpp/schemas/json/UpdateFirmware.json +0 -27
- package/dist/ocpp/schemas/json/UpdateFirmwareResponse.json +0 -8
- package/dist/ocpp/types.d.ts +0 -106
- package/dist/ocpp/types.js +0 -1
- package/dist/station/authorization.d.ts +0 -20
- package/dist/station/authorization.js +0 -78
- package/dist/station/charging-profile-registry.d.ts +0 -18
- package/dist/station/charging-profile-registry.js +0 -125
- package/dist/station/configuration-commands.d.ts +0 -11
- package/dist/station/configuration-commands.js +0 -29
- package/dist/station/configuration-registry.d.ts +0 -13
- package/dist/station/configuration-registry.js +0 -39
- package/dist/station/configuration.d.ts +0 -7
- package/dist/station/configuration.js +0 -88
- package/dist/station/connector-registry.d.ts +0 -25
- package/dist/station/connector-registry.js +0 -89
- package/dist/station/connector-status.d.ts +0 -7
- package/dist/station/connector-status.js +0 -66
- package/dist/station/local-auth-list.d.ts +0 -8
- package/dist/station/local-auth-list.js +0 -31
- package/dist/station/maintenance-lifecycle.d.ts +0 -25
- package/dist/station/maintenance-lifecycle.js +0 -110
- package/dist/station/meter-value-scheduler.d.ts +0 -21
- package/dist/station/meter-value-scheduler.js +0 -78
- package/dist/station/metering.d.ts +0 -15
- package/dist/station/metering.js +0 -138
- package/dist/station/ocpp-message.d.ts +0 -2
- package/dist/station/ocpp-message.js +0 -9
- package/dist/station/offline-transaction-sync.d.ts +0 -29
- package/dist/station/offline-transaction-sync.js +0 -99
- package/dist/station/payload.d.ts +0 -4
- package/dist/station/payload.js +0 -12
- package/dist/station/persistence.d.ts +0 -28
- package/dist/station/persistence.js +0 -206
- package/dist/station/reservation-rules.d.ts +0 -3
- package/dist/station/reservation-rules.js +0 -18
- package/dist/station/reservation-scheduler.d.ts +0 -11
- package/dist/station/reservation-scheduler.js +0 -33
- package/dist/station/smart-charging.d.ts +0 -45
- package/dist/station/smart-charging.js +0 -251
- package/dist/station/status-scheduler.d.ts +0 -16
- package/dist/station/status-scheduler.js +0 -58
- package/dist/station/transaction-message-retry.d.ts +0 -3
- package/dist/station/transaction-message-retry.js +0 -7
- package/dist/station/transaction-rules.d.ts +0 -7
- package/dist/station/transaction-rules.js +0 -34
- package/dist/station/trigger-message.d.ts +0 -2
- package/dist/station/trigger-message.js +0 -11
- package/dist/station-store.d.ts +0 -41
- package/dist/station-store.js +0 -104
- package/dist/station.d.ts +0 -113
- package/dist/station.js +0 -1204
- package/dist/tui.d.ts +0 -96
- package/dist/tui.js +0 -1656
- package/docs/OCPP_1.6J_CONFORMANCE.md +0 -61
package/dist/config.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
const configSchema = z.object({
|
|
3
|
-
centralSystemUrl: z
|
|
4
|
-
.string({
|
|
5
|
-
required_error: "Use --url or OCPP_URL to set the Central System WebSocket URL"
|
|
6
|
-
})
|
|
7
|
-
.url(),
|
|
8
|
-
chargePointId: z.string().min(1),
|
|
9
|
-
vendor: z.string().min(1),
|
|
10
|
-
model: z.string().min(1),
|
|
11
|
-
connectorId: z.coerce.number().int().positive(),
|
|
12
|
-
connectorCount: z.coerce.number().int().positive(),
|
|
13
|
-
heartbeatIntervalSeconds: z.coerce.number().int().positive(),
|
|
14
|
-
persistState: z.coerce.boolean(),
|
|
15
|
-
stateDirectory: z.string().min(1).optional(),
|
|
16
|
-
webSocketSubprotocol: z.string().min(1),
|
|
17
|
-
webSocketPingIntervalSeconds: z.coerce.number().int().nonnegative(),
|
|
18
|
-
webSocketReconnectEnabled: z.coerce.boolean(),
|
|
19
|
-
webSocketReconnectInitialDelayMs: z.coerce.number().int().nonnegative(),
|
|
20
|
-
webSocketReconnectMaxDelayMs: z.coerce.number().int().nonnegative(),
|
|
21
|
-
webSocketReconnectMaxAttempts: z.coerce.number().int().nonnegative(),
|
|
22
|
-
callTimeoutMs: z.coerce.number().int().positive(),
|
|
23
|
-
tlsRejectUnauthorized: z.coerce.boolean(),
|
|
24
|
-
tlsCaFile: z.string().min(1).optional(),
|
|
25
|
-
tlsCertFile: z.string().min(1).optional(),
|
|
26
|
-
tlsKeyFile: z.string().min(1).optional(),
|
|
27
|
-
tlsServerName: z.string().min(1).optional()
|
|
28
|
-
});
|
|
29
|
-
export function parseConfig(input) {
|
|
30
|
-
return configSchema.parse({
|
|
31
|
-
centralSystemUrl: input.centralSystemUrl ?? process.env.OCPP_URL,
|
|
32
|
-
chargePointId: input.chargePointId ?? process.env.OCPP_CHARGE_POINT_ID ?? "CP-001",
|
|
33
|
-
vendor: input.vendor ?? process.env.OCPP_VENDOR ?? "archhaze24",
|
|
34
|
-
model: input.model ?? process.env.OCPP_MODEL ?? "KIRBY",
|
|
35
|
-
connectorId: input.connectorId ?? 1,
|
|
36
|
-
connectorCount: input.connectorCount ?? input.connectorId ?? 1,
|
|
37
|
-
heartbeatIntervalSeconds: input.heartbeatIntervalSeconds ?? 30,
|
|
38
|
-
persistState: input.persistState ?? process.env.KIRBY_OCPP_NO_PERSIST !== "1",
|
|
39
|
-
stateDirectory: input.stateDirectory ?? process.env.KIRBY_OCPP_STATE_DIR,
|
|
40
|
-
webSocketSubprotocol: input.webSocketSubprotocol ?? process.env.OCPP_WS_SUBPROTOCOL ?? "ocpp1.6",
|
|
41
|
-
webSocketPingIntervalSeconds: input.webSocketPingIntervalSeconds ?? process.env.OCPP_WS_PING_INTERVAL ?? 30,
|
|
42
|
-
webSocketReconnectEnabled: input.webSocketReconnectEnabled ?? process.env.OCPP_RECONNECT !== "0",
|
|
43
|
-
webSocketReconnectInitialDelayMs: input.webSocketReconnectInitialDelayMs ?? process.env.OCPP_RECONNECT_INITIAL_MS ?? 1_000,
|
|
44
|
-
webSocketReconnectMaxDelayMs: input.webSocketReconnectMaxDelayMs ?? process.env.OCPP_RECONNECT_MAX_MS ?? 30_000,
|
|
45
|
-
webSocketReconnectMaxAttempts: input.webSocketReconnectMaxAttempts ?? process.env.OCPP_RECONNECT_MAX_ATTEMPTS ?? 0,
|
|
46
|
-
callTimeoutMs: input.callTimeoutMs ?? process.env.OCPP_CALL_TIMEOUT_MS ?? 30_000,
|
|
47
|
-
tlsRejectUnauthorized: input.tlsRejectUnauthorized ?? process.env.OCPP_TLS_SKIP_VERIFY !== "1",
|
|
48
|
-
tlsCaFile: input.tlsCaFile ?? process.env.OCPP_TLS_CA_FILE,
|
|
49
|
-
tlsCertFile: input.tlsCertFile ?? process.env.OCPP_TLS_CERT_FILE,
|
|
50
|
-
tlsKeyFile: input.tlsKeyFile ?? process.env.OCPP_TLS_KEY_FILE,
|
|
51
|
-
tlsServerName: input.tlsServerName ?? process.env.OCPP_TLS_SERVER_NAME
|
|
52
|
-
});
|
|
53
|
-
}
|
package/dist/ocpp/client.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import type { CallResponse, OcppMessage } from "./types.js";
|
|
3
|
-
interface OcppClientEvents {
|
|
4
|
-
connected: [];
|
|
5
|
-
disconnected: [code?: number, reason?: string];
|
|
6
|
-
error: [error: Error];
|
|
7
|
-
log: [direction: "in" | "out", message: OcppMessage];
|
|
8
|
-
call: [messageId: string, action: string, payload: Record<string, unknown>];
|
|
9
|
-
}
|
|
10
|
-
export interface OcppClientOptions {
|
|
11
|
-
subprotocol?: string;
|
|
12
|
-
pingIntervalSeconds?: number;
|
|
13
|
-
tlsRejectUnauthorized?: boolean;
|
|
14
|
-
tlsCaFile?: string;
|
|
15
|
-
tlsCertFile?: string;
|
|
16
|
-
tlsKeyFile?: string;
|
|
17
|
-
tlsServerName?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare interface OcppClient {
|
|
20
|
-
on<K extends keyof OcppClientEvents>(event: K, listener: (...args: OcppClientEvents[K]) => void): this;
|
|
21
|
-
emit<K extends keyof OcppClientEvents>(event: K, ...args: OcppClientEvents[K]): boolean;
|
|
22
|
-
}
|
|
23
|
-
export declare class OcppClient extends EventEmitter {
|
|
24
|
-
private readonly centralSystemUrl;
|
|
25
|
-
private readonly chargePointId;
|
|
26
|
-
private readonly options;
|
|
27
|
-
private socket?;
|
|
28
|
-
private pingTimer?;
|
|
29
|
-
private awaitingPong;
|
|
30
|
-
private readonly pending;
|
|
31
|
-
private readonly incomingCalls;
|
|
32
|
-
private readonly schemaValidator;
|
|
33
|
-
constructor(centralSystemUrl: string, chargePointId: string, options?: OcppClientOptions);
|
|
34
|
-
get isConnected(): boolean;
|
|
35
|
-
connect(): void;
|
|
36
|
-
close(): void;
|
|
37
|
-
call(action: string, payload: Record<string, unknown>, timeoutMs?: number): Promise<CallResponse>;
|
|
38
|
-
reply(messageId: string, payload: Record<string, unknown>): void;
|
|
39
|
-
replyError(messageId: string, code: string, description: string, details?: Record<string, unknown>): void;
|
|
40
|
-
private send;
|
|
41
|
-
private handleMessage;
|
|
42
|
-
private resolveCall;
|
|
43
|
-
private rejectCall;
|
|
44
|
-
private rejectPending;
|
|
45
|
-
private toError;
|
|
46
|
-
private assertValidRequest;
|
|
47
|
-
private assertValidResponse;
|
|
48
|
-
private validationErrorCode;
|
|
49
|
-
private isValidCall;
|
|
50
|
-
private isValidCallResult;
|
|
51
|
-
private isValidCallError;
|
|
52
|
-
private isObjectPayload;
|
|
53
|
-
private readMessageId;
|
|
54
|
-
private startPingTimer;
|
|
55
|
-
private stopPingTimer;
|
|
56
|
-
private buildChargePointUrl;
|
|
57
|
-
private buildWebSocketOptions;
|
|
58
|
-
}
|
|
59
|
-
export {};
|
package/dist/ocpp/client.js
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { nanoid } from "nanoid";
|
|
4
|
-
import WebSocket from "ws";
|
|
5
|
-
import { OcppSchemaValidator } from "./schema-validator.js";
|
|
6
|
-
export class OcppClient extends EventEmitter {
|
|
7
|
-
centralSystemUrl;
|
|
8
|
-
chargePointId;
|
|
9
|
-
options;
|
|
10
|
-
socket;
|
|
11
|
-
pingTimer;
|
|
12
|
-
awaitingPong = false;
|
|
13
|
-
pending = new Map();
|
|
14
|
-
incomingCalls = new Map();
|
|
15
|
-
schemaValidator = new OcppSchemaValidator();
|
|
16
|
-
constructor(centralSystemUrl, chargePointId, options = {}) {
|
|
17
|
-
super();
|
|
18
|
-
this.centralSystemUrl = centralSystemUrl;
|
|
19
|
-
this.chargePointId = chargePointId;
|
|
20
|
-
this.options = options;
|
|
21
|
-
}
|
|
22
|
-
get isConnected() {
|
|
23
|
-
return this.socket?.readyState === WebSocket.OPEN;
|
|
24
|
-
}
|
|
25
|
-
connect() {
|
|
26
|
-
this.close();
|
|
27
|
-
const url = this.buildChargePointUrl();
|
|
28
|
-
const subprotocol = this.options.subprotocol ?? "ocpp1.6";
|
|
29
|
-
this.socket = new WebSocket(url, [subprotocol], this.buildWebSocketOptions());
|
|
30
|
-
this.socket.on("open", () => {
|
|
31
|
-
if (this.socket?.protocol !== subprotocol) {
|
|
32
|
-
const error = new Error(`Central System did not negotiate required WebSocket subprotocol ${subprotocol}`);
|
|
33
|
-
this.emit("error", error);
|
|
34
|
-
this.socket?.close(1002, "OCPP subprotocol required");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
this.startPingTimer();
|
|
38
|
-
this.emit("connected");
|
|
39
|
-
});
|
|
40
|
-
this.socket.on("message", (data) => this.handleMessage(data.toString()));
|
|
41
|
-
this.socket.on("pong", () => {
|
|
42
|
-
this.awaitingPong = false;
|
|
43
|
-
});
|
|
44
|
-
this.socket.on("close", (code, reason) => {
|
|
45
|
-
this.stopPingTimer();
|
|
46
|
-
this.rejectPending(new Error(`Connection closed (${code})`));
|
|
47
|
-
this.emit("disconnected", code, reason.toString());
|
|
48
|
-
});
|
|
49
|
-
this.socket.on("error", (error) => this.emit("error", error));
|
|
50
|
-
}
|
|
51
|
-
close() {
|
|
52
|
-
if (!this.socket) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
this.rejectPending(new Error("Connection closed"));
|
|
56
|
-
this.stopPingTimer();
|
|
57
|
-
const socket = this.socket;
|
|
58
|
-
socket.removeAllListeners();
|
|
59
|
-
socket.on("error", () => { });
|
|
60
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
61
|
-
socket.close();
|
|
62
|
-
}
|
|
63
|
-
else if (socket.readyState === WebSocket.CONNECTING) {
|
|
64
|
-
socket.terminate();
|
|
65
|
-
}
|
|
66
|
-
this.socket = undefined;
|
|
67
|
-
}
|
|
68
|
-
async call(action, payload, timeoutMs = 30_000) {
|
|
69
|
-
if (!this.isConnected || !this.socket) {
|
|
70
|
-
throw new Error("Not connected to Central System");
|
|
71
|
-
}
|
|
72
|
-
this.assertValidRequest(action, payload);
|
|
73
|
-
const messageId = nanoid(12);
|
|
74
|
-
const message = [2, messageId, action, payload];
|
|
75
|
-
const response = new Promise((resolve, reject) => {
|
|
76
|
-
const timeout = setTimeout(() => {
|
|
77
|
-
this.pending.delete(messageId);
|
|
78
|
-
reject(new Error(`${action} timed out after ${timeoutMs}ms`));
|
|
79
|
-
}, timeoutMs);
|
|
80
|
-
this.pending.set(messageId, { action, resolve, reject, timeout });
|
|
81
|
-
});
|
|
82
|
-
this.send(message);
|
|
83
|
-
return response;
|
|
84
|
-
}
|
|
85
|
-
reply(messageId, payload) {
|
|
86
|
-
const action = this.incomingCalls.get(messageId);
|
|
87
|
-
if (!action) {
|
|
88
|
-
throw new Error(`Cannot reply to unknown CALL ${messageId}`);
|
|
89
|
-
}
|
|
90
|
-
this.assertValidResponse(action, payload);
|
|
91
|
-
this.incomingCalls.delete(messageId);
|
|
92
|
-
this.send([3, messageId, payload]);
|
|
93
|
-
}
|
|
94
|
-
replyError(messageId, code, description, details = {}) {
|
|
95
|
-
this.incomingCalls.delete(messageId);
|
|
96
|
-
this.send([4, messageId, code, description, details]);
|
|
97
|
-
}
|
|
98
|
-
send(message) {
|
|
99
|
-
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
100
|
-
throw new Error("WebSocket is not open");
|
|
101
|
-
}
|
|
102
|
-
this.socket.send(JSON.stringify(message));
|
|
103
|
-
this.emit("log", "out", message);
|
|
104
|
-
}
|
|
105
|
-
handleMessage(raw) {
|
|
106
|
-
let message;
|
|
107
|
-
try {
|
|
108
|
-
message = JSON.parse(raw);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
this.emit("log", "in", message);
|
|
115
|
-
if (!Array.isArray(message)) {
|
|
116
|
-
this.emit("error", new Error("Invalid OCPP message: expected array"));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (message[0] === 2) {
|
|
120
|
-
if (!this.isValidCall(message)) {
|
|
121
|
-
const messageId = this.readMessageId(message);
|
|
122
|
-
if (messageId) {
|
|
123
|
-
this.replyError(messageId, "FormationViolation", "Invalid CALL frame");
|
|
124
|
-
}
|
|
125
|
-
this.emit("error", new Error("Invalid CALL frame"));
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const call = message;
|
|
129
|
-
if (!this.schemaValidator.hasRequestSchema(call[2])) {
|
|
130
|
-
this.replyError(call[1], "NotImplemented", `${call[2]} is not an OCPP 1.6J action`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const validation = this.schemaValidator.validateRequest(call[2], call[3]);
|
|
134
|
-
if (!validation.valid) {
|
|
135
|
-
this.replyError(call[1], this.validationErrorCode(validation.errorNames), validation.errors.join("; "));
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
this.incomingCalls.set(call[1], call[2]);
|
|
139
|
-
this.emit("call", call[1], call[2], call[3] ?? {});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (message[0] === 3) {
|
|
143
|
-
if (!this.isValidCallResult(message)) {
|
|
144
|
-
this.emit("error", new Error("Invalid CALLRESULT frame"));
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
this.resolveCall(message);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (message[0] === 4) {
|
|
151
|
-
if (!this.isValidCallError(message)) {
|
|
152
|
-
this.emit("error", new Error("Invalid CALLERROR frame"));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
this.rejectCall(message);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
this.emit("error", new Error(`Unsupported OCPP message type: ${message[0]}`));
|
|
159
|
-
}
|
|
160
|
-
resolveCall(message) {
|
|
161
|
-
const [, messageId, payload] = message;
|
|
162
|
-
const pending = this.pending.get(messageId);
|
|
163
|
-
if (!pending) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
clearTimeout(pending.timeout);
|
|
167
|
-
this.pending.delete(messageId);
|
|
168
|
-
const validation = this.schemaValidator.validateResponse(pending.action, payload);
|
|
169
|
-
if (!validation.valid) {
|
|
170
|
-
pending.reject(new Error(`${pending.action} response violates OCPP 1.6J schema (${this.validationErrorCode(validation.errorNames)}): ${validation.errors.join("; ")}`));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
pending.resolve({ messageId, payload });
|
|
174
|
-
}
|
|
175
|
-
rejectCall(message) {
|
|
176
|
-
const error = this.toError(message);
|
|
177
|
-
const pending = this.pending.get(error.messageId);
|
|
178
|
-
if (!pending) {
|
|
179
|
-
this.emit("error", new Error(error.description || error.code));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
clearTimeout(pending.timeout);
|
|
183
|
-
this.pending.delete(error.messageId);
|
|
184
|
-
pending.reject(new Error(`${pending.action} failed: ${error.code} ${error.description}`.trim()));
|
|
185
|
-
}
|
|
186
|
-
rejectPending(error) {
|
|
187
|
-
for (const [messageId, pending] of this.pending) {
|
|
188
|
-
clearTimeout(pending.timeout);
|
|
189
|
-
pending.reject(error);
|
|
190
|
-
this.pending.delete(messageId);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
toError(message) {
|
|
194
|
-
const [, messageId, code, description, details] = message;
|
|
195
|
-
return { messageId, code, description, details };
|
|
196
|
-
}
|
|
197
|
-
assertValidRequest(action, payload) {
|
|
198
|
-
const validation = this.schemaValidator.validateRequest(action, payload);
|
|
199
|
-
if (!validation.valid) {
|
|
200
|
-
throw new Error(`${action} request violates OCPP 1.6J schema (${this.validationErrorCode(validation.errorNames)}): ${validation.errors.join("; ")}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
assertValidResponse(action, payload) {
|
|
204
|
-
const validation = this.schemaValidator.validateResponse(action, payload);
|
|
205
|
-
if (!validation.valid) {
|
|
206
|
-
throw new Error(`${action} response violates OCPP 1.6J schema (${this.validationErrorCode(validation.errorNames)}): ${validation.errors.join("; ")}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
validationErrorCode(errorNames) {
|
|
210
|
-
if (errorNames.includes("required")) {
|
|
211
|
-
return "OccurenceConstraintViolation";
|
|
212
|
-
}
|
|
213
|
-
if (errorNames.includes("type")) {
|
|
214
|
-
return "TypeConstraintViolation";
|
|
215
|
-
}
|
|
216
|
-
if (errorNames.some((name) => ["additionalProperties", "enum", "maxLength", "minLength", "minimum", "maximum", "multipleOf", "format"].includes(name))) {
|
|
217
|
-
return "PropertyConstraintViolation";
|
|
218
|
-
}
|
|
219
|
-
return "FormationViolation";
|
|
220
|
-
}
|
|
221
|
-
isValidCall(message) {
|
|
222
|
-
return (message.length === 4 &&
|
|
223
|
-
message[0] === 2 &&
|
|
224
|
-
typeof message[1] === "string" &&
|
|
225
|
-
typeof message[2] === "string" &&
|
|
226
|
-
this.isObjectPayload(message[3]));
|
|
227
|
-
}
|
|
228
|
-
isValidCallResult(message) {
|
|
229
|
-
return message.length === 3 && message[0] === 3 && typeof message[1] === "string" && this.isObjectPayload(message[2]);
|
|
230
|
-
}
|
|
231
|
-
isValidCallError(message) {
|
|
232
|
-
return (message.length === 5 &&
|
|
233
|
-
message[0] === 4 &&
|
|
234
|
-
typeof message[1] === "string" &&
|
|
235
|
-
typeof message[2] === "string" &&
|
|
236
|
-
typeof message[3] === "string" &&
|
|
237
|
-
this.isObjectPayload(message[4]));
|
|
238
|
-
}
|
|
239
|
-
isObjectPayload(value) {
|
|
240
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
241
|
-
}
|
|
242
|
-
readMessageId(message) {
|
|
243
|
-
return typeof message[1] === "string" ? message[1] : undefined;
|
|
244
|
-
}
|
|
245
|
-
startPingTimer() {
|
|
246
|
-
this.stopPingTimer();
|
|
247
|
-
const intervalSeconds = this.options.pingIntervalSeconds ?? 30;
|
|
248
|
-
if (intervalSeconds <= 0) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
this.awaitingPong = false;
|
|
252
|
-
this.pingTimer = setInterval(() => {
|
|
253
|
-
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
254
|
-
if (this.awaitingPong) {
|
|
255
|
-
this.emit("error", new Error("WebSocket pong timed out"));
|
|
256
|
-
this.socket.terminate();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
this.awaitingPong = true;
|
|
260
|
-
this.socket.ping();
|
|
261
|
-
}
|
|
262
|
-
}, intervalSeconds * 1000);
|
|
263
|
-
this.pingTimer.unref();
|
|
264
|
-
}
|
|
265
|
-
stopPingTimer() {
|
|
266
|
-
if (!this.pingTimer) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
clearInterval(this.pingTimer);
|
|
270
|
-
this.pingTimer = undefined;
|
|
271
|
-
this.awaitingPong = false;
|
|
272
|
-
}
|
|
273
|
-
buildChargePointUrl() {
|
|
274
|
-
const base = this.centralSystemUrl.replace(/\/+$/, "");
|
|
275
|
-
const id = encodeURIComponent(this.chargePointId);
|
|
276
|
-
if (base.endsWith(`/${id}`)) {
|
|
277
|
-
return base;
|
|
278
|
-
}
|
|
279
|
-
return `${base}/${id}`;
|
|
280
|
-
}
|
|
281
|
-
buildWebSocketOptions() {
|
|
282
|
-
const options = {
|
|
283
|
-
rejectUnauthorized: this.options.tlsRejectUnauthorized ?? true
|
|
284
|
-
};
|
|
285
|
-
if (this.options.tlsCaFile) {
|
|
286
|
-
options.ca = readFileSync(this.options.tlsCaFile);
|
|
287
|
-
}
|
|
288
|
-
if (this.options.tlsCertFile) {
|
|
289
|
-
options.cert = readFileSync(this.options.tlsCertFile);
|
|
290
|
-
}
|
|
291
|
-
if (this.options.tlsKeyFile) {
|
|
292
|
-
options.key = readFileSync(this.options.tlsKeyFile);
|
|
293
|
-
}
|
|
294
|
-
if (this.options.tlsServerName) {
|
|
295
|
-
options.servername = this.options.tlsServerName;
|
|
296
|
-
}
|
|
297
|
-
return options;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface OcppSchemaValidationResult {
|
|
2
|
-
valid: boolean;
|
|
3
|
-
errors: string[];
|
|
4
|
-
errorNames: string[];
|
|
5
|
-
}
|
|
6
|
-
export declare class OcppSchemaValidator {
|
|
7
|
-
private readonly validator;
|
|
8
|
-
private readonly schemas;
|
|
9
|
-
constructor(schemaDirectory?: string);
|
|
10
|
-
hasRequestSchema(action: string): boolean;
|
|
11
|
-
hasResponseSchema(action: string): boolean;
|
|
12
|
-
validateRequest(action: string, payload: unknown): OcppSchemaValidationResult;
|
|
13
|
-
validateResponse(action: string, payload: unknown): OcppSchemaValidationResult;
|
|
14
|
-
private validate;
|
|
15
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { Validator } from "jsonschema";
|
|
5
|
-
export class OcppSchemaValidator {
|
|
6
|
-
validator = new Validator();
|
|
7
|
-
schemas = new Map();
|
|
8
|
-
constructor(schemaDirectory = findSchemaDirectory()) {
|
|
9
|
-
for (const file of readdirSync(schemaDirectory)) {
|
|
10
|
-
if (!file.endsWith(".json")) {
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
const schema = JSON.parse(readFileSync(join(schemaDirectory, file), "utf8"));
|
|
14
|
-
const name = file.replace(/\.json$/, "");
|
|
15
|
-
this.schemas.set(name, stripSchemaMetadata(schema, true));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
hasRequestSchema(action) {
|
|
19
|
-
return this.schemas.has(action);
|
|
20
|
-
}
|
|
21
|
-
hasResponseSchema(action) {
|
|
22
|
-
return this.schemas.has(`${action}Response`);
|
|
23
|
-
}
|
|
24
|
-
validateRequest(action, payload) {
|
|
25
|
-
return this.validate(action, payload);
|
|
26
|
-
}
|
|
27
|
-
validateResponse(action, payload) {
|
|
28
|
-
return this.validate(`${action}Response`, payload);
|
|
29
|
-
}
|
|
30
|
-
validate(schemaName, payload) {
|
|
31
|
-
const schema = this.schemas.get(schemaName);
|
|
32
|
-
if (!schema) {
|
|
33
|
-
return {
|
|
34
|
-
valid: false,
|
|
35
|
-
errors: [`No JSON schema found for ${schemaName}`],
|
|
36
|
-
errorNames: ["schema"]
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const result = this.validator.validate(payload, schema);
|
|
40
|
-
return {
|
|
41
|
-
valid: result.valid,
|
|
42
|
-
errors: result.errors.map((error) => error.stack),
|
|
43
|
-
errorNames: result.errors.map((error) => error.name)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function stripSchemaMetadata(value, root = false) {
|
|
48
|
-
if (Array.isArray(value)) {
|
|
49
|
-
return value.map((item) => stripSchemaMetadata(item));
|
|
50
|
-
}
|
|
51
|
-
if (!value || typeof value !== "object") {
|
|
52
|
-
return value;
|
|
53
|
-
}
|
|
54
|
-
const output = {};
|
|
55
|
-
for (const [key, child] of Object.entries(value)) {
|
|
56
|
-
if (key === "$schema" || key === "title" || (root && key === "id")) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
output[key] = stripSchemaMetadata(child);
|
|
60
|
-
}
|
|
61
|
-
return output;
|
|
62
|
-
}
|
|
63
|
-
function findSchemaDirectory() {
|
|
64
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
65
|
-
const candidates = [
|
|
66
|
-
join(currentDir, "schemas/json"),
|
|
67
|
-
join(currentDir, "../../src/ocpp/schemas/json")
|
|
68
|
-
];
|
|
69
|
-
for (const candidate of candidates) {
|
|
70
|
-
if (existsSync(candidate)) {
|
|
71
|
-
return candidate;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
throw new Error("OCPP 1.6J JSON schemas were not found");
|
|
75
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:AuthorizeRequest",
|
|
4
|
-
"title": "AuthorizeRequest",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"idTag": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"maxLength": 20
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"additionalProperties": false,
|
|
13
|
-
"required": [
|
|
14
|
-
"idTag"
|
|
15
|
-
]
|
|
16
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:AuthorizeResponse",
|
|
4
|
-
"title": "AuthorizeResponse",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"idTagInfo": {
|
|
8
|
-
"type": "object",
|
|
9
|
-
"properties": {
|
|
10
|
-
"expiryDate": {
|
|
11
|
-
"type": "string",
|
|
12
|
-
"format": "date-time"
|
|
13
|
-
},
|
|
14
|
-
"parentIdTag": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"maxLength": 20
|
|
17
|
-
},
|
|
18
|
-
"status": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"additionalProperties": false,
|
|
21
|
-
"enum": [
|
|
22
|
-
"Accepted",
|
|
23
|
-
"Blocked",
|
|
24
|
-
"Expired",
|
|
25
|
-
"Invalid",
|
|
26
|
-
"ConcurrentTx"
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"additionalProperties": false,
|
|
31
|
-
"required": [
|
|
32
|
-
"status"
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"additionalProperties": false,
|
|
37
|
-
"required": [
|
|
38
|
-
"idTagInfo"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:BootNotificationRequest",
|
|
4
|
-
"title": "BootNotificationRequest",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"chargePointVendor": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"maxLength": 20
|
|
10
|
-
},
|
|
11
|
-
"chargePointModel": {
|
|
12
|
-
"type": "string",
|
|
13
|
-
"maxLength": 20
|
|
14
|
-
},
|
|
15
|
-
"chargePointSerialNumber": {
|
|
16
|
-
"type": "string",
|
|
17
|
-
"maxLength": 25
|
|
18
|
-
},
|
|
19
|
-
"chargeBoxSerialNumber": {
|
|
20
|
-
"type": "string",
|
|
21
|
-
"maxLength": 25
|
|
22
|
-
},
|
|
23
|
-
"firmwareVersion": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"maxLength": 50
|
|
26
|
-
},
|
|
27
|
-
"iccid": {
|
|
28
|
-
"type": "string",
|
|
29
|
-
"maxLength": 20
|
|
30
|
-
},
|
|
31
|
-
"imsi": {
|
|
32
|
-
"type": "string",
|
|
33
|
-
"maxLength": 20
|
|
34
|
-
},
|
|
35
|
-
"meterType": {
|
|
36
|
-
"type": "string",
|
|
37
|
-
"maxLength": 25
|
|
38
|
-
},
|
|
39
|
-
"meterSerialNumber": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"maxLength": 25
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"additionalProperties": false,
|
|
45
|
-
"required": [
|
|
46
|
-
"chargePointVendor",
|
|
47
|
-
"chargePointModel"
|
|
48
|
-
]
|
|
49
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:BootNotificationResponse",
|
|
4
|
-
"title": "BootNotificationResponse",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"status": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"additionalProperties": false,
|
|
10
|
-
"enum": [
|
|
11
|
-
"Accepted",
|
|
12
|
-
"Pending",
|
|
13
|
-
"Rejected"
|
|
14
|
-
]
|
|
15
|
-
},
|
|
16
|
-
"currentTime": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"format": "date-time"
|
|
19
|
-
},
|
|
20
|
-
"interval": {
|
|
21
|
-
"type": "integer"
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"additionalProperties": false,
|
|
25
|
-
"required": [
|
|
26
|
-
"status",
|
|
27
|
-
"currentTime",
|
|
28
|
-
"interval"
|
|
29
|
-
]
|
|
30
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:CancelReservationRequest",
|
|
4
|
-
"title": "CancelReservationRequest",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"reservationId": {
|
|
8
|
-
"type": "integer"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"additionalProperties": false,
|
|
12
|
-
"required": [
|
|
13
|
-
"reservationId"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
-
"id": "urn:OCPP:1.6:2019:12:CancelReservationResponse",
|
|
4
|
-
"title": "CancelReservationResponse",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"status": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"additionalProperties": false,
|
|
10
|
-
"enum": [
|
|
11
|
-
"Accepted",
|
|
12
|
-
"Rejected"
|
|
13
|
-
]
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"additionalProperties": false,
|
|
17
|
-
"required": [
|
|
18
|
-
"status"
|
|
19
|
-
]
|
|
20
|
-
}
|