1 import { readFileSync
} from
'node:fs';
2 import { dirname
, join
} from
'node:path';
3 import { fileURLToPath
} from
'node:url';
5 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv';
6 import { isDate
} from
'date-fns';
8 import { OCPP16Constants
} from
'./1.6/OCPP16Constants.js';
9 import { OCPP20Constants
} from
'./2.0/OCPP20Constants.js';
10 import { OCPPConstants
} from
'./OCPPConstants.js';
15 } from
'../../charging-station/index.js';
16 import { BaseError
, OCPPError
} from
'../../exception/index.js';
19 type AuthorizeRequest
,
20 type AuthorizeResponse
,
22 ChargingStationEvents
,
24 type ConnectorStatusEnum
,
28 IncomingRequestCommand
,
30 type MeasurandPerPhaseSampledValueTemplates
,
40 type OCPP16StatusNotificationRequest
,
41 type OCPP20StatusNotificationRequest
,
45 type SampledValueTemplate
,
46 StandardParametersKey
,
47 type StatusNotificationRequest
,
48 type StatusNotificationResponse
,
49 } from
'../../types/index.js';
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
69 } from
'../../utils/index.js';
71 export const getMessageTypeString
= (messageType
: MessageType
): string => {
72 switch (messageType
) {
73 case MessageType
.CALL_MESSAGE
:
75 case MessageType
.CALL_RESULT_MESSAGE
:
77 case MessageType
.CALL_ERROR_MESSAGE
:
84 export const buildStatusNotificationRequest
= (
85 chargingStation
: ChargingStation
,
87 status: ConnectorStatusEnum
,
89 ): StatusNotificationRequest
=> {
90 switch (chargingStation
.stationInfo
?.ocppVersion
) {
91 case OCPPVersion
.VERSION_16
:
95 errorCode
: ChargePointErrorCode
.NO_ERROR
,
96 } as OCPP16StatusNotificationRequest
;
97 case OCPPVersion
.VERSION_20
:
98 case OCPPVersion
.VERSION_201
:
100 timestamp
: new Date(),
101 connectorStatus
: status,
104 } as OCPP20StatusNotificationRequest
;
106 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
110 export const isIdTagAuthorized
= async (
111 chargingStation
: ChargingStation
,
114 ): Promise
<boolean> => {
116 !chargingStation
.getLocalAuthListEnabled() &&
117 !chargingStation
.stationInfo
?.remoteAuthorization
120 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`,
124 chargingStation
.getLocalAuthListEnabled() === true &&
125 isIdTagLocalAuthorized(chargingStation
, idTag
)
127 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
128 connectorStatus
.localAuthorizeIdTag
= idTag
;
129 connectorStatus
.idTagLocalAuthorized
= true;
131 } else if (chargingStation
.stationInfo
?.remoteAuthorization
) {
132 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
);
137 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
139 chargingStation
.hasIdTags() === true &&
141 chargingStation
.idTagsCache
142 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
143 ?.find((tag
) => tag
=== idTag
),
148 const isIdTagRemoteAuthorized
= async (
149 chargingStation
: ChargingStation
,
152 ): Promise
<boolean> => {
153 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
;
156 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
158 RequestCommand
.AUTHORIZE
,
163 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
167 export const sendAndSetConnectorStatus
= async (
168 chargingStation
: ChargingStation
,
170 status: ConnectorStatusEnum
,
172 options
?: { send
: boolean },
173 ): Promise
<void> => {
174 options
= { send
: true, ...options
};
176 checkConnectorStatusTransition(chargingStation
, connectorId
, status);
177 await chargingStation
.ocppRequestService
.requestHandler
<
178 StatusNotificationRequest
,
179 StatusNotificationResponse
182 RequestCommand
.STATUS_NOTIFICATION
,
183 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
),
186 chargingStation
.getConnectorStatus(connectorId
)!.status = status;
187 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
189 ...chargingStation
.getConnectorStatus(connectorId
),
193 const checkConnectorStatusTransition
= (
194 chargingStation
: ChargingStation
,
196 status: ConnectorStatusEnum
,
198 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status;
199 let transitionAllowed
= false;
200 switch (chargingStation
.stationInfo
?.ocppVersion
) {
201 case OCPPVersion
.VERSION_16
:
203 (connectorId
=== 0 &&
204 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
205 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
208 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
209 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
212 transitionAllowed
= true;
215 case OCPPVersion
.VERSION_20
:
216 case OCPPVersion
.VERSION_201
:
218 (connectorId
=== 0 &&
219 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
220 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
223 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
224 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
227 transitionAllowed
= true;
232 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
233 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
236 if (transitionAllowed
=== false) {
238 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
239 ?.ocppVersion} connector id ${connectorId} status transition from '${
240 chargingStation.getConnectorStatus(connectorId)!.status
241 }' to '${status}' is not allowed`,
244 return transitionAllowed
;
247 export const buildMeterValue
= (
248 chargingStation
: ChargingStation
,
250 transactionId
: number,
254 const connector
= chargingStation
.getConnectorStatus(connectorId
);
255 let meterValue
: MeterValue
;
256 let socSampledValueTemplate
: SampledValueTemplate
| undefined;
257 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined;
258 let powerSampledValueTemplate
: SampledValueTemplate
| undefined;
259 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
260 let currentSampledValueTemplate
: SampledValueTemplate
| undefined;
261 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
262 let energySampledValueTemplate
: SampledValueTemplate
| undefined;
263 switch (chargingStation
.stationInfo
?.ocppVersion
) {
264 case OCPPVersion
.VERSION_16
:
266 timestamp
: new Date(),
270 socSampledValueTemplate
= getSampledValueTemplate(
273 MeterValueMeasurand
.STATE_OF_CHARGE
,
275 if (socSampledValueTemplate
) {
276 const socMaximumValue
= 100;
277 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
278 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
279 ? getRandomFloatFluctuatedRounded(
280 parseInt(socSampledValueTemplate
.value
),
281 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
283 : getRandomInteger(socMaximumValue
, socMinimumValue
);
284 meterValue
.sampledValue
.push(
285 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
287 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
289 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
290 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
294 `${chargingStation.logPrefix()} MeterValues measurand ${
295 meterValue.sampledValue[sampledValuesIndex].measurand ??
296 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
297 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
298 meterValue.sampledValue[sampledValuesIndex].value
299 }/${socMaximumValue}`,
304 voltageSampledValueTemplate
= getSampledValueTemplate(
307 MeterValueMeasurand
.VOLTAGE
,
309 if (voltageSampledValueTemplate
) {
310 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
311 ? parseInt(voltageSampledValueTemplate
.value
)
312 : chargingStation
.stationInfo
.voltageOut
!;
313 const fluctuationPercent
=
314 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
315 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
316 voltageSampledValueTemplateValue
,
320 chargingStation
.getNumberOfPhases() !== 3 ||
321 (chargingStation
.getNumberOfPhases() === 3 &&
322 chargingStation
.stationInfo
?.mainVoltageMeterValues
)
324 meterValue
.sampledValue
.push(
325 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
330 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
333 const phaseLineToNeutralValue
= `L${phase}-N`;
334 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
337 MeterValueMeasurand
.VOLTAGE
,
338 phaseLineToNeutralValue
as MeterValuePhase
,
340 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
341 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
342 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
343 voltagePhaseLineToNeutralSampledValueTemplate
.value
,
345 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
346 : chargingStation
.stationInfo
.voltageOut
!;
347 const fluctuationPhaseToNeutralPercent
=
348 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
349 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
350 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
351 voltagePhaseLineToNeutralSampledValueTemplateValue
,
352 fluctuationPhaseToNeutralPercent
,
355 meterValue
.sampledValue
.push(
357 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
358 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
360 phaseLineToNeutralValue
as MeterValuePhase
,
363 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
) {
364 const phaseLineToLineValue
= `L${phase}-L${
365 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
366 ? (phase + 1) % chargingStation.getNumberOfPhases()
367 : chargingStation.getNumberOfPhases()
369 const voltagePhaseLineToLineValueRounded
= roundTo(
370 Math.sqrt(chargingStation
.getNumberOfPhases()) *
371 chargingStation
.stationInfo
.voltageOut
!,
374 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
377 MeterValueMeasurand
.VOLTAGE
,
378 phaseLineToLineValue
as MeterValuePhase
,
380 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
381 if (voltagePhaseLineToLineSampledValueTemplate
) {
382 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
383 voltagePhaseLineToLineSampledValueTemplate
.value
,
385 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
386 : voltagePhaseLineToLineValueRounded
;
387 const fluctuationPhaseLineToLinePercent
=
388 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
389 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
390 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
391 voltagePhaseLineToLineSampledValueTemplateValue
,
392 fluctuationPhaseLineToLinePercent
,
395 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
396 voltagePhaseLineToLineValueRounded
,
399 meterValue
.sampledValue
.push(
401 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
402 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
404 phaseLineToLineValue
as MeterValuePhase
,
410 // Power.Active.Import measurand
411 powerSampledValueTemplate
= getSampledValueTemplate(
414 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
416 if (chargingStation
.getNumberOfPhases() === 3) {
417 powerPerPhaseSampledValueTemplates
= {
418 L1
: getSampledValueTemplate(
421 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
422 MeterValuePhase
.L1_N
,
424 L2
: getSampledValueTemplate(
427 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
428 MeterValuePhase
.L2_N
,
430 L3
: getSampledValueTemplate(
433 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
434 MeterValuePhase
.L3_N
,
438 if (powerSampledValueTemplate
) {
439 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
!);
440 const errMsg
= `MeterValues measurand ${
441 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
442 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
443 chargingStation.templateFile
444 }, cannot calculate ${
445 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
447 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
448 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
449 const connectorMaximumAvailablePower
=
450 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
451 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
452 const connectorMaximumPowerPerPhase
= Math.round(
453 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
455 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0);
456 const connectorMinimumPowerPerPhase
= Math.round(
457 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
459 switch (chargingStation
.stationInfo
?.currentOutType
) {
461 if (chargingStation
.getNumberOfPhases() === 3) {
462 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
463 powerSampledValueTemplate
.value
,
465 ? getRandomFloatFluctuatedRounded(
466 getLimitFromSampledValueTemplateCustomValue(
467 powerSampledValueTemplate
.value
,
468 connectorMaximumPower
/ unitDivider
,
469 connectorMinimumPower
/ unitDivider
,
472 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
473 fallbackValue
: connectorMinimumPower
/ unitDivider
,
475 ) / chargingStation
.getNumberOfPhases(),
476 powerSampledValueTemplate
.fluctuationPercent
??
477 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
480 const phase1FluctuatedValue
= isNotEmptyString(
481 powerPerPhaseSampledValueTemplates
.L1
?.value
,
483 ? getRandomFloatFluctuatedRounded(
484 getLimitFromSampledValueTemplateCustomValue(
485 powerPerPhaseSampledValueTemplates
.L1
?.value
,
486 connectorMaximumPowerPerPhase
/ unitDivider
,
487 connectorMinimumPowerPerPhase
/ unitDivider
,
490 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
491 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
494 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
495 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
498 const phase2FluctuatedValue
= isNotEmptyString(
499 powerPerPhaseSampledValueTemplates
.L2
?.value
,
501 ? getRandomFloatFluctuatedRounded(
502 getLimitFromSampledValueTemplateCustomValue(
503 powerPerPhaseSampledValueTemplates
.L2
?.value
,
504 connectorMaximumPowerPerPhase
/ unitDivider
,
505 connectorMinimumPowerPerPhase
/ unitDivider
,
508 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
509 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
512 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
513 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
516 const phase3FluctuatedValue
= isNotEmptyString(
517 powerPerPhaseSampledValueTemplates
.L3
?.value
,
519 ? getRandomFloatFluctuatedRounded(
520 getLimitFromSampledValueTemplateCustomValue(
521 powerPerPhaseSampledValueTemplates
.L3
?.value
,
522 connectorMaximumPowerPerPhase
/ unitDivider
,
523 connectorMinimumPowerPerPhase
/ unitDivider
,
526 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
527 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
530 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
531 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
534 powerMeasurandValues
.L1
=
535 phase1FluctuatedValue
??
536 defaultFluctuatedPowerPerPhase
??
537 getRandomFloatRounded(
538 connectorMaximumPowerPerPhase
/ unitDivider
,
539 connectorMinimumPowerPerPhase
/ unitDivider
,
541 powerMeasurandValues
.L2
=
542 phase2FluctuatedValue
??
543 defaultFluctuatedPowerPerPhase
??
544 getRandomFloatRounded(
545 connectorMaximumPowerPerPhase
/ unitDivider
,
546 connectorMinimumPowerPerPhase
/ unitDivider
,
548 powerMeasurandValues
.L3
=
549 phase3FluctuatedValue
??
550 defaultFluctuatedPowerPerPhase
??
551 getRandomFloatRounded(
552 connectorMaximumPowerPerPhase
/ unitDivider
,
553 connectorMinimumPowerPerPhase
/ unitDivider
,
556 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
557 ? getRandomFloatFluctuatedRounded(
558 getLimitFromSampledValueTemplateCustomValue(
559 powerSampledValueTemplate
.value
,
560 connectorMaximumPower
/ unitDivider
,
561 connectorMinimumPower
/ unitDivider
,
564 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
565 fallbackValue
: connectorMinimumPower
/ unitDivider
,
568 powerSampledValueTemplate
.fluctuationPercent
??
569 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
571 : getRandomFloatRounded(
572 connectorMaximumPower
/ unitDivider
,
573 connectorMinimumPower
/ unitDivider
,
575 powerMeasurandValues
.L2
= 0;
576 powerMeasurandValues
.L3
= 0;
578 powerMeasurandValues
.allPhases
= roundTo(
579 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
584 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
585 ? getRandomFloatFluctuatedRounded(
586 getLimitFromSampledValueTemplateCustomValue(
587 powerSampledValueTemplate
.value
,
588 connectorMaximumPower
/ unitDivider
,
589 connectorMinimumPower
/ unitDivider
,
592 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
593 fallbackValue
: connectorMinimumPower
/ unitDivider
,
596 powerSampledValueTemplate
.fluctuationPercent
??
597 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
599 : getRandomFloatRounded(
600 connectorMaximumPower
/ unitDivider
,
601 connectorMinimumPower
/ unitDivider
,
605 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
606 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
608 meterValue
.sampledValue
.push(
609 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
),
611 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
612 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
613 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
615 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
616 connectorMaximumPowerRounded
||
617 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
618 connectorMinimumPowerRounded
||
622 `${chargingStation.logPrefix()} MeterValues measurand ${
623 meterValue.sampledValue[sampledValuesIndex].measurand ??
624 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
625 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
626 meterValue.sampledValue[sampledValuesIndex].value
627 }/${connectorMaximumPowerRounded}`,
632 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
635 const phaseValue
= `L${phase}-N`;
636 meterValue
.sampledValue
.push(
638 powerPerPhaseSampledValueTemplates
[
639 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
640 ] ?? powerSampledValueTemplate
,
641 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
643 phaseValue
as MeterValuePhase
,
646 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
647 const connectorMaximumPowerPerPhaseRounded
= roundTo(
648 connectorMaximumPowerPerPhase
/ unitDivider
,
651 const connectorMinimumPowerPerPhaseRounded
= roundTo(
652 connectorMinimumPowerPerPhase
/ unitDivider
,
656 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
657 connectorMaximumPowerPerPhaseRounded
||
658 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
659 connectorMinimumPowerPerPhaseRounded
||
663 `${chargingStation.logPrefix()} MeterValues measurand ${
664 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
665 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
667 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
668 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
669 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
670 }/${connectorMaximumPowerPerPhaseRounded}`,
675 // Current.Import measurand
676 currentSampledValueTemplate
= getSampledValueTemplate(
679 MeterValueMeasurand
.CURRENT_IMPORT
,
681 if (chargingStation
.getNumberOfPhases() === 3) {
682 currentPerPhaseSampledValueTemplates
= {
683 L1
: getSampledValueTemplate(
686 MeterValueMeasurand
.CURRENT_IMPORT
,
689 L2
: getSampledValueTemplate(
692 MeterValueMeasurand
.CURRENT_IMPORT
,
695 L3
: getSampledValueTemplate(
698 MeterValueMeasurand
.CURRENT_IMPORT
,
703 if (currentSampledValueTemplate
) {
704 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
!);
705 const errMsg
= `MeterValues measurand ${
706 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
707 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
708 chargingStation.templateFile
709 }, cannot calculate ${
710 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
712 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
713 const connectorMaximumAvailablePower
=
714 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
715 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
716 let connectorMaximumAmperage
: number;
717 switch (chargingStation
.stationInfo
?.currentOutType
) {
719 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
720 chargingStation
.getNumberOfPhases(),
721 connectorMaximumAvailablePower
,
722 chargingStation
.stationInfo
.voltageOut
!,
724 if (chargingStation
.getNumberOfPhases() === 3) {
725 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
726 currentSampledValueTemplate
.value
,
728 ? getRandomFloatFluctuatedRounded(
729 getLimitFromSampledValueTemplateCustomValue(
730 currentSampledValueTemplate
.value
,
731 connectorMaximumAmperage
,
732 connectorMinimumAmperage
,
735 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
736 fallbackValue
: connectorMinimumAmperage
,
739 currentSampledValueTemplate
.fluctuationPercent
??
740 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
743 const phase1FluctuatedValue
= isNotEmptyString(
744 currentPerPhaseSampledValueTemplates
.L1
?.value
,
746 ? getRandomFloatFluctuatedRounded(
747 getLimitFromSampledValueTemplateCustomValue(
748 currentPerPhaseSampledValueTemplates
.L1
?.value
,
749 connectorMaximumAmperage
,
750 connectorMinimumAmperage
,
753 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
754 fallbackValue
: connectorMinimumAmperage
,
757 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
758 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
761 const phase2FluctuatedValue
= isNotEmptyString(
762 currentPerPhaseSampledValueTemplates
.L2
?.value
,
764 ? getRandomFloatFluctuatedRounded(
765 getLimitFromSampledValueTemplateCustomValue(
766 currentPerPhaseSampledValueTemplates
.L2
?.value
,
767 connectorMaximumAmperage
,
768 connectorMinimumAmperage
,
771 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
772 fallbackValue
: connectorMinimumAmperage
,
775 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
776 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
779 const phase3FluctuatedValue
= isNotEmptyString(
780 currentPerPhaseSampledValueTemplates
.L3
?.value
,
782 ? getRandomFloatFluctuatedRounded(
783 getLimitFromSampledValueTemplateCustomValue(
784 currentPerPhaseSampledValueTemplates
.L3
?.value
,
785 connectorMaximumAmperage
,
786 connectorMinimumAmperage
,
789 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
790 fallbackValue
: connectorMinimumAmperage
,
793 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
794 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
797 currentMeasurandValues
.L1
=
798 phase1FluctuatedValue
??
799 defaultFluctuatedAmperagePerPhase
??
800 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
801 currentMeasurandValues
.L2
=
802 phase2FluctuatedValue
??
803 defaultFluctuatedAmperagePerPhase
??
804 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
805 currentMeasurandValues
.L3
=
806 phase3FluctuatedValue
??
807 defaultFluctuatedAmperagePerPhase
??
808 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
810 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
811 ? getRandomFloatFluctuatedRounded(
812 getLimitFromSampledValueTemplateCustomValue(
813 currentSampledValueTemplate
.value
,
814 connectorMaximumAmperage
,
815 connectorMinimumAmperage
,
818 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
819 fallbackValue
: connectorMinimumAmperage
,
822 currentSampledValueTemplate
.fluctuationPercent
??
823 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
825 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
826 currentMeasurandValues
.L2
= 0;
827 currentMeasurandValues
.L3
= 0;
829 currentMeasurandValues
.allPhases
= roundTo(
830 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
831 chargingStation
.getNumberOfPhases(),
836 connectorMaximumAmperage
= DCElectricUtils
.amperage(
837 connectorMaximumAvailablePower
,
838 chargingStation
.stationInfo
.voltageOut
!,
840 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
841 ? getRandomFloatFluctuatedRounded(
842 getLimitFromSampledValueTemplateCustomValue(
843 currentSampledValueTemplate
.value
,
844 connectorMaximumAmperage
,
845 connectorMinimumAmperage
,
848 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
849 fallbackValue
: connectorMinimumAmperage
,
852 currentSampledValueTemplate
.fluctuationPercent
??
853 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
855 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
858 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
859 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
861 meterValue
.sampledValue
.push(
862 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
),
864 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
866 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
867 connectorMaximumAmperage
||
868 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
869 connectorMinimumAmperage
||
873 `${chargingStation.logPrefix()} MeterValues measurand ${
874 meterValue.sampledValue[sampledValuesIndex].measurand ??
875 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
876 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
877 meterValue.sampledValue[sampledValuesIndex].value
878 }/${connectorMaximumAmperage}`,
883 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
886 const phaseValue
= `L${phase}`;
887 meterValue
.sampledValue
.push(
889 currentPerPhaseSampledValueTemplates
[
890 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
891 ] ?? currentSampledValueTemplate
,
892 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
894 phaseValue
as MeterValuePhase
,
897 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
899 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
900 connectorMaximumAmperage
||
901 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
902 connectorMinimumAmperage
||
906 `${chargingStation.logPrefix()} MeterValues measurand ${
907 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
908 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
910 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
911 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
912 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
913 }/${connectorMaximumAmperage}`,
918 // Energy.Active.Import.Register measurand (default)
919 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
);
920 if (energySampledValueTemplate
) {
921 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!);
923 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
924 const connectorMaximumAvailablePower
=
925 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
926 const connectorMaximumEnergyRounded
= roundTo(
927 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
930 const connectorMinimumEnergyRounded
= roundTo(
931 energySampledValueTemplate
.minimumValue
?? 0,
934 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
935 ? getRandomFloatFluctuatedRounded(
936 getLimitFromSampledValueTemplateCustomValue(
937 energySampledValueTemplate
.value
,
938 connectorMaximumEnergyRounded
,
939 connectorMinimumEnergyRounded
,
941 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
942 fallbackValue
: connectorMinimumEnergyRounded
,
943 unitMultiplier
: unitDivider
,
946 energySampledValueTemplate
.fluctuationPercent
??
947 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
949 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
950 // Persist previous value on connector
953 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
954 connector
.energyActiveImportRegisterValue
! >= 0 &&
955 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
956 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
958 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
959 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
961 connector
.energyActiveImportRegisterValue
= 0;
962 connector
.transactionEnergyActiveImportRegisterValue
= 0;
965 meterValue
.sampledValue
.push(
967 energySampledValueTemplate
,
969 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
975 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
977 energyValueRounded
> connectorMaximumEnergyRounded
||
978 energyValueRounded
< connectorMinimumEnergyRounded
||
982 `${chargingStation.logPrefix()} MeterValues measurand ${
983 meterValue.sampledValue[sampledValuesIndex].measurand ??
984 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
985 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
990 case OCPPVersion
.VERSION_20
:
991 case OCPPVersion
.VERSION_201
:
994 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
995 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
1000 export const buildTransactionEndMeterValue
= (
1001 chargingStation
: ChargingStation
,
1002 connectorId
: number,
1005 let meterValue
: MeterValue
;
1006 let sampledValueTemplate
: SampledValueTemplate
| undefined;
1007 let unitDivider
: number;
1008 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1009 case OCPPVersion
.VERSION_16
:
1011 timestamp
: new Date(),
1014 // Energy.Active.Import.Register measurand (default)
1015 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
);
1016 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
1017 meterValue
.sampledValue
.push(
1019 sampledValueTemplate
!,
1020 roundTo((meterStop
?? 0) / unitDivider
, 4),
1021 MeterValueContext
.TRANSACTION_END
,
1025 case OCPPVersion
.VERSION_20
:
1026 case OCPPVersion
.VERSION_201
:
1028 throw new BaseError(
1029 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1030 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
1035 const checkMeasurandPowerDivider
= (
1036 chargingStation
: ChargingStation
,
1037 measurandType
: MeterValueMeasurand
,
1039 if (isUndefined(chargingStation
.powerDivider
)) {
1040 const errMsg
= `MeterValues measurand ${
1041 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1042 }: powerDivider is undefined`;
1043 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1044 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
1045 } else if (chargingStation
?.powerDivider
<= 0) {
1046 const errMsg
= `MeterValues measurand ${
1047 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1048 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1049 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1050 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
1054 const getLimitFromSampledValueTemplateCustomValue
= (
1055 value
: string | undefined,
1058 options
?: { limitationEnabled
?: boolean; fallbackValue
?: number; unitMultiplier
?: number },
1062 limitationEnabled
: false,
1068 const parsedValue
= parseInt(value
?? '');
1069 if (options
?.limitationEnabled
) {
1071 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1075 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!;
1078 const getSampledValueTemplate
= (
1079 chargingStation
: ChargingStation
,
1080 connectorId
: number,
1081 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1082 phase
?: MeterValuePhase
,
1083 ): SampledValueTemplate
| undefined => {
1084 const onPhaseStr
= phase
? `on phase ${phase} ` : '';
1085 if (OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
) === false) {
1087 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1092 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1093 getConfigurationKey(
1095 StandardParametersKey
.MeterValuesSampledData
,
1096 )?.value
?.includes(measurand
) === false
1099 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1100 StandardParametersKey.MeterValuesSampledData
1105 const sampledValueTemplates
: SampledValueTemplate
[] =
1106 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
;
1109 isNotEmptyArray(sampledValueTemplates
) === true && index
< sampledValueTemplates
.length
;
1113 OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1114 sampledValueTemplates
[index
]?.measurand
??
1115 MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1119 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1123 sampledValueTemplates
[index
]?.phase
=== phase
&&
1124 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1125 getConfigurationKey(
1127 StandardParametersKey
.MeterValuesSampledData
,
1128 )?.value
?.includes(measurand
) === true
1130 return sampledValueTemplates
[index
];
1133 !sampledValueTemplates
[index
]?.phase
&&
1134 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1135 getConfigurationKey(
1137 StandardParametersKey
.MeterValuesSampledData
,
1138 )?.value
?.includes(measurand
) === true
1140 return sampledValueTemplates
[index
];
1142 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1143 (!sampledValueTemplates
[index
]?.measurand
||
1144 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1146 return sampledValueTemplates
[index
];
1149 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1150 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`;
1151 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`);
1152 throw new BaseError(errorMsg
);
1155 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1159 const buildSampledValue
= (
1160 sampledValueTemplate
: SampledValueTemplate
,
1162 context
?: MeterValueContext
,
1163 phase
?: MeterValuePhase
,
1164 ): SampledValue
=> {
1165 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1166 const sampledValueLocation
=
1167 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1168 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1170 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1171 unit
: sampledValueTemplate
.unit
,
1173 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1174 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1175 measurand
: sampledValueTemplate
.measurand
,
1177 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1178 ...(!isNullOrUndefined(value
) && { value
: value
.toString() }),
1179 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1183 const getMeasurandDefaultLocation
= (
1184 measurandType
: MeterValueMeasurand
,
1185 ): MeterValueLocation
| undefined => {
1186 switch (measurandType
) {
1187 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1188 return MeterValueLocation
.EV
;
1192 // const getMeasurandDefaultUnit = (
1193 // measurandType: MeterValueMeasurand,
1194 // ): MeterValueUnit | undefined => {
1195 // switch (measurandType) {
1196 // case MeterValueMeasurand.CURRENT_EXPORT:
1197 // case MeterValueMeasurand.CURRENT_IMPORT:
1198 // case MeterValueMeasurand.CURRENT_OFFERED:
1199 // return MeterValueUnit.AMP;
1200 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1201 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1202 // return MeterValueUnit.WATT_HOUR;
1203 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1204 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1205 // case MeterValueMeasurand.POWER_OFFERED:
1206 // return MeterValueUnit.WATT;
1207 // case MeterValueMeasurand.STATE_OF_CHARGE:
1208 // return MeterValueUnit.PERCENT;
1209 // case MeterValueMeasurand.VOLTAGE:
1210 // return MeterValueUnit.VOLT;
1214 export class OCPPServiceUtils
{
1215 public static getMessageTypeString
= getMessageTypeString
;
1216 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
;
1217 public static isIdTagAuthorized
= isIdTagAuthorized
;
1218 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
;
1219 protected static getSampledValueTemplate
= getSampledValueTemplate
;
1220 protected static buildSampledValue
= buildSampledValue
;
1222 protected constructor() {
1223 // This is intentional
1226 public static ajvErrorsToErrorType(errors
: ErrorObject
[] | null | undefined): ErrorType
{
1227 if (isNotEmptyArray(errors
) === true) {
1228 for (const error
of errors
as DefinedError
[]) {
1229 switch (error
.keyword
) {
1231 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
;
1232 case 'dependencies':
1234 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
;
1237 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
;
1241 return ErrorType
.FORMAT_VIOLATION
;
1244 public static isRequestCommandSupported(
1245 chargingStation
: ChargingStation
,
1246 command
: RequestCommand
,
1248 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
);
1250 isRequestCommand
=== true &&
1251 !chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
1255 isRequestCommand
=== true &&
1256 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
]
1258 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
];
1260 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
1264 public static isIncomingRequestCommandSupported(
1265 chargingStation
: ChargingStation
,
1266 command
: IncomingRequestCommand
,
1268 const isIncomingRequestCommand
=
1269 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
);
1271 isIncomingRequestCommand
=== true &&
1272 !chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
1276 isIncomingRequestCommand
=== true &&
1277 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
?.[command
]
1279 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
];
1281 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
1285 public static isMessageTriggerSupported(
1286 chargingStation
: ChargingStation
,
1287 messageTrigger
: MessageTrigger
,
1289 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
);
1290 if (isMessageTrigger
=== true && !chargingStation
.stationInfo
?.messageTriggerSupport
) {
1293 isMessageTrigger
=== true &&
1294 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
]
1296 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
];
1299 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`,
1304 public static isConnectorIdValid(
1305 chargingStation
: ChargingStation
,
1306 ocppCommand
: IncomingRequestCommand
,
1307 connectorId
: number,
1309 if (connectorId
< 0) {
1311 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`,
1318 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1319 for (const key
in obj
) {
1320 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1321 if (isDate(obj
![key
])) {
1322 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1323 (obj
![key
] as string) = (obj
![key
] as Date).toISOString();
1324 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1325 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
1326 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1327 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
);
1332 public static startHeartbeatInterval(chargingStation
: ChargingStation
, interval
: number): void {
1333 if (chargingStation
.heartbeatSetInterval
=== undefined) {
1334 chargingStation
.startHeartbeat();
1335 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1336 chargingStation
.restartHeartbeat();
1340 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1341 relativePath
: string,
1342 ocppVersion
: OCPPVersion
,
1343 moduleName
?: string,
1344 methodName
?: string,
1345 ): JSONSchemaType
<T
> {
1346 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
);
1348 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>;
1350 handleFileException(
1352 FileType
.JsonSchema
,
1353 error
as NodeJS
.ErrnoException
,
1354 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1355 { throwError
: false },
1357 return {} as JSONSchemaType
<T
>;
1361 private static logPrefix
= (
1362 ocppVersion
: OCPPVersion
,
1363 moduleName
?: string,
1364 methodName
?: string,
1367 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1368 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1369 : ` OCPP ${ocppVersion} |`;
1370 return logPrefix(logMsg
);