1 import fs from
'node:fs';
3 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv';
5 import { BaseError
} from
'../../exception';
8 type ConnectorStatusEnum
,
11 IncomingRequestCommand
,
18 type OCPP16StatusNotificationRequest
,
19 type OCPP20StatusNotificationRequest
,
22 type SampledValueTemplate
,
23 StandardParametersKey
,
24 type StatusNotificationRequest
,
26 import { Constants
} from
'../../utils/Constants';
27 import { FileUtils
} from
'../../utils/FileUtils';
28 import { logger
} from
'../../utils/Logger';
29 import { Utils
} from
'../../utils/Utils';
30 import type { ChargingStation
} from
'../ChargingStation';
31 import { ChargingStationConfigurationUtils
} from
'../ChargingStationConfigurationUtils';
33 export class OCPPServiceUtils
{
34 protected constructor() {
35 // This is intentional
38 public static ajvErrorsToErrorType(errors
: ErrorObject
[]): ErrorType
{
39 for (const error
of errors
as DefinedError
[]) {
40 switch (error
.keyword
) {
42 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
;
45 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
;
48 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
;
51 return ErrorType
.FORMAT_VIOLATION
;
54 public static getMessageTypeString(messageType
: MessageType
): string {
55 switch (messageType
) {
56 case MessageType
.CALL_MESSAGE
:
58 case MessageType
.CALL_RESULT_MESSAGE
:
60 case MessageType
.CALL_ERROR_MESSAGE
:
67 public static isRequestCommandSupported(
68 chargingStation
: ChargingStation
,
69 command
: RequestCommand
71 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
);
73 isRequestCommand
=== true &&
74 !chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
78 isRequestCommand
=== true &&
79 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
81 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
] ?? false;
83 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
87 public static isIncomingRequestCommandSupported(
88 chargingStation
: ChargingStation
,
89 command
: IncomingRequestCommand
91 const isIncomingRequestCommand
=
92 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
);
94 isIncomingRequestCommand
=== true &&
95 !chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
99 isIncomingRequestCommand
=== true &&
100 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
102 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] ?? false;
104 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
108 public static isMessageTriggerSupported(
109 chargingStation
: ChargingStation
,
110 messageTrigger
: MessageTrigger
112 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
);
113 if (isMessageTrigger
=== true && !chargingStation
.stationInfo
?.messageTriggerSupport
) {
115 } else if (isMessageTrigger
=== true && chargingStation
.stationInfo
?.messageTriggerSupport
) {
116 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
] ?? false;
119 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
124 public static isConnectorIdValid(
125 chargingStation
: ChargingStation
,
126 ocppCommand
: IncomingRequestCommand
,
129 if (connectorId
< 0) {
131 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
138 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
139 for (const key
in obj
) {
140 if (obj
[key
] instanceof Date) {
141 (obj
as JsonObject
)[key
] = (obj
[key
] as Date).toISOString();
142 } else if (obj
[key
] !== null && typeof obj
[key
] === 'object') {
143 this.convertDateToISOString
<T
>(obj
[key
] as T
);
148 public static buildStatusNotificationRequest(
149 chargingStation
: ChargingStation
,
151 status: ConnectorStatusEnum
152 ): StatusNotificationRequest
{
153 switch (chargingStation
.stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
) {
154 case OCPPVersion
.VERSION_16
:
158 errorCode
: ChargePointErrorCode
.NO_ERROR
,
159 } as OCPP16StatusNotificationRequest
;
160 case OCPPVersion
.VERSION_20
:
161 case OCPPVersion
.VERSION_201
:
163 timestamp
: new Date(),
164 connectorStatus
: status,
167 } as OCPP20StatusNotificationRequest
;
169 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
173 protected static parseJsonSchemaFile
<T
extends JsonType
>(
175 ocppVersion
: OCPPVersion
,
178 ): JSONSchemaType
<T
> {
180 return JSON
.parse(fs
.readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>;
182 FileUtils
.handleFileException(
185 error
as NodeJS
.ErrnoException
,
186 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
187 { throwError
: false }
192 protected static getSampledValueTemplate(
193 chargingStation
: ChargingStation
,
195 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
196 phase
?: MeterValuePhase
197 ): SampledValueTemplate
| undefined {
198 const onPhaseStr
= phase
? `on phase ${phase} ` : '';
199 if (Constants
.SUPPORTED_MEASURANDS
.includes(measurand
) === false) {
201 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
206 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
207 ChargingStationConfigurationUtils
.getConfigurationKey(
209 StandardParametersKey
.MeterValuesSampledData
210 )?.value
?.includes(measurand
) === false
213 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
214 StandardParametersKey.MeterValuesSampledData
219 const sampledValueTemplates
: SampledValueTemplate
[] =
220 chargingStation
.getConnectorStatus(connectorId
)?.MeterValues
;
223 Utils
.isNotEmptyArray(sampledValueTemplates
) === true && index
< sampledValueTemplates
.length
;
227 Constants
.SUPPORTED_MEASURANDS
.includes(
228 sampledValueTemplates
[index
]?.measurand
??
229 MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
233 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
237 sampledValueTemplates
[index
]?.phase
=== phase
&&
238 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
239 ChargingStationConfigurationUtils
.getConfigurationKey(
241 StandardParametersKey
.MeterValuesSampledData
242 )?.value
?.includes(measurand
) === true
244 return sampledValueTemplates
[index
];
247 !sampledValueTemplates
[index
].phase
&&
248 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
249 ChargingStationConfigurationUtils
.getConfigurationKey(
251 StandardParametersKey
.MeterValuesSampledData
252 )?.value
?.includes(measurand
) === true
254 return sampledValueTemplates
[index
];
256 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
257 (!sampledValueTemplates
[index
].measurand
||
258 sampledValueTemplates
[index
].measurand
=== measurand
)
260 return sampledValueTemplates
[index
];
263 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
264 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
265 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`);
266 throw new BaseError(errorMsg
);
269 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
273 protected static getLimitFromSampledValueTemplateCustomValue(
276 options
: { limitationEnabled
?: boolean; unitMultiplier
?: number } = {
277 limitationEnabled
: true,
281 options
.limitationEnabled
= options
?.limitationEnabled
?? true;
282 options
.unitMultiplier
= options
?.unitMultiplier
?? 1;
283 const parsedInt
= parseInt(value
);
284 const numberValue
= isNaN(parsedInt
) ? Infinity : parsedInt
;
285 return options
?.limitationEnabled
286 ? Math.min(numberValue
* options
.unitMultiplier
, limit
)
287 : numberValue
* options
.unitMultiplier
;
290 private static logPrefix
= (
291 ocppVersion
: OCPPVersion
,
296 Utils
.isNotEmptyString(moduleName
) && Utils
.isNotEmptyString(methodName
)
297 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
298 : ` OCPP ${ocppVersion} |`;
299 return Utils
.logPrefix(logMsg
);