Move OCPP log message formatting helper into static helpers class
[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';
56 }
57 }
58
ed3d2808 59 public static isRequestCommandSupported(
fd3c56d1
JB
60 chargingStation: ChargingStation,
61 command: RequestCommand
ed3d2808 62 ): boolean {
edd13439 63 const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
ed3d2808
JB
64 if (
65 isRequestCommand === true &&
66 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
67 ) {
68 return true;
69 } else if (
70 isRequestCommand === true &&
71 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
72 ) {
73 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
74 }
75 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
76 return false;
77 }
78
79 public static isIncomingRequestCommandSupported(
fd3c56d1
JB
80 chargingStation: ChargingStation,
81 command: IncomingRequestCommand
ed3d2808 82 ): boolean {
edd13439
JB
83 const isIncomingRequestCommand =
84 Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
ed3d2808
JB
85 if (
86 isIncomingRequestCommand === true &&
87 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
88 ) {
89 return true;
90 } else if (
91 isIncomingRequestCommand === true &&
92 chargingStation.stationInfo?.commandsSupport?.incomingCommands
93 ) {
94 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
95 }
96 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
97 return false;
98 }
99
c60ed4b8
JB
100 public static isMessageTriggerSupported(
101 chargingStation: ChargingStation,
102 messageTrigger: MessageTrigger
103 ): boolean {
104 const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
105 if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
106 return true;
107 } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) {
108 return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false;
109 }
110 logger.error(
111 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
112 );
113 return false;
114 }
115
116 public static isConnectorIdValid(
117 chargingStation: ChargingStation,
118 ocppCommand: IncomingRequestCommand,
119 connectorId: number
120 ): boolean {
121 if (connectorId < 0) {
122 logger.error(
8eb3b688 123 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
c60ed4b8
JB
124 );
125 return false;
126 }
127 return true;
128 }
129
1799761a 130 public static convertDateToISOString<T extends JsonType>(obj: T): void {
02887891
JB
131 for (const key in obj) {
132 if (obj[key] instanceof Date) {
133 (obj as JsonObject)[key] = (obj[key] as Date).toISOString();
134 } else if (obj[key] !== null && typeof obj[key] === 'object') {
135 this.convertDateToISOString<T>(obj[key] as T);
1799761a
JB
136 }
137 }
138 }
139
6e939d9e
JB
140 public static buildStatusNotificationRequest(
141 chargingStation: ChargingStation,
142 connectorId: number,
143 status: ConnectorStatusEnum
144 ): StatusNotificationRequest {
cda96260 145 switch (chargingStation.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16) {
6e939d9e
JB
146 case OCPPVersion.VERSION_16:
147 return {
148 connectorId,
149 status,
150 errorCode: ChargePointErrorCode.NO_ERROR,
151 } as OCPP16StatusNotificationRequest;
152 case OCPPVersion.VERSION_20:
153 case OCPPVersion.VERSION_201:
154 return {
155 timestamp: new Date(),
156 connectorStatus: status,
157 connectorId,
158 evseId: connectorId,
159 } as OCPP20StatusNotificationRequest;
cda96260
JB
160 default:
161 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
6e939d9e
JB
162 }
163 }
164
884a6fdf
JB
165 protected static getSampledValueTemplate(
166 chargingStation: ChargingStation,
167 connectorId: number,
168 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
169 phase?: MeterValuePhase
170 ): SampledValueTemplate | undefined {
171 const onPhaseStr = phase ? `on phase ${phase} ` : '';
172 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
173 logger.warn(
174 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
175 );
176 return;
177 }
178 if (
179 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
23290150 180 ChargingStationConfigurationUtils.getConfigurationKey(
884a6fdf
JB
181 chargingStation,
182 StandardParametersKey.MeterValuesSampledData
23290150 183 )?.value.includes(measurand) === false
884a6fdf
JB
184 ) {
185 logger.debug(
186 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
187 StandardParametersKey.MeterValuesSampledData
188 }' OCPP parameter`
189 );
190 return;
191 }
192 const sampledValueTemplates: SampledValueTemplate[] =
193 chargingStation.getConnectorStatus(connectorId).MeterValues;
194 for (
195 let index = 0;
23290150 196 Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
884a6fdf
JB
197 index++
198 ) {
199 if (
200 Constants.SUPPORTED_MEASURANDS.includes(
201 sampledValueTemplates[index]?.measurand ??
202 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
203 ) === false
204 ) {
205 logger.warn(
206 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
207 );
208 } else if (
209 phase &&
210 sampledValueTemplates[index]?.phase === phase &&
211 sampledValueTemplates[index]?.measurand === measurand &&
212 ChargingStationConfigurationUtils.getConfigurationKey(
213 chargingStation,
214 StandardParametersKey.MeterValuesSampledData
215 )?.value.includes(measurand) === true
216 ) {
217 return sampledValueTemplates[index];
218 } else if (
219 !phase &&
220 !sampledValueTemplates[index].phase &&
221 sampledValueTemplates[index]?.measurand === measurand &&
222 ChargingStationConfigurationUtils.getConfigurationKey(
223 chargingStation,
224 StandardParametersKey.MeterValuesSampledData
225 )?.value.includes(measurand) === true
226 ) {
227 return sampledValueTemplates[index];
228 } else if (
229 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
230 (!sampledValueTemplates[index].measurand ||
231 sampledValueTemplates[index].measurand === measurand)
232 ) {
233 return sampledValueTemplates[index];
234 }
235 }
236 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
237 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
238 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
239 throw new BaseError(errorMsg);
240 }
241 logger.debug(
242 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
243 );
244 }
245
90befdb8
JB
246 protected static getLimitFromSampledValueTemplateCustomValue(
247 value: string,
248 limit: number,
249 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
250 limitationEnabled: true,
251 unitMultiplier: 1,
252 }
253 ): number {
254 options.limitationEnabled = options?.limitationEnabled ?? true;
255 options.unitMultiplier = options?.unitMultiplier ?? 1;
231d1ecd
JB
256 const parsedInt = parseInt(value);
257 const numberValue = isNaN(parsedInt) ? Infinity : parsedInt;
90befdb8 258 return options?.limitationEnabled
f126aa15
JB
259 ? Math.min(numberValue * options.unitMultiplier, limit)
260 : numberValue * options.unitMultiplier;
90befdb8
JB
261 }
262}