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