diff --git a/src/lib/wallbox-simulator/ConnectButton.svelte b/src/lib/wallbox-simulator/ConnectButton.svelte index 7c49e64..d70b182 100644 --- a/src/lib/wallbox-simulator/ConnectButton.svelte +++ b/src/lib/wallbox-simulator/ConnectButton.svelte @@ -2,6 +2,7 @@ import type { Writable } from 'svelte/store'; import Button from '../components/Button.svelte'; +import { startHeartbeat, stopHeartbeat } from './heatbeatTrigger'; import type { ConnectionState } from './store'; import { connectStation, disconnectStation } from './websocket'; @@ -50,8 +51,10 @@ setErrorMessage, addLogMessage ); + startHeartbeat(webSocket); } else if ($connectionState === 'connected') { webSocket = disconnectStation(webSocket, setConnectionState); + stopHeartbeat(); } } diff --git a/src/lib/wallbox-simulator/heatbeatTrigger.ts b/src/lib/wallbox-simulator/heatbeatTrigger.ts new file mode 100644 index 0000000..e8b0c91 --- /dev/null +++ b/src/lib/wallbox-simulator/heatbeatTrigger.ts @@ -0,0 +1,56 @@ +import { HeartbeatRequest } from "./ocpp/messages/heartBeat"; + +const DEFAULT_INTERVAL_SECONDS = 300; + +let heartbeatTimer: NodeJS.Timeout; + +/** + * Start sending OCPP hearbeat messages in regular intervals (default: every 5 minutes). + * + * @param intervalSeconds The interval (in seconds) for the OCPP heartbeat message to be sent. + */ +export function startHeartbeat( + websocket: WebSocket, + intervalSeconds = DEFAULT_INTERVAL_SECONDS +) { + heartbeatTimer = setTimeout(function () { + sendHeartbeatMessage(websocket); + }, intervalSeconds * 1000); + + console.log(`OCPP Heartbeat initialized with ${intervalSeconds} seconds.`); +} + +/** + * Reset the timer for the OCPP heartbeat message. Trigger this, whenever you send any other OCPP + * message to the backend. + * + * (Reasoning: the heartbeat message should only be sent after the wallbox has been idling for the + * specified amount of time...). + * + * @param intervalSeconds The interval (in seconds) for the OCPP heartbeat message to be sent. + */ +export function resetHeartbeatTimer( + websocket: WebSocket, + intervalSeconds = DEFAULT_INTERVAL_SECONDS +) { + stopHeartbeat(); + startHeartbeat(websocket, intervalSeconds); +} + +/** + * Clears the OCPP heartbeat timer so that the wallbox simulator stops sending heartbeat messages. + */ +export function stopHeartbeat() { + if (heartbeatTimer) { + clearTimeout(heartbeatTimer); + } + heartbeatTimer = undefined; + console.log(`OCPP Heartbeat timer cleared.`); +} + +/** + * Sends an OCPP heartbeat message. + */ +function sendHeartbeatMessage(websocket: WebSocket) { + HeartbeatRequest(websocket, {}); +} diff --git a/src/lib/wallbox-simulator/ocpp/messages/bootNotification.ts b/src/lib/wallbox-simulator/ocpp/messages/bootNotification.ts index bbb9c75..16a65ba 100644 --- a/src/lib/wallbox-simulator/ocpp/messages/bootNotification.ts +++ b/src/lib/wallbox-simulator/ocpp/messages/bootNotification.ts @@ -1,19 +1,15 @@ +import type { BootNotificationRequestType } from "../types/bootNotificationRequestType"; import { OcppCallMessageBuilder } from "./ocppMessage"; +/** + * ### 1.2. BootNotification + * + * Sent by the wallbox to the CSMS after booting. + */ export const BootNotification = - OcppCallMessageBuilder("BootNotification"); + OcppCallMessageBuilder("BootNotification"); -export type BootNotificationPayload = { - chargingStation: { - serialNumber: string; - model: string; - vendorName: string; - firmwareVersion: string; - }; - reason: string; -}; - -export const defaultBootNotificationPayload: BootNotificationPayload = { +export const defaultBootNotificationPayload: BootNotificationRequestType = { chargingStation: { serialNumber: "SOME-serial-NUMBER-123", model: "MGWB Fake", diff --git a/src/lib/wallbox-simulator/ocpp/messages/heartBeat.ts b/src/lib/wallbox-simulator/ocpp/messages/heartBeat.ts new file mode 100644 index 0000000..cd2d7e7 --- /dev/null +++ b/src/lib/wallbox-simulator/ocpp/messages/heartBeat.ts @@ -0,0 +1,12 @@ +import { OcppCallMessageBuilder } from "./ocppMessage"; + +export type HeartBeatRequestPayload = {}; + +/** + * ### 1.29.1. HeartbeatRequest + * + * This contains the field definition of the HeartbeatRequest PDU sent by the + * Charging Station to the CSMS. No fields are defined. + */ +export const HeartbeatRequest = + OcppCallMessageBuilder("Heartbeat"); diff --git a/src/lib/wallbox-simulator/ocpp/messages/ocppMessage.ts b/src/lib/wallbox-simulator/ocpp/messages/ocppMessage.ts index 39943cb..2084437 100644 --- a/src/lib/wallbox-simulator/ocpp/messages/ocppMessage.ts +++ b/src/lib/wallbox-simulator/ocpp/messages/ocppMessage.ts @@ -1,4 +1,5 @@ import { v4 as uuidv4 } from "uuid"; +import { resetHeartbeatTimer } from "../../heatbeatTrigger"; /** * Specifies the nature of any OCPP message. To identify the type of message one @@ -12,9 +13,12 @@ export enum OcppMessageType { /** * List of OCCP actions implemented & supported by the simulator. - * */ -const ocppActions = ["BootNotification", "TransactionEvent"] as const; +const ocppActions = [ + "BootNotification", + "Heartbeat", + "TransactionEvent", +] as const; export type OcppAction = typeof ocppActions[number]; /** @@ -72,8 +76,11 @@ export function OcppCallMessageBuilder( ]); console.log(`Sending ${ocppActionType}.`); + + resetHeartbeatTimer(websocket); websocket.send(ocppMessage); sessionStorage.setItem("LastAction", ocppActionType); + console.log(`${ocppActionType} sent!`); }; } diff --git a/src/lib/wallbox-simulator/ocpp/types/bootNotificationRequestType.ts b/src/lib/wallbox-simulator/ocpp/types/bootNotificationRequestType.ts new file mode 100644 index 0000000..c5027fe --- /dev/null +++ b/src/lib/wallbox-simulator/ocpp/types/bootNotificationRequestType.ts @@ -0,0 +1,13 @@ +import type { BootReasonEnumType } from "./bootReasonEnumType"; +import type { ChargingStationType } from "./chargingStationType"; + +/** + * ### 1.2.1. BootNotificationRequest + * + * This contains the field definition of the BootNotificationRequest PDU sent by + * the Charging Station to the CSMS. + */ +export type BootNotificationRequestType = { + reason: BootReasonEnumType; + chargingStation: ChargingStationType; +}; diff --git a/src/lib/wallbox-simulator/ocpp/types/bootReasonEnumType.ts b/src/lib/wallbox-simulator/ocpp/types/bootReasonEnumType.ts new file mode 100644 index 0000000..fb9e455 --- /dev/null +++ b/src/lib/wallbox-simulator/ocpp/types/bootReasonEnumType.ts @@ -0,0 +1,19 @@ +/** + * ### 2.5. BootReasonEnumType + * _Enumeration_ + * + * BootReasonEnumType is used by: `bootNotification:BootNotificationRequest` + */ +export type BootReasonEnumType = typeof bootReasons[number]; + +export const bootReasons = [ + "ApplicationReset", // The Charging Station rebooted due to an application error. + "FirmwareUpdate", // The Charging Station rebooted due to a firmware update. + "LocalReset", // The Charging Station rebooted due to a local reset command. + "PowerUp", // The Charging Station powered up and registers itself with the CSMS. + "RemoteReset", // The Charging Station rebooted due to a remote reset command. + "ScheduledReset", // The Charging Station rebooted due to a scheduled reset command. + "Triggered", // Requested by the CSMS via a TriggerMessage + "Unknown", // The boot reason is unknown. + "Watchdog", // The Charging Station rebooted due to an elapsed watchdog timer. +] as const; diff --git a/src/lib/wallbox-simulator/ocpp/types/chargingStationType.ts b/src/lib/wallbox-simulator/ocpp/types/chargingStationType.ts new file mode 100644 index 0000000..be4df52 --- /dev/null +++ b/src/lib/wallbox-simulator/ocpp/types/chargingStationType.ts @@ -0,0 +1,14 @@ +/** + * ### 1.13. ChargingStationType + * _Class_ + * + * The physical system where an Electrical Vehicle (EV) can be charged. + * ChargingStationType is used by: _BootNotificationRequest_ + */ +export type ChargingStationType = { + serialNumber?: string; // string[0..20] 0..1 Optional. Vendor-specific device identifier. + model: string; // string[0..20] 1..1 Required. Defines the model of the device. + vendorName: string; // string[0..50] 1..1 Required. Identifies the vendor (not necessarily in a unique manner). + firmwareVersion?: string; // string[0..50] 0..1 Optional. This contains the firmware version of the Charging Station. + modem?: any; // ModemType 0..1 Optional. Defines the functional +};