Add trigger message type feature flag in charging station template
[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 { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
5 import { StandardParametersKey } from '../../types/ocpp/Configuration';
6 import { ErrorType } from '../../types/ocpp/ErrorType';
7 import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
8 import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../types/ocpp/Requests';
9 import Constants from '../../utils/Constants';
10 import logger from '../../utils/Logger';
11 import Utils from '../../utils/Utils';
12 import type ChargingStation from '../ChargingStation';
13 import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
14
15 export class OCPPServiceUtils {
16 protected constructor() {
17 // This is intentional
18 }
19
20 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
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
36 public static isRequestCommandSupported(
37 chargingStation: ChargingStation,
38 command: RequestCommand
39 ): boolean {
40 const isRequestCommand = Object.values(RequestCommand).includes(command);
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(
57 chargingStation: ChargingStation,
58 command: IncomingRequestCommand
59 ): boolean {
60 const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
61 if (
62 isIncomingRequestCommand === true &&
63 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
64 ) {
65 return true;
66 } else if (
67 isIncomingRequestCommand === true &&
68 chargingStation.stationInfo?.commandsSupport?.incomingCommands
69 ) {
70 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
71 }
72 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
73 return false;
74 }
75
76 public static isMessageTriggerSupported(
77 chargingStation: ChargingStation,
78 messageTrigger: MessageTrigger
79 ): boolean {
80 const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
81 if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
82 return true;
83 } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) {
84 return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false;
85 }
86 logger.error(
87 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
88 );
89 return false;
90 }
91
92 public static isConnectorIdValid(
93 chargingStation: ChargingStation,
94 ocppCommand: IncomingRequestCommand,
95 connectorId: number
96 ): boolean {
97 if (connectorId < 0) {
98 logger.error(
99 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connectorId ${connectorId}`
100 );
101 return false;
102 }
103 return true;
104 }
105
106 protected static getSampledValueTemplate(
107 chargingStation: ChargingStation,
108 connectorId: number,
109 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
110 phase?: MeterValuePhase
111 ): SampledValueTemplate | undefined {
112 const onPhaseStr = phase ? `on phase ${phase} ` : '';
113 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
114 logger.warn(
115 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
116 );
117 return;
118 }
119 if (
120 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
121 ChargingStationConfigurationUtils.getConfigurationKey(
122 chargingStation,
123 StandardParametersKey.MeterValuesSampledData
124 )?.value.includes(measurand) === false
125 ) {
126 logger.debug(
127 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
128 StandardParametersKey.MeterValuesSampledData
129 }' OCPP parameter`
130 );
131 return;
132 }
133 const sampledValueTemplates: SampledValueTemplate[] =
134 chargingStation.getConnectorStatus(connectorId).MeterValues;
135 for (
136 let index = 0;
137 Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
138 index++
139 ) {
140 if (
141 Constants.SUPPORTED_MEASURANDS.includes(
142 sampledValueTemplates[index]?.measurand ??
143 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
144 ) === false
145 ) {
146 logger.warn(
147 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
148 );
149 } else if (
150 phase &&
151 sampledValueTemplates[index]?.phase === phase &&
152 sampledValueTemplates[index]?.measurand === measurand &&
153 ChargingStationConfigurationUtils.getConfigurationKey(
154 chargingStation,
155 StandardParametersKey.MeterValuesSampledData
156 )?.value.includes(measurand) === true
157 ) {
158 return sampledValueTemplates[index];
159 } else if (
160 !phase &&
161 !sampledValueTemplates[index].phase &&
162 sampledValueTemplates[index]?.measurand === measurand &&
163 ChargingStationConfigurationUtils.getConfigurationKey(
164 chargingStation,
165 StandardParametersKey.MeterValuesSampledData
166 )?.value.includes(measurand) === true
167 ) {
168 return sampledValueTemplates[index];
169 } else if (
170 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
171 (!sampledValueTemplates[index].measurand ||
172 sampledValueTemplates[index].measurand === measurand)
173 ) {
174 return sampledValueTemplates[index];
175 }
176 }
177 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
178 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
179 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
180 throw new BaseError(errorMsg);
181 }
182 logger.debug(
183 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
184 );
185 }
186
187 protected static getLimitFromSampledValueTemplateCustomValue(
188 value: string,
189 limit: number,
190 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
191 limitationEnabled: true,
192 unitMultiplier: 1,
193 }
194 ): number {
195 options.limitationEnabled = options?.limitationEnabled ?? true;
196 options.unitMultiplier = options?.unitMultiplier ?? 1;
197 const numberValue = isNaN(parseInt(value)) ? Infinity : parseInt(value);
198 return options?.limitationEnabled
199 ? Math.min(numberValue * options.unitMultiplier, limit)
200 : numberValue * options.unitMultiplier;
201 }
202 }