Fixes to OCPP command payload validation:
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
CommitLineData
6c1761d4 1import type { DefinedError, ErrorObject } from 'ajv';
06ad945f 2
884a6fdf
JB
3import BaseError from '../../exception/BaseError';
4import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
5import { StandardParametersKey } from '../../types/ocpp/Configuration';
06ad945f 6import { ErrorType } from '../../types/ocpp/ErrorType';
884a6fdf 7import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
c60ed4b8 8import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../types/ocpp/Requests';
884a6fdf 9import Constants from '../../utils/Constants';
ed3d2808 10import logger from '../../utils/Logger';
884a6fdf 11import Utils from '../../utils/Utils';
ed3d2808 12import type ChargingStation from '../ChargingStation';
884a6fdf 13import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
06ad945f 14
90befdb8 15export class OCPPServiceUtils {
d5bd1c00
JB
16 protected constructor() {
17 // This is intentional
18 }
19
01a4dcbb 20 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
06ad945f
JB
21 for (const error of errors as DefinedError[]) {
22 switch (error.keyword) {
23 case 'type':
24 return ErrorType.TYPE_CONSTRAINT_VIOLATION;
25 case 'dependencies':
26 case 'required':
27 return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
28 case 'pattern':
29 case 'format':
30 return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
31 }
32 }
33 return ErrorType.FORMAT_VIOLATION;
34 }
35
ed3d2808 36 public static isRequestCommandSupported(
fd3c56d1
JB
37 chargingStation: ChargingStation,
38 command: RequestCommand
ed3d2808 39 ): boolean {
edd13439 40 const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
ed3d2808
JB
41 if (
42 isRequestCommand === true &&
43 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
44 ) {
45 return true;
46 } else if (
47 isRequestCommand === true &&
48 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
49 ) {
50 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
51 }
52 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
53 return false;
54 }
55
56 public static isIncomingRequestCommandSupported(
fd3c56d1
JB
57 chargingStation: ChargingStation,
58 command: IncomingRequestCommand
ed3d2808 59 ): boolean {
edd13439
JB
60 const isIncomingRequestCommand =
61 Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
ed3d2808
JB
62 if (
63 isIncomingRequestCommand === true &&
64 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
65 ) {
66 return true;
67 } else if (
68 isIncomingRequestCommand === true &&
69 chargingStation.stationInfo?.commandsSupport?.incomingCommands
70 ) {
71 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
72 }
73 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
74 return false;
75 }
76
c60ed4b8
JB
77 public static isMessageTriggerSupported(
78 chargingStation: ChargingStation,
79 messageTrigger: MessageTrigger
80 ): boolean {
81 const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
82 if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
83 return true;
84 } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) {
85 return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false;
86 }
87 logger.error(
88 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
89 );
90 return false;
91 }
92
93 public static isConnectorIdValid(
94 chargingStation: ChargingStation,
95 ocppCommand: IncomingRequestCommand,
96 connectorId: number
97 ): boolean {
98 if (connectorId < 0) {
99 logger.error(
8eb3b688 100 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
c60ed4b8
JB
101 );
102 return false;
103 }
104 return true;
105 }
106
884a6fdf
JB
107 protected static getSampledValueTemplate(
108 chargingStation: ChargingStation,
109 connectorId: number,
110 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
111 phase?: MeterValuePhase
112 ): SampledValueTemplate | undefined {
113 const onPhaseStr = phase ? `on phase ${phase} ` : '';
114 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
115 logger.warn(
116 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
117 );
118 return;
119 }
120 if (
121 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
23290150 122 ChargingStationConfigurationUtils.getConfigurationKey(
884a6fdf
JB
123 chargingStation,
124 StandardParametersKey.MeterValuesSampledData
23290150 125 )?.value.includes(measurand) === false
884a6fdf
JB
126 ) {
127 logger.debug(
128 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
129 StandardParametersKey.MeterValuesSampledData
130 }' OCPP parameter`
131 );
132 return;
133 }
134 const sampledValueTemplates: SampledValueTemplate[] =
135 chargingStation.getConnectorStatus(connectorId).MeterValues;
136 for (
137 let index = 0;
23290150 138 Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
884a6fdf
JB
139 index++
140 ) {
141 if (
142 Constants.SUPPORTED_MEASURANDS.includes(
143 sampledValueTemplates[index]?.measurand ??
144 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
145 ) === false
146 ) {
147 logger.warn(
148 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
149 );
150 } else if (
151 phase &&
152 sampledValueTemplates[index]?.phase === phase &&
153 sampledValueTemplates[index]?.measurand === measurand &&
154 ChargingStationConfigurationUtils.getConfigurationKey(
155 chargingStation,
156 StandardParametersKey.MeterValuesSampledData
157 )?.value.includes(measurand) === true
158 ) {
159 return sampledValueTemplates[index];
160 } else if (
161 !phase &&
162 !sampledValueTemplates[index].phase &&
163 sampledValueTemplates[index]?.measurand === measurand &&
164 ChargingStationConfigurationUtils.getConfigurationKey(
165 chargingStation,
166 StandardParametersKey.MeterValuesSampledData
167 )?.value.includes(measurand) === true
168 ) {
169 return sampledValueTemplates[index];
170 } else if (
171 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
172 (!sampledValueTemplates[index].measurand ||
173 sampledValueTemplates[index].measurand === measurand)
174 ) {
175 return sampledValueTemplates[index];
176 }
177 }
178 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
179 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
180 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
181 throw new BaseError(errorMsg);
182 }
183 logger.debug(
184 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
185 );
186 }
187
90befdb8
JB
188 protected static getLimitFromSampledValueTemplateCustomValue(
189 value: string,
190 limit: number,
191 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
192 limitationEnabled: true,
193 unitMultiplier: 1,
194 }
195 ): number {
196 options.limitationEnabled = options?.limitationEnabled ?? true;
197 options.unitMultiplier = options?.unitMultiplier ?? 1;
231d1ecd
JB
198 const parsedInt = parseInt(value);
199 const numberValue = isNaN(parsedInt) ? Infinity : parsedInt;
90befdb8 200 return options?.limitationEnabled
f126aa15
JB
201 ? Math.min(numberValue * options.unitMultiplier, limit)
202 : numberValue * options.unitMultiplier;
90befdb8
JB
203 }
204}