kirby-ocpp 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -95
- package/THIRD_PARTY_NOTICES.md +6 -2
- package/bin/kirby-ocpp.js +55 -0
- package/package.json +22 -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
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { readNumber, readString } from "./payload.js";
|
|
2
|
-
export class MaintenanceLifecycle {
|
|
3
|
-
options;
|
|
4
|
-
diagnosticsTimers = [];
|
|
5
|
-
firmwareTimers = [];
|
|
6
|
-
diagnosticsOutcome = "success";
|
|
7
|
-
firmwareOutcome = "success";
|
|
8
|
-
constructor(options) {
|
|
9
|
-
this.options = options;
|
|
10
|
-
}
|
|
11
|
-
setDiagnosticsOutcome(outcome) {
|
|
12
|
-
this.diagnosticsOutcome = outcome;
|
|
13
|
-
}
|
|
14
|
-
setFirmwareOutcome(outcome) {
|
|
15
|
-
this.firmwareOutcome = outcome;
|
|
16
|
-
}
|
|
17
|
-
startDiagnostics(payload) {
|
|
18
|
-
this.clearDiagnostics();
|
|
19
|
-
const location = readString(payload.location, "");
|
|
20
|
-
const fileName = diagnosticsFileName(location);
|
|
21
|
-
const retries = boundedRetries(readNumber(payload.retries, 0));
|
|
22
|
-
const retryInterval = readNumber(payload.retryInterval, 0);
|
|
23
|
-
const startDelay = retryInterval > 0 ? Math.min(retryInterval * 1000, 1000) : 100;
|
|
24
|
-
const stepDelay = retryInterval > 0 ? Math.min(retryInterval * 1000, 1000) : 400;
|
|
25
|
-
const statuses = diagnosticsStatuses(this.diagnosticsOutcome, retries, startDelay, stepDelay);
|
|
26
|
-
this.options.log("info", `Diagnostics upload scheduled to ${location}`);
|
|
27
|
-
this.diagnosticsTimers = this.schedule(statuses, this.options.diagnosticsStatus);
|
|
28
|
-
return fileName;
|
|
29
|
-
}
|
|
30
|
-
startFirmware(payload) {
|
|
31
|
-
this.clearFirmware();
|
|
32
|
-
const location = readString(payload.location, "");
|
|
33
|
-
const retrieveDate = readString(payload.retrieveDate, "");
|
|
34
|
-
const retries = boundedRetries(readNumber(payload.retries, 0));
|
|
35
|
-
const retryInterval = readNumber(payload.retryInterval, 0);
|
|
36
|
-
const startDelay = Math.min(Math.max(Date.parse(retrieveDate) - Date.now(), 100), 1000);
|
|
37
|
-
const stepDelay = retryInterval > 0 ? Math.min(retryInterval * 1000, 1000) : 400;
|
|
38
|
-
const statuses = firmwareStatuses(this.firmwareOutcome, retries, startDelay, stepDelay);
|
|
39
|
-
this.options.log("info", `Firmware update scheduled from ${location}`);
|
|
40
|
-
this.firmwareTimers = this.schedule(statuses, this.options.firmwareStatus);
|
|
41
|
-
}
|
|
42
|
-
clearAll() {
|
|
43
|
-
this.clearDiagnostics();
|
|
44
|
-
this.clearFirmware();
|
|
45
|
-
}
|
|
46
|
-
clearDiagnostics() {
|
|
47
|
-
this.clear(this.diagnosticsTimers);
|
|
48
|
-
this.diagnosticsTimers = [];
|
|
49
|
-
}
|
|
50
|
-
clearFirmware() {
|
|
51
|
-
this.clear(this.firmwareTimers);
|
|
52
|
-
this.firmwareTimers = [];
|
|
53
|
-
}
|
|
54
|
-
schedule(statuses, send) {
|
|
55
|
-
return statuses.map(({ status, delayMs }) => setTimeout(() => {
|
|
56
|
-
this.options.run(() => send(status));
|
|
57
|
-
}, delayMs));
|
|
58
|
-
}
|
|
59
|
-
clear(timers) {
|
|
60
|
-
for (const timer of timers) {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function diagnosticsFileName(location) {
|
|
66
|
-
try {
|
|
67
|
-
const url = new URL(location);
|
|
68
|
-
const leaf = url.pathname.split("/").filter(Boolean).at(-1);
|
|
69
|
-
return leaf || "kirby-ocpp-diagnostics.log";
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return "kirby-ocpp-diagnostics.log";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function diagnosticsStatuses(outcome, retries, startDelay, stepDelay) {
|
|
76
|
-
if (outcome === "uploadFailure") {
|
|
77
|
-
return retryingStatuses("Uploading", "UploadFailed", retries, startDelay, stepDelay);
|
|
78
|
-
}
|
|
79
|
-
return [
|
|
80
|
-
...Array.from({ length: retries + 1 }, (_, index) => ({ status: "Uploading", delayMs: startDelay + stepDelay * index })),
|
|
81
|
-
{ status: "Uploaded", delayMs: startDelay + stepDelay * (retries + 1) }
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
function firmwareStatuses(outcome, retries, startDelay, stepDelay) {
|
|
85
|
-
if (outcome === "downloadFailure") {
|
|
86
|
-
return retryingStatuses("Downloading", "DownloadFailed", retries, startDelay, stepDelay);
|
|
87
|
-
}
|
|
88
|
-
if (outcome === "installationFailure") {
|
|
89
|
-
return [
|
|
90
|
-
{ status: "Downloading", delayMs: startDelay },
|
|
91
|
-
{ status: "Downloaded", delayMs: startDelay + stepDelay },
|
|
92
|
-
...retryingStatuses("Installing", "InstallationFailed", retries, startDelay + stepDelay * 2, stepDelay)
|
|
93
|
-
];
|
|
94
|
-
}
|
|
95
|
-
return [
|
|
96
|
-
{ status: "Downloading", delayMs: startDelay },
|
|
97
|
-
{ status: "Downloaded", delayMs: startDelay + stepDelay },
|
|
98
|
-
{ status: "Installing", delayMs: startDelay + stepDelay * 2 },
|
|
99
|
-
{ status: "Installed", delayMs: startDelay + stepDelay * 3 }
|
|
100
|
-
];
|
|
101
|
-
}
|
|
102
|
-
function retryingStatuses(activeStatus, failureStatus, retries, startDelay, stepDelay) {
|
|
103
|
-
return [
|
|
104
|
-
...Array.from({ length: retries + 1 }, (_, index) => ({ status: activeStatus, delayMs: startDelay + stepDelay * index })),
|
|
105
|
-
{ status: failureStatus, delayMs: startDelay + stepDelay * (retries + 1) }
|
|
106
|
-
];
|
|
107
|
-
}
|
|
108
|
-
function boundedRetries(value) {
|
|
109
|
-
return Number.isInteger(value) && value > 0 ? Math.min(value, 5) : 0;
|
|
110
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export declare class MeterValueScheduler {
|
|
2
|
-
private readonly options;
|
|
3
|
-
private readonly periodicTimers;
|
|
4
|
-
private clockAlignedTimer?;
|
|
5
|
-
constructor(options: {
|
|
6
|
-
sampleIntervalSeconds: () => number;
|
|
7
|
-
clockAlignedIntervalSeconds: () => number;
|
|
8
|
-
isConnected: () => boolean;
|
|
9
|
-
activeConnectorIds: () => number[];
|
|
10
|
-
isPeriodicConnectorActive: (connectorId: number) => boolean;
|
|
11
|
-
sendPeriodic: (connectorId: number, deltaWh: number) => Promise<void>;
|
|
12
|
-
sendClockAligned: (connectorIds: number[], deltaWh: number) => Promise<void>;
|
|
13
|
-
runSafely: (task: () => Promise<void>) => void;
|
|
14
|
-
});
|
|
15
|
-
startPeriodic(connectorId: number): void;
|
|
16
|
-
stopPeriodic(connectorId: number): void;
|
|
17
|
-
stopAllPeriodic(): void;
|
|
18
|
-
reschedulePeriodic(connectorIds: number[]): void;
|
|
19
|
-
startClockAligned(): void;
|
|
20
|
-
stopClockAligned(): void;
|
|
21
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { clockAlignedDelayMs, periodicMeterDeltaWh } from "./metering.js";
|
|
2
|
-
export class MeterValueScheduler {
|
|
3
|
-
options;
|
|
4
|
-
periodicTimers = new Map();
|
|
5
|
-
clockAlignedTimer;
|
|
6
|
-
constructor(options) {
|
|
7
|
-
this.options = options;
|
|
8
|
-
}
|
|
9
|
-
startPeriodic(connectorId) {
|
|
10
|
-
this.stopPeriodic(connectorId);
|
|
11
|
-
const intervalSeconds = this.options.sampleIntervalSeconds();
|
|
12
|
-
if (intervalSeconds <= 0) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const timer = setInterval(() => {
|
|
16
|
-
this.options.runSafely(async () => {
|
|
17
|
-
if (!this.options.isConnected()) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (!this.options.isPeriodicConnectorActive(connectorId)) {
|
|
21
|
-
this.stopPeriodic(connectorId);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
await this.options.sendPeriodic(connectorId, periodicMeterDeltaWh(intervalSeconds));
|
|
25
|
-
});
|
|
26
|
-
}, intervalSeconds * 1000);
|
|
27
|
-
this.periodicTimers.set(connectorId, timer);
|
|
28
|
-
}
|
|
29
|
-
stopPeriodic(connectorId) {
|
|
30
|
-
const timer = this.periodicTimers.get(connectorId);
|
|
31
|
-
if (!timer) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
clearInterval(timer);
|
|
35
|
-
this.periodicTimers.delete(connectorId);
|
|
36
|
-
}
|
|
37
|
-
stopAllPeriodic() {
|
|
38
|
-
for (const connectorId of [...this.periodicTimers.keys()]) {
|
|
39
|
-
this.stopPeriodic(connectorId);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
reschedulePeriodic(connectorIds) {
|
|
43
|
-
for (const connectorId of connectorIds) {
|
|
44
|
-
if (this.options.isPeriodicConnectorActive(connectorId)) {
|
|
45
|
-
this.startPeriodic(connectorId);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
this.stopPeriodic(connectorId);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
startClockAligned() {
|
|
52
|
-
this.stopClockAligned();
|
|
53
|
-
const intervalSeconds = this.options.clockAlignedIntervalSeconds();
|
|
54
|
-
if (intervalSeconds <= 0 || !this.options.isConnected()) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
this.clockAlignedTimer = setTimeout(() => {
|
|
58
|
-
this.clockAlignedTimer = undefined;
|
|
59
|
-
this.options.runSafely(async () => {
|
|
60
|
-
try {
|
|
61
|
-
await this.options.sendClockAligned(this.options.activeConnectorIds(), periodicMeterDeltaWh(intervalSeconds));
|
|
62
|
-
}
|
|
63
|
-
finally {
|
|
64
|
-
if (this.options.isConnected()) {
|
|
65
|
-
this.startClockAligned();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}, clockAlignedDelayMs(intervalSeconds));
|
|
70
|
-
}
|
|
71
|
-
stopClockAligned() {
|
|
72
|
-
if (!this.clockAlignedTimer) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
clearTimeout(this.clockAlignedTimer);
|
|
76
|
-
this.clockAlignedTimer = undefined;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ConnectorStatus } from "../ocpp/types.js";
|
|
2
|
-
export type MeterValueContext = "Sample.Clock" | "Sample.Periodic" | "Transaction.End";
|
|
3
|
-
export type Measurand = "Energy.Active.Export.Register" | "Energy.Active.Import.Register" | "Energy.Reactive.Export.Register" | "Energy.Reactive.Import.Register" | "Energy.Active.Export.Interval" | "Energy.Active.Import.Interval" | "Energy.Reactive.Export.Interval" | "Energy.Reactive.Import.Interval" | "Power.Active.Export" | "Power.Active.Import" | "Power.Offered" | "Power.Reactive.Export" | "Power.Reactive.Import" | "Power.Factor" | "Current.Import" | "Current.Export" | "Current.Offered" | "Voltage" | "Frequency" | "Temperature" | "SoC" | "RPM";
|
|
4
|
-
export type MeteringConfigurationKey = "MeterValuesAlignedData" | "MeterValuesSampledData" | "StopTxnAlignedData" | "StopTxnSampledData";
|
|
5
|
-
export interface MeteringConnector {
|
|
6
|
-
status: ConnectorStatus;
|
|
7
|
-
meterWh: number;
|
|
8
|
-
}
|
|
9
|
-
export declare const DEFAULT_SAMPLED_DATA: Measurand[];
|
|
10
|
-
export declare const MEASURANDS: Set<Measurand>;
|
|
11
|
-
export declare function buildMeterValue(connector: MeteringConnector, context: MeterValueContext, measurands: Measurand[]): Record<string, unknown>;
|
|
12
|
-
export declare function readConfiguredMeasurands(value: string | undefined): Measurand[];
|
|
13
|
-
export declare function isValidSampledDataConfiguration(value: string): boolean;
|
|
14
|
-
export declare function periodicMeterDeltaWh(intervalSeconds: number): number;
|
|
15
|
-
export declare function clockAlignedDelayMs(intervalSeconds: number): number;
|
package/dist/station/metering.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_SAMPLED_DATA = [
|
|
2
|
-
"Energy.Active.Import.Register",
|
|
3
|
-
"Power.Active.Import",
|
|
4
|
-
"Current.Import",
|
|
5
|
-
"Voltage",
|
|
6
|
-
"Temperature",
|
|
7
|
-
"SoC"
|
|
8
|
-
];
|
|
9
|
-
export const MEASURANDS = new Set([
|
|
10
|
-
"Energy.Active.Export.Register",
|
|
11
|
-
"Energy.Active.Import.Register",
|
|
12
|
-
"Energy.Reactive.Export.Register",
|
|
13
|
-
"Energy.Reactive.Import.Register",
|
|
14
|
-
"Energy.Active.Export.Interval",
|
|
15
|
-
"Energy.Active.Import.Interval",
|
|
16
|
-
"Energy.Reactive.Export.Interval",
|
|
17
|
-
"Energy.Reactive.Import.Interval",
|
|
18
|
-
"Power.Active.Export",
|
|
19
|
-
"Power.Offered",
|
|
20
|
-
"Power.Active.Import",
|
|
21
|
-
"Power.Reactive.Export",
|
|
22
|
-
"Power.Reactive.Import",
|
|
23
|
-
"Power.Factor",
|
|
24
|
-
"Current.Import",
|
|
25
|
-
"Current.Export",
|
|
26
|
-
"Current.Offered",
|
|
27
|
-
"Voltage",
|
|
28
|
-
"Frequency",
|
|
29
|
-
"Temperature",
|
|
30
|
-
"SoC",
|
|
31
|
-
"RPM"
|
|
32
|
-
]);
|
|
33
|
-
export function buildMeterValue(connector, context, measurands) {
|
|
34
|
-
return {
|
|
35
|
-
timestamp: new Date().toISOString(),
|
|
36
|
-
sampledValue: measurands.map((measurand) => buildSampledValue(connector, context, measurand))
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
export function readConfiguredMeasurands(value) {
|
|
40
|
-
const measurands = (value ?? "")
|
|
41
|
-
.split(",")
|
|
42
|
-
.map((item) => item.trim())
|
|
43
|
-
.filter(Boolean)
|
|
44
|
-
.filter((item) => MEASURANDS.has(item));
|
|
45
|
-
return measurands.length > 0 ? measurands : DEFAULT_SAMPLED_DATA;
|
|
46
|
-
}
|
|
47
|
-
export function isValidSampledDataConfiguration(value) {
|
|
48
|
-
const items = value
|
|
49
|
-
.split(",")
|
|
50
|
-
.map((item) => item.trim())
|
|
51
|
-
.filter(Boolean);
|
|
52
|
-
return items.length > 0 && items.every((item) => MEASURANDS.has(item));
|
|
53
|
-
}
|
|
54
|
-
export function periodicMeterDeltaWh(intervalSeconds) {
|
|
55
|
-
return Math.max(0, Math.round((7200 * intervalSeconds) / 3600));
|
|
56
|
-
}
|
|
57
|
-
export function clockAlignedDelayMs(intervalSeconds) {
|
|
58
|
-
const intervalMs = intervalSeconds * 1000;
|
|
59
|
-
const remainder = Date.now() % intervalMs;
|
|
60
|
-
return remainder === 0 ? intervalMs : intervalMs - remainder;
|
|
61
|
-
}
|
|
62
|
-
function buildSampledValue(connector, context, measurand) {
|
|
63
|
-
const sample = sampleForMeasurand(connector, measurand);
|
|
64
|
-
const sampledValue = {
|
|
65
|
-
value: sample.value,
|
|
66
|
-
context,
|
|
67
|
-
format: "Raw",
|
|
68
|
-
measurand
|
|
69
|
-
};
|
|
70
|
-
if (sample.location) {
|
|
71
|
-
sampledValue.location = sample.location;
|
|
72
|
-
}
|
|
73
|
-
if (sample.phase) {
|
|
74
|
-
sampledValue.phase = sample.phase;
|
|
75
|
-
}
|
|
76
|
-
if (sample.unit) {
|
|
77
|
-
sampledValue.unit = sample.unit;
|
|
78
|
-
}
|
|
79
|
-
return sampledValue;
|
|
80
|
-
}
|
|
81
|
-
function sampleForMeasurand(connector, measurand) {
|
|
82
|
-
const charging = connector.status === "Charging";
|
|
83
|
-
const cycle = Math.abs(Math.round(connector.meterWh / 120)) % 8;
|
|
84
|
-
const voltageV = charging ? 228 + (cycle % 6) : 230;
|
|
85
|
-
const activePowerW = charging ? 6800 + cycle * 90 : 0;
|
|
86
|
-
const currentA = charging ? activePowerW / voltageV : 0;
|
|
87
|
-
const temperatureC = charging ? 29 + cycle + Math.min(8, Math.floor(connector.meterWh / 3000)) : 25;
|
|
88
|
-
const soc = charging ? clampNumber(18 + Math.floor(connector.meterWh / 450), 18, 96) : 0;
|
|
89
|
-
switch (measurand) {
|
|
90
|
-
case "Energy.Active.Import.Register":
|
|
91
|
-
return { value: String(Math.round(connector.meterWh)), unit: "Wh", location: "Outlet" };
|
|
92
|
-
case "Energy.Active.Export.Register":
|
|
93
|
-
return { value: "0", unit: "Wh", location: "Outlet" };
|
|
94
|
-
case "Energy.Reactive.Import.Register":
|
|
95
|
-
case "Energy.Reactive.Export.Register":
|
|
96
|
-
return { value: "0", unit: "varh", location: "Outlet" };
|
|
97
|
-
case "Energy.Active.Import.Interval":
|
|
98
|
-
return { value: charging ? "120" : "0", unit: "Wh", location: "Outlet" };
|
|
99
|
-
case "Energy.Active.Export.Interval":
|
|
100
|
-
return { value: "0", unit: "Wh", location: "Outlet" };
|
|
101
|
-
case "Energy.Reactive.Import.Interval":
|
|
102
|
-
case "Energy.Reactive.Export.Interval":
|
|
103
|
-
return { value: "0", unit: "varh", location: "Outlet" };
|
|
104
|
-
case "Power.Active.Import":
|
|
105
|
-
return { value: String(activePowerW), unit: "W", location: "Outlet" };
|
|
106
|
-
case "Power.Active.Export":
|
|
107
|
-
return { value: "0", unit: "W", location: "Outlet" };
|
|
108
|
-
case "Power.Offered":
|
|
109
|
-
return { value: "7400", unit: "W", location: "Outlet" };
|
|
110
|
-
case "Power.Reactive.Import":
|
|
111
|
-
case "Power.Reactive.Export":
|
|
112
|
-
return { value: "0", unit: "var", location: "Outlet" };
|
|
113
|
-
case "Power.Factor":
|
|
114
|
-
return { value: charging ? "1" : "0", location: "Outlet" };
|
|
115
|
-
case "Current.Import":
|
|
116
|
-
return { value: formatDecimal(currentA, 1), unit: "A", location: "Outlet", phase: "L1" };
|
|
117
|
-
case "Current.Export":
|
|
118
|
-
return { value: "0", unit: "A", location: "Outlet", phase: "L1" };
|
|
119
|
-
case "Current.Offered":
|
|
120
|
-
return { value: "32", unit: "A", location: "Outlet", phase: "L1" };
|
|
121
|
-
case "Voltage":
|
|
122
|
-
return { value: String(voltageV), unit: "V", location: "Outlet", phase: "L1-N" };
|
|
123
|
-
case "Temperature":
|
|
124
|
-
return { value: String(temperatureC), unit: "Celsius", location: "Body" };
|
|
125
|
-
case "SoC":
|
|
126
|
-
return { value: String(soc), unit: "Percent", location: "EV" };
|
|
127
|
-
case "Frequency":
|
|
128
|
-
return { value: "50", location: "Outlet" };
|
|
129
|
-
case "RPM":
|
|
130
|
-
return { value: "0", location: "Body" };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function clampNumber(value, min, max) {
|
|
134
|
-
return Math.min(Math.max(value, min), max);
|
|
135
|
-
}
|
|
136
|
-
function formatDecimal(value, digits) {
|
|
137
|
-
return value.toFixed(digits).replace(/\.0$/, "");
|
|
138
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { CallResponse, ConnectorStatus, StopReason } from "../ocpp/types.js";
|
|
2
|
-
import { type IdTagInfo } from "./authorization.js";
|
|
3
|
-
import type { ChargingProfileRegistry } from "./charging-profile-registry.js";
|
|
4
|
-
import type { ConnectorRegistry } from "./connector-registry.js";
|
|
5
|
-
export interface OfflineTransactionSyncOptions {
|
|
6
|
-
connectors: ConnectorRegistry;
|
|
7
|
-
chargingProfiles: ChargingProfileRegistry;
|
|
8
|
-
send: (action: string, payload: Record<string, unknown>) => Promise<CallResponse>;
|
|
9
|
-
readIdTagInfo: (value: unknown) => IdTagInfo;
|
|
10
|
-
rememberAuthorization: (idTag: string, idTagInfo: IdTagInfo) => void;
|
|
11
|
-
save: () => void;
|
|
12
|
-
log: (level: "success" | "warn", message: string) => void;
|
|
13
|
-
statusNotification: (connectorId: number, status: ConnectorStatus) => Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
export declare class OfflineTransactionSync {
|
|
16
|
-
private readonly options;
|
|
17
|
-
constructor(options: OfflineTransactionSyncOptions);
|
|
18
|
-
syncPendingTransactions(): Promise<void>;
|
|
19
|
-
private deliverPendingStartTransaction;
|
|
20
|
-
private deliverPendingStopTransaction;
|
|
21
|
-
}
|
|
22
|
-
export declare function localTransactionId(connectorId: number): number;
|
|
23
|
-
export declare function pendingStopPayload(options: {
|
|
24
|
-
meterStop: number;
|
|
25
|
-
timestamp: string;
|
|
26
|
-
reason: StopReason;
|
|
27
|
-
idTag?: string;
|
|
28
|
-
transactionData: Record<string, unknown>[];
|
|
29
|
-
}): Record<string, unknown>;
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { isAcceptedIdTagInfo } from "./authorization.js";
|
|
2
|
-
import { readNumber } from "./payload.js";
|
|
3
|
-
import { statusAfterStopTransaction } from "./transaction-rules.js";
|
|
4
|
-
export class OfflineTransactionSync {
|
|
5
|
-
options;
|
|
6
|
-
constructor(options) {
|
|
7
|
-
this.options = options;
|
|
8
|
-
}
|
|
9
|
-
async syncPendingTransactions() {
|
|
10
|
-
for (const connector of this.options.connectors.all()) {
|
|
11
|
-
if (!connector.pendingStartTransaction) {
|
|
12
|
-
if (connector.pendingStopTransaction && connector.transactionId && connector.transactionId > 0) {
|
|
13
|
-
await this.deliverPendingStopTransaction(connector.id);
|
|
14
|
-
}
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
await this.deliverPendingStartTransaction(connector.id);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
async deliverPendingStartTransaction(connectorId) {
|
|
21
|
-
const connector = this.options.connectors.get(connectorId);
|
|
22
|
-
const pendingStart = connector.pendingStartTransaction;
|
|
23
|
-
if (!pendingStart) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const response = await this.options.send("StartTransaction", pendingStart);
|
|
27
|
-
const idTagInfo = this.options.readIdTagInfo(response.payload.idTagInfo);
|
|
28
|
-
this.options.rememberAuthorization(pendingStart.idTag, idTagInfo);
|
|
29
|
-
if (!isAcceptedIdTagInfo(idTagInfo)) {
|
|
30
|
-
this.options.log("warn", `Offline StartTransaction rejected during sync: ${idTagInfo.status} (${pendingStart.idTag})`);
|
|
31
|
-
this.options.connectors.patch(connectorId, {
|
|
32
|
-
transactionId: undefined,
|
|
33
|
-
transactionStartedAt: undefined,
|
|
34
|
-
pendingStartTransaction: undefined,
|
|
35
|
-
pendingStopTransaction: undefined,
|
|
36
|
-
stopTransactionAtWh: undefined,
|
|
37
|
-
status: connector.evConnected ? "Finishing" : connector.availability === "Inoperative" ? "Unavailable" : "Available"
|
|
38
|
-
});
|
|
39
|
-
this.options.save();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const transactionId = readNumber(response.payload.transactionId, Date.now());
|
|
43
|
-
const oldTransactionId = connector.transactionId;
|
|
44
|
-
this.options.connectors.patch(connectorId, {
|
|
45
|
-
transactionId,
|
|
46
|
-
pendingStartTransaction: undefined
|
|
47
|
-
});
|
|
48
|
-
if (oldTransactionId && oldTransactionId !== transactionId) {
|
|
49
|
-
this.options.chargingProfiles.clearForTransaction(oldTransactionId);
|
|
50
|
-
}
|
|
51
|
-
this.options.save();
|
|
52
|
-
this.options.log("success", `Offline transaction on connector ${connectorId} synced as transaction ${transactionId}`);
|
|
53
|
-
if (this.options.connectors.get(connectorId).pendingStopTransaction) {
|
|
54
|
-
await this.deliverPendingStopTransaction(connectorId);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
async deliverPendingStopTransaction(connectorId) {
|
|
58
|
-
const connector = this.options.connectors.get(connectorId);
|
|
59
|
-
if (!connector.pendingStopTransaction || !connector.transactionId) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const payload = {
|
|
63
|
-
...connector.pendingStopTransaction,
|
|
64
|
-
transactionId: connector.transactionId
|
|
65
|
-
};
|
|
66
|
-
await this.options.send("StopTransaction", payload);
|
|
67
|
-
const { evConnected, status } = statusAfterStopTransaction(connector.pendingStopTransaction.reason, connector.evConnected, connector.availability);
|
|
68
|
-
const transactionId = connector.transactionId;
|
|
69
|
-
this.options.connectors.patch(connectorId, {
|
|
70
|
-
transactionId: undefined,
|
|
71
|
-
transactionStartedAt: undefined,
|
|
72
|
-
stopTransactionAtWh: undefined,
|
|
73
|
-
pendingStartTransaction: undefined,
|
|
74
|
-
pendingStopTransaction: undefined,
|
|
75
|
-
evConnected,
|
|
76
|
-
status
|
|
77
|
-
});
|
|
78
|
-
if (this.options.chargingProfiles.clearForTransaction(transactionId)) {
|
|
79
|
-
this.options.save();
|
|
80
|
-
}
|
|
81
|
-
this.options.save();
|
|
82
|
-
await this.options.statusNotification(connectorId, status);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export function localTransactionId(connectorId) {
|
|
86
|
-
return -Math.abs(Date.now() * 100 + connectorId);
|
|
87
|
-
}
|
|
88
|
-
export function pendingStopPayload(options) {
|
|
89
|
-
const payload = {
|
|
90
|
-
meterStop: options.meterStop,
|
|
91
|
-
timestamp: options.timestamp,
|
|
92
|
-
reason: options.reason,
|
|
93
|
-
transactionData: options.transactionData
|
|
94
|
-
};
|
|
95
|
-
if (options.idTag) {
|
|
96
|
-
payload.idTag = options.idTag;
|
|
97
|
-
}
|
|
98
|
-
return payload;
|
|
99
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export declare function readString(value: unknown, fallback: string): string;
|
|
2
|
-
export declare function readOptionalString(value: unknown): string | undefined;
|
|
3
|
-
export declare function readNumber(value: unknown, fallback: number): number;
|
|
4
|
-
export declare function readObject(value: unknown): Record<string, unknown>;
|
package/dist/station/payload.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function readString(value, fallback) {
|
|
2
|
-
return typeof value === "string" ? value : fallback;
|
|
3
|
-
}
|
|
4
|
-
export function readOptionalString(value) {
|
|
5
|
-
return typeof value === "string" ? value : undefined;
|
|
6
|
-
}
|
|
7
|
-
export function readNumber(value, fallback) {
|
|
8
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
9
|
-
}
|
|
10
|
-
export function readObject(value) {
|
|
11
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { ConnectorState } from "../ocpp/types.js";
|
|
2
|
-
import type { PersistedStationState } from "../station-store.js";
|
|
3
|
-
import type { ConfigurationKey } from "./configuration.js";
|
|
4
|
-
import type { ConfigurationRegistry } from "./configuration-registry.js";
|
|
5
|
-
import type { ConnectorRegistry } from "./connector-registry.js";
|
|
6
|
-
import { type ChargingProfileEntry } from "./smart-charging.js";
|
|
7
|
-
export declare function buildPersistedStationState(options: {
|
|
8
|
-
chargePointId: string;
|
|
9
|
-
connectorId: number;
|
|
10
|
-
connectors: ConnectorState[];
|
|
11
|
-
localListVersion: number;
|
|
12
|
-
localAuthorizationList: Map<string, Record<string, unknown> | undefined>;
|
|
13
|
-
authorizationCache: Map<string, Record<string, unknown> | undefined>;
|
|
14
|
-
chargingProfiles: ChargingProfileEntry[];
|
|
15
|
-
configuration: Map<string, ConfigurationKey>;
|
|
16
|
-
}): PersistedStationState;
|
|
17
|
-
export declare function restorePersistedStationState(options: {
|
|
18
|
-
persisted: PersistedStationState;
|
|
19
|
-
connectorId: number;
|
|
20
|
-
connectors: ConnectorRegistry;
|
|
21
|
-
configuration: ConfigurationRegistry;
|
|
22
|
-
localAuthorizationList: Map<string, Record<string, unknown> | undefined>;
|
|
23
|
-
authorizationCache: Map<string, Record<string, unknown> | undefined>;
|
|
24
|
-
scheduleReservation: (connectorId: number) => void;
|
|
25
|
-
}): {
|
|
26
|
-
localListVersion: number;
|
|
27
|
-
chargingProfiles: ChargingProfileEntry[];
|
|
28
|
-
};
|