refactor(simulator): switch to named exports
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
CommitLineData
7164966d
JB
1import fs from 'node:fs';
2
3import type { DefinedError, ErrorObject, JSONSchemaType } from 'ajv';
06ad945f 4
268a74bb 5import { BaseError } from '../../exception';
6e939d9e 6import {
268a74bb
JB
7 ChargePointErrorCode,
8 type ConnectorStatusEnum,
9 ErrorType,
10 FileType,
6e939d9e 11 IncomingRequestCommand,
268a74bb
JB
12 type JsonObject,
13 type JsonType,
6e939d9e 14 MessageTrigger,
268a74bb
JB
15 MessageType,
16 MeterValueMeasurand,
17 type MeterValuePhase,
18 type OCPP16StatusNotificationRequest,
19 type OCPP20StatusNotificationRequest,
20 OCPPVersion,
6e939d9e 21 RequestCommand,
268a74bb
JB
22 type SampledValueTemplate,
23 StandardParametersKey,
6e939d9e 24 type StatusNotificationRequest,
268a74bb
JB
25} from '../../types';
26import { Constants } from '../../utils/Constants';
27import { FileUtils } from '../../utils/FileUtils';
28import { logger } from '../../utils/Logger';
29import { Utils } from '../../utils/Utils';
30import type { ChargingStation } from '../ChargingStation';
884a6fdf 31import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
06ad945f 32
90befdb8 33export class OCPPServiceUtils {
d5bd1c00
JB
34 protected constructor() {
35 // This is intentional
36 }
37
01a4dcbb 38 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
06ad945f
JB
39 for (const error of errors as DefinedError[]) {
40 switch (error.keyword) {
41 case 'type':
42 return ErrorType.TYPE_CONSTRAINT_VIOLATION;
43 case 'dependencies':
44 case 'required':
45 return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
46 case 'pattern':
47 case 'format':
48 return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
49 }
50 }
51 return ErrorType.FORMAT_VIOLATION;
52 }
53
2cc5d5ec
JB
54 public static getMessageTypeString(messageType: MessageType): string {
55 switch (messageType) {
56 case MessageType.CALL_MESSAGE:
57 return 'request';
58 case MessageType.CALL_RESULT_MESSAGE:
59 return 'response';
60 case MessageType.CALL_ERROR_MESSAGE:
61 return 'error';
336f2829
JB
62 default:
63 return 'unknown';
2cc5d5ec
JB
64 }
65 }
66
ed3d2808 67 public static isRequestCommandSupported(
fd3c56d1
JB
68 chargingStation: ChargingStation,
69 command: RequestCommand
ed3d2808 70 ): boolean {
edd13439 71 const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
ed3d2808
JB
72 if (
73 isRequestCommand === true &&
74 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
75 ) {
76 return true;
77 } else if (
78 isRequestCommand === true &&
79 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
80 ) {
81 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
82 }
83 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
84 return false;
85 }
86
87 public static isIncomingRequestCommandSupported(
fd3c56d1
JB
88 chargingStation: ChargingStation,
89 command: IncomingRequestCommand
ed3d2808 90 ): boolean {
edd13439
JB
91 const isIncomingRequestCommand =
92 Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
ed3d2808
JB
93 if (
94 isIncomingRequestCommand === true &&
95 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
96 ) {
97 return true;
98 } else if (
99 isIncomingRequestCommand === true &&
100 chargingStation.stationInfo?.commandsSupport?.incomingCommands
101 ) {
102 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
103 }
104 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
105 return false;
106 }
107
c60ed4b8
JB
108 public static isMessageTriggerSupported(
109 chargingStation: ChargingStation,
110 messageTrigger: MessageTrigger
111 ): boolean {
112 const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
113 if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
114 return true;
115 } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) {
116 return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false;
117 }
118 logger.error(
119 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
120 );
121 return false;
122 }
123
124 public static isConnectorIdValid(
125 chargingStation: ChargingStation,
126 ocppCommand: IncomingRequestCommand,
127 connectorId: number
128 ): boolean {
129 if (connectorId < 0) {
130 logger.error(
8eb3b688 131 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
c60ed4b8
JB
132 );
133 return false;
134 }
135 return true;
136 }
137
1799761a 138 public static convertDateToISOString<T extends JsonType>(obj: T): void {
02887891
JB
139 for (const key in obj) {
140 if (obj[key] instanceof Date) {
141 (obj as JsonObject)[key] = (obj[key] as Date).toISOString();
142 } else if (obj[key] !== null && typeof obj[key] === 'object') {
143 this.convertDateToISOString<T>(obj[key] as T);
1799761a
JB
144 }
145 }
146 }
147
6e939d9e
JB
148 public static buildStatusNotificationRequest(
149 chargingStation: ChargingStation,
150 connectorId: number,
151 status: ConnectorStatusEnum
152 ): StatusNotificationRequest {
cda96260 153 switch (chargingStation.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16) {
6e939d9e
JB
154 case OCPPVersion.VERSION_16:
155 return {
156 connectorId,
157 status,
158 errorCode: ChargePointErrorCode.NO_ERROR,
159 } as OCPP16StatusNotificationRequest;
160 case OCPPVersion.VERSION_20:
161 case OCPPVersion.VERSION_201:
162 return {
163 timestamp: new Date(),
164 connectorStatus: status,
165 connectorId,
166 evseId: connectorId,
167 } as OCPP20StatusNotificationRequest;
cda96260
JB
168 default:
169 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
6e939d9e
JB
170 }
171 }
172
7164966d
JB
173 protected static parseJsonSchemaFile<T extends JsonType>(
174 filePath: string,
1b271a54
JB
175 ocppVersion: OCPPVersion,
176 moduleName?: string,
177 methodName?: string
7164966d
JB
178 ): JSONSchemaType<T> {
179 try {
180 return JSON.parse(fs.readFileSync(filePath, 'utf8')) as JSONSchemaType<T>;
181 } catch (error) {
182 FileUtils.handleFileException(
183 filePath,
184 FileType.JsonSchema,
185 error as NodeJS.ErrnoException,
1b271a54 186 OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName),
7164966d
JB
187 { throwError: false }
188 );
189 }
130783a7
JB
190 }
191
884a6fdf
JB
192 protected static getSampledValueTemplate(
193 chargingStation: ChargingStation,
194 connectorId: number,
195 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
196 phase?: MeterValuePhase
197 ): SampledValueTemplate | undefined {
198 const onPhaseStr = phase ? `on phase ${phase} ` : '';
199 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
200 logger.warn(
201 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
202 );
203 return;
204 }
205 if (
206 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
23290150 207 ChargingStationConfigurationUtils.getConfigurationKey(
884a6fdf
JB
208 chargingStation,
209 StandardParametersKey.MeterValuesSampledData
72092cfc 210 )?.value?.includes(measurand) === false
884a6fdf
JB
211 ) {
212 logger.debug(
213 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
214 StandardParametersKey.MeterValuesSampledData
215 }' OCPP parameter`
216 );
217 return;
218 }
219 const sampledValueTemplates: SampledValueTemplate[] =
72092cfc 220 chargingStation.getConnectorStatus(connectorId)?.MeterValues;
884a6fdf
JB
221 for (
222 let index = 0;
53ac516c 223 Utils.isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length;
884a6fdf
JB
224 index++
225 ) {
226 if (
227 Constants.SUPPORTED_MEASURANDS.includes(
228 sampledValueTemplates[index]?.measurand ??
229 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
230 ) === false
231 ) {
232 logger.warn(
233 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
234 );
235 } else if (
236 phase &&
237 sampledValueTemplates[index]?.phase === phase &&
238 sampledValueTemplates[index]?.measurand === measurand &&
239 ChargingStationConfigurationUtils.getConfigurationKey(
240 chargingStation,
241 StandardParametersKey.MeterValuesSampledData
72092cfc 242 )?.value?.includes(measurand) === true
884a6fdf
JB
243 ) {
244 return sampledValueTemplates[index];
245 } else if (
246 !phase &&
247 !sampledValueTemplates[index].phase &&
248 sampledValueTemplates[index]?.measurand === measurand &&
249 ChargingStationConfigurationUtils.getConfigurationKey(
250 chargingStation,
251 StandardParametersKey.MeterValuesSampledData
72092cfc 252 )?.value?.includes(measurand) === true
884a6fdf
JB
253 ) {
254 return sampledValueTemplates[index];
255 } else if (
256 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
257 (!sampledValueTemplates[index].measurand ||
258 sampledValueTemplates[index].measurand === measurand)
259 ) {
260 return sampledValueTemplates[index];
261 }
262 }
263 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
264 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
265 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
266 throw new BaseError(errorMsg);
267 }
268 logger.debug(
269 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
270 );
271 }
272
90befdb8
JB
273 protected static getLimitFromSampledValueTemplateCustomValue(
274 value: string,
275 limit: number,
276 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
277 limitationEnabled: true,
278 unitMultiplier: 1,
279 }
280 ): number {
281 options.limitationEnabled = options?.limitationEnabled ?? true;
282 options.unitMultiplier = options?.unitMultiplier ?? 1;
231d1ecd
JB
283 const parsedInt = parseInt(value);
284 const numberValue = isNaN(parsedInt) ? Infinity : parsedInt;
90befdb8 285 return options?.limitationEnabled
f126aa15
JB
286 ? Math.min(numberValue * options.unitMultiplier, limit)
287 : numberValue * options.unitMultiplier;
90befdb8 288 }
7164966d 289
1b271a54
JB
290 private static logPrefix = (
291 ocppVersion: OCPPVersion,
292 moduleName?: string,
293 methodName?: string
294 ): string => {
295 const logMsg =
5a2a53cf 296 Utils.isNotEmptyString(moduleName) && Utils.isNotEmptyString(methodName)
1b271a54
JB
297 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
298 : ` OCPP ${ocppVersion} |`;
299 return Utils.logPrefix(logMsg);
8b7072dc 300 };
90befdb8 301}