Strict null check fixes
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
1 import type { DefinedError, ErrorObject } from 'ajv';
2
3 import BaseError from '../../exception/BaseError';
4 import type { JsonObject, JsonType } from '../../types/JsonType';
5 import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
6 import type { OCPP16StatusNotificationRequest } from '../../types/ocpp/1.6/Requests';
7 import type { OCPP20StatusNotificationRequest } from '../../types/ocpp/2.0/Requests';
8 import { ChargePointErrorCode } from '../../types/ocpp/ChargePointErrorCode';
9 import { StandardParametersKey } from '../../types/ocpp/Configuration';
10 import type { ConnectorStatusEnum } from '../../types/ocpp/ConnectorStatusEnum';
11 import { ErrorType } from '../../types/ocpp/ErrorType';
12 import { MessageType } from '../../types/ocpp/MessageType';
13 import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
14 import { OCPPVersion } from '../../types/ocpp/OCPPVersion';
15 import {
16 IncomingRequestCommand,
17 MessageTrigger,
18 RequestCommand,
19 type StatusNotificationRequest,
20 } from '../../types/ocpp/Requests';
21 import Constants from '../../utils/Constants';
22 import logger from '../../utils/Logger';
23 import Utils from '../../utils/Utils';
24 import type ChargingStation from '../ChargingStation';
25 import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
26
27 export class OCPPServiceUtils {
28 protected constructor() {
29 // This is intentional
30 }
31
32 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
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
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 default:
57 return 'unknown';
58 }
59 }
60
61 public static isRequestCommandSupported(
62 chargingStation: ChargingStation,
63 command: RequestCommand
64 ): boolean {
65 const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
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(
82 chargingStation: ChargingStation,
83 command: IncomingRequestCommand
84 ): boolean {
85 const isIncomingRequestCommand =
86 Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
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
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(
125 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
126 );
127 return false;
128 }
129 return true;
130 }
131
132 public static convertDateToISOString<T extends JsonType>(obj: T): void {
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);
138 }
139 }
140 }
141
142 public static buildStatusNotificationRequest(
143 chargingStation: ChargingStation,
144 connectorId: number,
145 status: ConnectorStatusEnum
146 ): StatusNotificationRequest {
147 switch (chargingStation.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16) {
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;
162 default:
163 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
164 }
165 }
166
167 protected static getSampledValueTemplate(
168 chargingStation: ChargingStation,
169 connectorId: number,
170 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
171 phase?: MeterValuePhase
172 ): SampledValueTemplate | undefined {
173 const onPhaseStr = phase ? `on phase ${phase} ` : '';
174 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
175 logger.warn(
176 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
177 );
178 return;
179 }
180 if (
181 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
182 ChargingStationConfigurationUtils.getConfigurationKey(
183 chargingStation,
184 StandardParametersKey.MeterValuesSampledData
185 )?.value?.includes(measurand) === false
186 ) {
187 logger.debug(
188 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
189 StandardParametersKey.MeterValuesSampledData
190 }' OCPP parameter`
191 );
192 return;
193 }
194 const sampledValueTemplates: SampledValueTemplate[] =
195 chargingStation.getConnectorStatus(connectorId)?.MeterValues;
196 for (
197 let index = 0;
198 Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
199 index++
200 ) {
201 if (
202 Constants.SUPPORTED_MEASURANDS.includes(
203 sampledValueTemplates[index]?.measurand ??
204 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
205 ) === false
206 ) {
207 logger.warn(
208 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
209 );
210 } else if (
211 phase &&
212 sampledValueTemplates[index]?.phase === phase &&
213 sampledValueTemplates[index]?.measurand === measurand &&
214 ChargingStationConfigurationUtils.getConfigurationKey(
215 chargingStation,
216 StandardParametersKey.MeterValuesSampledData
217 )?.value?.includes(measurand) === true
218 ) {
219 return sampledValueTemplates[index];
220 } else if (
221 !phase &&
222 !sampledValueTemplates[index].phase &&
223 sampledValueTemplates[index]?.measurand === measurand &&
224 ChargingStationConfigurationUtils.getConfigurationKey(
225 chargingStation,
226 StandardParametersKey.MeterValuesSampledData
227 )?.value?.includes(measurand) === true
228 ) {
229 return sampledValueTemplates[index];
230 } else if (
231 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
232 (!sampledValueTemplates[index].measurand ||
233 sampledValueTemplates[index].measurand === measurand)
234 ) {
235 return sampledValueTemplates[index];
236 }
237 }
238 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
239 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
240 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
241 throw new BaseError(errorMsg);
242 }
243 logger.debug(
244 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
245 );
246 }
247
248 protected static getLimitFromSampledValueTemplateCustomValue(
249 value: string,
250 limit: number,
251 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
252 limitationEnabled: true,
253 unitMultiplier: 1,
254 }
255 ): number {
256 options.limitationEnabled = options?.limitationEnabled ?? true;
257 options.unitMultiplier = options?.unitMultiplier ?? 1;
258 const parsedInt = parseInt(value);
259 const numberValue = isNaN(parsedInt) ? Infinity : parsedInt;
260 return options?.limitationEnabled
261 ? Math.min(numberValue * options.unitMultiplier, limit)
262 : numberValue * options.unitMultiplier;
263 }
264 }