Add Hearbeat command to OCPP 2.0.1
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
CommitLineData
6c1761d4 1import type { DefinedError, ErrorObject } from 'ajv';
06ad945f 2
884a6fdf 3import BaseError from '../../exception/BaseError';
1799761a 4import type { JsonObject, JsonType } from '../../types/JsonType';
884a6fdf
JB
5import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
6import { StandardParametersKey } from '../../types/ocpp/Configuration';
06ad945f 7import { ErrorType } from '../../types/ocpp/ErrorType';
884a6fdf 8import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
c60ed4b8 9import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../types/ocpp/Requests';
884a6fdf 10import Constants from '../../utils/Constants';
ed3d2808 11import logger from '../../utils/Logger';
884a6fdf 12import Utils from '../../utils/Utils';
ed3d2808 13import type ChargingStation from '../ChargingStation';
884a6fdf 14import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
06ad945f 15
90befdb8 16export class OCPPServiceUtils {
d5bd1c00
JB
17 protected constructor() {
18 // This is intentional
19 }
20
01a4dcbb 21 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
06ad945f
JB
22 for (const error of errors as DefinedError[]) {
23 switch (error.keyword) {
24 case 'type':
25 return ErrorType.TYPE_CONSTRAINT_VIOLATION;
26 case 'dependencies':
27 case 'required':
28 return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
29 case 'pattern':
30 case 'format':
31 return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
32 }
33 }
34 return ErrorType.FORMAT_VIOLATION;
35 }
36
ed3d2808 37 public static isRequestCommandSupported(
fd3c56d1
JB
38 chargingStation: ChargingStation,
39 command: RequestCommand
ed3d2808 40 ): boolean {
edd13439 41 const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
ed3d2808
JB
42 if (
43 isRequestCommand === true &&
44 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
45 ) {
46 return true;
47 } else if (
48 isRequestCommand === true &&
49 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
50 ) {
51 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
52 }
53 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
54 return false;
55 }
56
57 public static isIncomingRequestCommandSupported(
fd3c56d1
JB
58 chargingStation: ChargingStation,
59 command: IncomingRequestCommand
ed3d2808 60 ): boolean {
edd13439
JB
61 const isIncomingRequestCommand =
62 Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
ed3d2808
JB
63 if (
64 isIncomingRequestCommand === true &&
65 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
66 ) {
67 return true;
68 } else if (
69 isIncomingRequestCommand === true &&
70 chargingStation.stationInfo?.commandsSupport?.incomingCommands
71 ) {
72 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
73 }
74 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
75 return false;
76 }
77
c60ed4b8
JB
78 public static isMessageTriggerSupported(
79 chargingStation: ChargingStation,
80 messageTrigger: MessageTrigger
81 ): boolean {
82 const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
83 if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
84 return true;
85 } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) {
86 return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false;
87 }
88 logger.error(
89 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
90 );
91 return false;
92 }
93
94 public static isConnectorIdValid(
95 chargingStation: ChargingStation,
96 ocppCommand: IncomingRequestCommand,
97 connectorId: number
98 ): boolean {
99 if (connectorId < 0) {
100 logger.error(
8eb3b688 101 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
c60ed4b8
JB
102 );
103 return false;
104 }
105 return true;
106 }
107
1799761a 108 public static convertDateToISOString<T extends JsonType>(obj: T): void {
02887891
JB
109 for (const key in obj) {
110 if (obj[key] instanceof Date) {
111 (obj as JsonObject)[key] = (obj[key] as Date).toISOString();
112 } else if (obj[key] !== null && typeof obj[key] === 'object') {
113 this.convertDateToISOString<T>(obj[key] as T);
1799761a
JB
114 }
115 }
116 }
117
884a6fdf
JB
118 protected static getSampledValueTemplate(
119 chargingStation: ChargingStation,
120 connectorId: number,
121 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
122 phase?: MeterValuePhase
123 ): SampledValueTemplate | undefined {
124 const onPhaseStr = phase ? `on phase ${phase} ` : '';
125 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
126 logger.warn(
127 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
128 );
129 return;
130 }
131 if (
132 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
23290150 133 ChargingStationConfigurationUtils.getConfigurationKey(
884a6fdf
JB
134 chargingStation,
135 StandardParametersKey.MeterValuesSampledData
23290150 136 )?.value.includes(measurand) === false
884a6fdf
JB
137 ) {
138 logger.debug(
139 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
140 StandardParametersKey.MeterValuesSampledData
141 }' OCPP parameter`
142 );
143 return;
144 }
145 const sampledValueTemplates: SampledValueTemplate[] =
146 chargingStation.getConnectorStatus(connectorId).MeterValues;
147 for (
148 let index = 0;
23290150 149 Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
884a6fdf
JB
150 index++
151 ) {
152 if (
153 Constants.SUPPORTED_MEASURANDS.includes(
154 sampledValueTemplates[index]?.measurand ??
155 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
156 ) === false
157 ) {
158 logger.warn(
159 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
160 );
161 } else if (
162 phase &&
163 sampledValueTemplates[index]?.phase === phase &&
164 sampledValueTemplates[index]?.measurand === measurand &&
165 ChargingStationConfigurationUtils.getConfigurationKey(
166 chargingStation,
167 StandardParametersKey.MeterValuesSampledData
168 )?.value.includes(measurand) === true
169 ) {
170 return sampledValueTemplates[index];
171 } else if (
172 !phase &&
173 !sampledValueTemplates[index].phase &&
174 sampledValueTemplates[index]?.measurand === measurand &&
175 ChargingStationConfigurationUtils.getConfigurationKey(
176 chargingStation,
177 StandardParametersKey.MeterValuesSampledData
178 )?.value.includes(measurand) === true
179 ) {
180 return sampledValueTemplates[index];
181 } else if (
182 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
183 (!sampledValueTemplates[index].measurand ||
184 sampledValueTemplates[index].measurand === measurand)
185 ) {
186 return sampledValueTemplates[index];
187 }
188 }
189 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
190 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
191 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
192 throw new BaseError(errorMsg);
193 }
194 logger.debug(
195 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
196 );
197 }
198
90befdb8
JB
199 protected static getLimitFromSampledValueTemplateCustomValue(
200 value: string,
201 limit: number,
202 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
203 limitationEnabled: true,
204 unitMultiplier: 1,
205 }
206 ): number {
207 options.limitationEnabled = options?.limitationEnabled ?? true;
208 options.unitMultiplier = options?.unitMultiplier ?? 1;
231d1ecd
JB
209 const parsedInt = parseInt(value);
210 const numberValue = isNaN(parsedInt) ? Infinity : parsedInt;
90befdb8 211 return options?.limitationEnabled
f126aa15
JB
212 ? Math.min(numberValue * options.unitMultiplier, limit)
213 : numberValue * options.unitMultiplier;
90befdb8
JB
214 }
215}