1 import fs from
'node:fs';
3 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv';
5 import { type ChargingStation
, ChargingStationConfigurationUtils
} from
'../../charging-station';
6 import { BaseError
} from
'../../exception';
9 type ConnectorStatusEnum
,
12 IncomingRequestCommand
,
19 type OCPP16StatusNotificationRequest
,
20 type OCPP20StatusNotificationRequest
,
23 type SampledValueTemplate
,
24 StandardParametersKey
,
25 type StatusNotificationRequest
,
27 import { Constants
, FileUtils
, Utils
, logger
} from
'../../utils';
29 export class OCPPServiceUtils
{
30 protected constructor() {
31 // This is intentional
34 public static ajvErrorsToErrorType(errors
: ErrorObject
[]): ErrorType
{
35 for (const error
of errors
as DefinedError
[]) {
36 switch (error
.keyword
) {
38 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
;
41 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
;
44 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
;
47 return ErrorType
.FORMAT_VIOLATION
;
50 public static getMessageTypeString(messageType
: MessageType
): string {
51 switch (messageType
) {
52 case MessageType
.CALL_MESSAGE
:
54 case MessageType
.CALL_RESULT_MESSAGE
:
56 case MessageType
.CALL_ERROR_MESSAGE
:
63 public static isRequestCommandSupported(
64 chargingStation
: ChargingStation
,
65 command
: RequestCommand
67 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
);
69 isRequestCommand
=== true &&
70 !chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
74 isRequestCommand
=== true &&
75 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
77 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
] ?? false;
79 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
83 public static isIncomingRequestCommandSupported(
84 chargingStation
: ChargingStation
,
85 command
: IncomingRequestCommand
87 const isIncomingRequestCommand
=
88 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
);
90 isIncomingRequestCommand
=== true &&
91 !chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
95 isIncomingRequestCommand
=== true &&
96 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
98 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] ?? false;
100 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
104 public static isMessageTriggerSupported(
105 chargingStation
: ChargingStation
,
106 messageTrigger
: MessageTrigger
108 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
);
109 if (isMessageTrigger
=== true && !chargingStation
.stationInfo
?.messageTriggerSupport
) {
111 } else if (isMessageTrigger
=== true && chargingStation
.stationInfo
?.messageTriggerSupport
) {
112 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
] ?? false;
115 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
120 public static isConnectorIdValid(
121 chargingStation
: ChargingStation
,
122 ocppCommand
: IncomingRequestCommand
,
125 if (connectorId
< 0) {
127 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
134 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
135 for (const key
in obj
) {
136 if (obj
[key
] instanceof Date) {
137 (obj
as JsonObject
)[key
] = (obj
[key
] as Date).toISOString();
138 } else if (obj
[key
] !== null && typeof obj
[key
] === 'object') {
139 this.convertDateToISOString
<T
>(obj
[key
] as T
);
144 public static buildStatusNotificationRequest(
145 chargingStation
: ChargingStation
,
147 status: ConnectorStatusEnum
148 ): StatusNotificationRequest
{
149 switch (chargingStation
.stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
) {
150 case OCPPVersion
.VERSION_16
:
154 errorCode
: ChargePointErrorCode
.NO_ERROR
,
155 } as OCPP16StatusNotificationRequest
;
156 case OCPPVersion
.VERSION_20
:
157 case OCPPVersion
.VERSION_201
:
159 timestamp
: new Date(),
160 connectorStatus
: status,
163 } as OCPP20StatusNotificationRequest
;
165 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
169 protected static parseJsonSchemaFile
<T
extends JsonType
>(
171 ocppVersion
: OCPPVersion
,
174 ): JSONSchemaType
<T
> {
176 return JSON
.parse(fs
.readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>;
178 FileUtils
.handleFileException(
181 error
as NodeJS
.ErrnoException
,
182 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
183 { throwError
: false }
188 protected static getSampledValueTemplate(
189 chargingStation
: ChargingStation
,
191 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
192 phase
?: MeterValuePhase
193 ): SampledValueTemplate
| undefined {
194 const onPhaseStr
= phase
? `on phase ${phase} ` : '';
195 if (Constants
.SUPPORTED_MEASURANDS
.includes(measurand
) === false) {
197 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
202 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
203 ChargingStationConfigurationUtils
.getConfigurationKey(
205 StandardParametersKey
.MeterValuesSampledData
206 )?.value
?.includes(measurand
) === false
209 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
210 StandardParametersKey.MeterValuesSampledData
215 const sampledValueTemplates
: SampledValueTemplate
[] =
216 chargingStation
.getConnectorStatus(connectorId
)?.MeterValues
;
219 Utils
.isNotEmptyArray(sampledValueTemplates
) === true && index
< sampledValueTemplates
.length
;
223 Constants
.SUPPORTED_MEASURANDS
.includes(
224 sampledValueTemplates
[index
]?.measurand
??
225 MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
229 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
233 sampledValueTemplates
[index
]?.phase
=== phase
&&
234 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
235 ChargingStationConfigurationUtils
.getConfigurationKey(
237 StandardParametersKey
.MeterValuesSampledData
238 )?.value
?.includes(measurand
) === true
240 return sampledValueTemplates
[index
];
243 !sampledValueTemplates
[index
].phase
&&
244 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
245 ChargingStationConfigurationUtils
.getConfigurationKey(
247 StandardParametersKey
.MeterValuesSampledData
248 )?.value
?.includes(measurand
) === true
250 return sampledValueTemplates
[index
];
252 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
253 (!sampledValueTemplates
[index
].measurand
||
254 sampledValueTemplates
[index
].measurand
=== measurand
)
256 return sampledValueTemplates
[index
];
259 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
260 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
261 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`);
262 throw new BaseError(errorMsg
);
265 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
269 protected static getLimitFromSampledValueTemplateCustomValue(
272 options
: { limitationEnabled
?: boolean; unitMultiplier
?: number } = {
273 limitationEnabled
: true,
277 options
.limitationEnabled
= options
?.limitationEnabled
?? true;
278 options
.unitMultiplier
= options
?.unitMultiplier
?? 1;
279 const parsedInt
= parseInt(value
);
280 const numberValue
= isNaN(parsedInt
) ? Infinity : parsedInt
;
281 return options
?.limitationEnabled
282 ? Math.min(numberValue
* options
.unitMultiplier
, limit
)
283 : numberValue
* options
.unitMultiplier
;
286 private static logPrefix
= (
287 ocppVersion
: OCPPVersion
,
292 Utils
.isNotEmptyString(moduleName
) && Utils
.isNotEmptyString(methodName
)
293 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
294 : ` OCPP ${ocppVersion} |`;
295 return Utils
.logPrefix(logMsg
);