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