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