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';
9 import { OCPP20Constants
} from
'./2.0/OCPP20Constants';
10 import { OCPPConstants
} from
'./OCPPConstants';
11 import { type ChargingStation
, getConfigurationKey
, getIdTagsFile
} from
'../../charging-station';
12 import { BaseError
, OCPPError
} from
'../../exception';
15 type AuthorizeRequest
,
16 type AuthorizeResponse
,
18 ChargingStationEvents
,
20 type ConnectorStatusEnum
,
24 IncomingRequestCommand
,
26 type MeasurandPerPhaseSampledValueTemplates
,
36 type OCPP16StatusNotificationRequest
,
37 type OCPP20StatusNotificationRequest
,
41 type SampledValueTemplate
,
42 StandardParametersKey
,
43 type StatusNotificationRequest
,
44 type StatusNotificationResponse
,
52 getRandomFloatFluctuatedRounded
,
53 getRandomFloatRounded
,
67 export const getMessageTypeString
= (messageType
: MessageType
): string => {
68 switch (messageType
) {
69 case MessageType
.CALL_MESSAGE
:
71 case MessageType
.CALL_RESULT_MESSAGE
:
73 case MessageType
.CALL_ERROR_MESSAGE
:
80 export const buildStatusNotificationRequest
= (
81 chargingStation
: ChargingStation
,
83 status: ConnectorStatusEnum
,
85 ): StatusNotificationRequest
=> {
86 switch (chargingStation
.stationInfo
?.ocppVersion
) {
87 case OCPPVersion
.VERSION_16
:
91 errorCode
: ChargePointErrorCode
.NO_ERROR
,
92 } as OCPP16StatusNotificationRequest
;
93 case OCPPVersion
.VERSION_20
:
94 case OCPPVersion
.VERSION_201
:
96 timestamp
: new Date(),
97 connectorStatus
: status,
100 } as OCPP20StatusNotificationRequest
;
102 throw new BaseError('Cannot build status notification payload: OCPP version not supported');
106 export const isIdTagAuthorized
= async (
107 chargingStation
: ChargingStation
,
110 ): Promise
<boolean> => {
112 !chargingStation
.getLocalAuthListEnabled() &&
113 !chargingStation
.stationInfo
?.remoteAuthorization
116 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`,
120 chargingStation
.getLocalAuthListEnabled() === true &&
121 isIdTagLocalAuthorized(chargingStation
, idTag
)
123 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
124 connectorStatus
.localAuthorizeIdTag
= idTag
;
125 connectorStatus
.idTagLocalAuthorized
= true;
127 } else if (chargingStation
.stationInfo
?.remoteAuthorization
) {
128 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
);
133 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
135 chargingStation
.hasIdTags() === true &&
137 chargingStation
.idTagsCache
138 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
139 ?.find((tag
) => tag
=== idTag
),
144 const isIdTagRemoteAuthorized
= async (
145 chargingStation
: ChargingStation
,
148 ): Promise
<boolean> => {
149 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
;
152 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
154 RequestCommand
.AUTHORIZE
,
159 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
163 export const sendAndSetConnectorStatus
= async (
164 chargingStation
: ChargingStation
,
166 status: ConnectorStatusEnum
,
168 options
?: { send
: boolean },
169 ): Promise
<void> => {
170 options
= { send
: true, ...options
};
172 checkConnectorStatusTransition(chargingStation
, connectorId
, status);
173 await chargingStation
.ocppRequestService
.requestHandler
<
174 StatusNotificationRequest
,
175 StatusNotificationResponse
178 RequestCommand
.STATUS_NOTIFICATION
,
179 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
),
182 chargingStation
.getConnectorStatus(connectorId
)!.status = status;
183 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
185 ...chargingStation
.getConnectorStatus(connectorId
),
189 const checkConnectorStatusTransition
= (
190 chargingStation
: ChargingStation
,
192 status: ConnectorStatusEnum
,
194 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status;
195 let transitionAllowed
= false;
196 switch (chargingStation
.stationInfo
?.ocppVersion
) {
197 case OCPPVersion
.VERSION_16
:
199 (connectorId
=== 0 &&
200 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
201 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
204 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
205 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
208 transitionAllowed
= true;
211 case OCPPVersion
.VERSION_20
:
212 case OCPPVersion
.VERSION_201
:
214 (connectorId
=== 0 &&
215 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
216 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
219 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
220 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status,
223 transitionAllowed
= true;
228 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
229 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
232 if (transitionAllowed
=== false) {
234 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
235 ?.ocppVersion} connector id ${connectorId} status transition from '${
236 chargingStation.getConnectorStatus(connectorId)!.status
237 }' to '${status}' is not allowed`,
240 return transitionAllowed
;
243 export const buildMeterValue
= (
244 chargingStation
: ChargingStation
,
246 transactionId
: number,
250 const connector
= chargingStation
.getConnectorStatus(connectorId
);
251 let meterValue
: MeterValue
;
252 let socSampledValueTemplate
: SampledValueTemplate
| undefined;
253 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined;
254 let powerSampledValueTemplate
: SampledValueTemplate
| undefined;
255 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
256 let currentSampledValueTemplate
: SampledValueTemplate
| undefined;
257 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
258 let energySampledValueTemplate
: SampledValueTemplate
| undefined;
259 switch (chargingStation
.stationInfo
?.ocppVersion
) {
260 case OCPPVersion
.VERSION_16
:
262 timestamp
: new Date(),
266 socSampledValueTemplate
= getSampledValueTemplate(
269 MeterValueMeasurand
.STATE_OF_CHARGE
,
271 if (socSampledValueTemplate
) {
272 const socMaximumValue
= 100;
273 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
274 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
275 ? getRandomFloatFluctuatedRounded(
276 parseInt(socSampledValueTemplate
.value
),
277 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
279 : getRandomInteger(socMaximumValue
, socMinimumValue
);
280 meterValue
.sampledValue
.push(
281 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
283 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
285 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
286 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
290 `${chargingStation.logPrefix()} MeterValues measurand ${
291 meterValue.sampledValue[sampledValuesIndex].measurand ??
292 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
293 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
294 meterValue.sampledValue[sampledValuesIndex].value
295 }/${socMaximumValue}`,
300 voltageSampledValueTemplate
= getSampledValueTemplate(
303 MeterValueMeasurand
.VOLTAGE
,
305 if (voltageSampledValueTemplate
) {
306 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
307 ? parseInt(voltageSampledValueTemplate
.value
)
308 : chargingStation
.stationInfo
.voltageOut
!;
309 const fluctuationPercent
=
310 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
311 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
312 voltageSampledValueTemplateValue
,
316 chargingStation
.getNumberOfPhases() !== 3 ||
317 (chargingStation
.getNumberOfPhases() === 3 &&
318 chargingStation
.stationInfo
?.mainVoltageMeterValues
)
320 meterValue
.sampledValue
.push(
321 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
326 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
329 const phaseLineToNeutralValue
= `L${phase}-N`;
330 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
333 MeterValueMeasurand
.VOLTAGE
,
334 phaseLineToNeutralValue
as MeterValuePhase
,
336 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
337 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
338 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
339 voltagePhaseLineToNeutralSampledValueTemplate
.value
,
341 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
342 : chargingStation
.stationInfo
.voltageOut
!;
343 const fluctuationPhaseToNeutralPercent
=
344 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
345 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
346 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
347 voltagePhaseLineToNeutralSampledValueTemplateValue
,
348 fluctuationPhaseToNeutralPercent
,
351 meterValue
.sampledValue
.push(
353 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
354 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
356 phaseLineToNeutralValue
as MeterValuePhase
,
359 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
) {
360 const phaseLineToLineValue
= `L${phase}-L${
361 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
362 ? (phase + 1) % chargingStation.getNumberOfPhases()
363 : chargingStation.getNumberOfPhases()
365 const voltagePhaseLineToLineValueRounded
= roundTo(
366 Math.sqrt(chargingStation
.getNumberOfPhases()) *
367 chargingStation
.stationInfo
.voltageOut
!,
370 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
373 MeterValueMeasurand
.VOLTAGE
,
374 phaseLineToLineValue
as MeterValuePhase
,
376 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
377 if (voltagePhaseLineToLineSampledValueTemplate
) {
378 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
379 voltagePhaseLineToLineSampledValueTemplate
.value
,
381 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
382 : voltagePhaseLineToLineValueRounded
;
383 const fluctuationPhaseLineToLinePercent
=
384 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
385 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
386 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
387 voltagePhaseLineToLineSampledValueTemplateValue
,
388 fluctuationPhaseLineToLinePercent
,
391 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
392 voltagePhaseLineToLineValueRounded
,
395 meterValue
.sampledValue
.push(
397 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
398 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
400 phaseLineToLineValue
as MeterValuePhase
,
406 // Power.Active.Import measurand
407 powerSampledValueTemplate
= getSampledValueTemplate(
410 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
412 if (chargingStation
.getNumberOfPhases() === 3) {
413 powerPerPhaseSampledValueTemplates
= {
414 L1
: getSampledValueTemplate(
417 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
418 MeterValuePhase
.L1_N
,
420 L2
: getSampledValueTemplate(
423 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
424 MeterValuePhase
.L2_N
,
426 L3
: getSampledValueTemplate(
429 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
430 MeterValuePhase
.L3_N
,
434 if (powerSampledValueTemplate
) {
435 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
!);
436 const errMsg
= `MeterValues measurand ${
437 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
438 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
439 chargingStation.templateFile
440 }, cannot calculate ${
441 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
443 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
444 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
445 const connectorMaximumAvailablePower
=
446 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
447 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
448 const connectorMaximumPowerPerPhase
= Math.round(
449 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
451 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0);
452 const connectorMinimumPowerPerPhase
= Math.round(
453 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
455 switch (chargingStation
.stationInfo
?.currentOutType
) {
457 if (chargingStation
.getNumberOfPhases() === 3) {
458 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
459 powerSampledValueTemplate
.value
,
461 ? getRandomFloatFluctuatedRounded(
462 getLimitFromSampledValueTemplateCustomValue(
463 powerSampledValueTemplate
.value
,
464 connectorMaximumPower
/ unitDivider
,
465 connectorMinimumPower
/ unitDivider
,
468 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
469 fallbackValue
: connectorMinimumPower
/ unitDivider
,
471 ) / chargingStation
.getNumberOfPhases(),
472 powerSampledValueTemplate
.fluctuationPercent
??
473 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
476 const phase1FluctuatedValue
= isNotEmptyString(
477 powerPerPhaseSampledValueTemplates
.L1
?.value
,
479 ? getRandomFloatFluctuatedRounded(
480 getLimitFromSampledValueTemplateCustomValue(
481 powerPerPhaseSampledValueTemplates
.L1
?.value
,
482 connectorMaximumPowerPerPhase
/ unitDivider
,
483 connectorMinimumPowerPerPhase
/ unitDivider
,
486 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
487 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
490 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
491 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
494 const phase2FluctuatedValue
= isNotEmptyString(
495 powerPerPhaseSampledValueTemplates
.L2
?.value
,
497 ? getRandomFloatFluctuatedRounded(
498 getLimitFromSampledValueTemplateCustomValue(
499 powerPerPhaseSampledValueTemplates
.L2
?.value
,
500 connectorMaximumPowerPerPhase
/ unitDivider
,
501 connectorMinimumPowerPerPhase
/ unitDivider
,
504 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
505 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
508 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
509 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
512 const phase3FluctuatedValue
= isNotEmptyString(
513 powerPerPhaseSampledValueTemplates
.L3
?.value
,
515 ? getRandomFloatFluctuatedRounded(
516 getLimitFromSampledValueTemplateCustomValue(
517 powerPerPhaseSampledValueTemplates
.L3
?.value
,
518 connectorMaximumPowerPerPhase
/ unitDivider
,
519 connectorMinimumPowerPerPhase
/ unitDivider
,
522 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
523 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
526 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
527 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
530 powerMeasurandValues
.L1
=
531 phase1FluctuatedValue
??
532 defaultFluctuatedPowerPerPhase
??
533 getRandomFloatRounded(
534 connectorMaximumPowerPerPhase
/ unitDivider
,
535 connectorMinimumPowerPerPhase
/ unitDivider
,
537 powerMeasurandValues
.L2
=
538 phase2FluctuatedValue
??
539 defaultFluctuatedPowerPerPhase
??
540 getRandomFloatRounded(
541 connectorMaximumPowerPerPhase
/ unitDivider
,
542 connectorMinimumPowerPerPhase
/ unitDivider
,
544 powerMeasurandValues
.L3
=
545 phase3FluctuatedValue
??
546 defaultFluctuatedPowerPerPhase
??
547 getRandomFloatRounded(
548 connectorMaximumPowerPerPhase
/ unitDivider
,
549 connectorMinimumPowerPerPhase
/ unitDivider
,
552 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
553 ? getRandomFloatFluctuatedRounded(
554 getLimitFromSampledValueTemplateCustomValue(
555 powerSampledValueTemplate
.value
,
556 connectorMaximumPower
/ unitDivider
,
557 connectorMinimumPower
/ unitDivider
,
560 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
561 fallbackValue
: connectorMinimumPower
/ unitDivider
,
564 powerSampledValueTemplate
.fluctuationPercent
??
565 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
567 : getRandomFloatRounded(
568 connectorMaximumPower
/ unitDivider
,
569 connectorMinimumPower
/ unitDivider
,
571 powerMeasurandValues
.L2
= 0;
572 powerMeasurandValues
.L3
= 0;
574 powerMeasurandValues
.allPhases
= roundTo(
575 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
580 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
581 ? getRandomFloatFluctuatedRounded(
582 getLimitFromSampledValueTemplateCustomValue(
583 powerSampledValueTemplate
.value
,
584 connectorMaximumPower
/ unitDivider
,
585 connectorMinimumPower
/ unitDivider
,
588 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
589 fallbackValue
: connectorMinimumPower
/ unitDivider
,
592 powerSampledValueTemplate
.fluctuationPercent
??
593 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
595 : getRandomFloatRounded(
596 connectorMaximumPower
/ unitDivider
,
597 connectorMinimumPower
/ unitDivider
,
601 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
602 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
604 meterValue
.sampledValue
.push(
605 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
),
607 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
608 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
609 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
611 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
612 connectorMaximumPowerRounded
||
613 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
614 connectorMinimumPowerRounded
||
618 `${chargingStation.logPrefix()} MeterValues measurand ${
619 meterValue.sampledValue[sampledValuesIndex].measurand ??
620 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
621 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
622 meterValue.sampledValue[sampledValuesIndex].value
623 }/${connectorMaximumPowerRounded}`,
628 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
631 const phaseValue
= `L${phase}-N`;
632 meterValue
.sampledValue
.push(
634 powerPerPhaseSampledValueTemplates
[
635 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
636 ] ?? powerSampledValueTemplate
,
637 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
639 phaseValue
as MeterValuePhase
,
642 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
643 const connectorMaximumPowerPerPhaseRounded
= roundTo(
644 connectorMaximumPowerPerPhase
/ unitDivider
,
647 const connectorMinimumPowerPerPhaseRounded
= roundTo(
648 connectorMinimumPowerPerPhase
/ unitDivider
,
652 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
653 connectorMaximumPowerPerPhaseRounded
||
654 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
655 connectorMinimumPowerPerPhaseRounded
||
659 `${chargingStation.logPrefix()} MeterValues measurand ${
660 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
661 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
663 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
664 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
665 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
666 }/${connectorMaximumPowerPerPhaseRounded}`,
671 // Current.Import measurand
672 currentSampledValueTemplate
= getSampledValueTemplate(
675 MeterValueMeasurand
.CURRENT_IMPORT
,
677 if (chargingStation
.getNumberOfPhases() === 3) {
678 currentPerPhaseSampledValueTemplates
= {
679 L1
: getSampledValueTemplate(
682 MeterValueMeasurand
.CURRENT_IMPORT
,
685 L2
: getSampledValueTemplate(
688 MeterValueMeasurand
.CURRENT_IMPORT
,
691 L3
: getSampledValueTemplate(
694 MeterValueMeasurand
.CURRENT_IMPORT
,
699 if (currentSampledValueTemplate
) {
700 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
!);
701 const errMsg
= `MeterValues measurand ${
702 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
703 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
704 chargingStation.templateFile
705 }, cannot calculate ${
706 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
708 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
709 const connectorMaximumAvailablePower
=
710 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
711 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
712 let connectorMaximumAmperage
: number;
713 switch (chargingStation
.stationInfo
?.currentOutType
) {
715 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
716 chargingStation
.getNumberOfPhases(),
717 connectorMaximumAvailablePower
,
718 chargingStation
.stationInfo
.voltageOut
!,
720 if (chargingStation
.getNumberOfPhases() === 3) {
721 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
722 currentSampledValueTemplate
.value
,
724 ? getRandomFloatFluctuatedRounded(
725 getLimitFromSampledValueTemplateCustomValue(
726 currentSampledValueTemplate
.value
,
727 connectorMaximumAmperage
,
728 connectorMinimumAmperage
,
731 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
732 fallbackValue
: connectorMinimumAmperage
,
735 currentSampledValueTemplate
.fluctuationPercent
??
736 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
739 const phase1FluctuatedValue
= isNotEmptyString(
740 currentPerPhaseSampledValueTemplates
.L1
?.value
,
742 ? getRandomFloatFluctuatedRounded(
743 getLimitFromSampledValueTemplateCustomValue(
744 currentPerPhaseSampledValueTemplates
.L1
?.value
,
745 connectorMaximumAmperage
,
746 connectorMinimumAmperage
,
749 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
750 fallbackValue
: connectorMinimumAmperage
,
753 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
754 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
757 const phase2FluctuatedValue
= isNotEmptyString(
758 currentPerPhaseSampledValueTemplates
.L2
?.value
,
760 ? getRandomFloatFluctuatedRounded(
761 getLimitFromSampledValueTemplateCustomValue(
762 currentPerPhaseSampledValueTemplates
.L2
?.value
,
763 connectorMaximumAmperage
,
764 connectorMinimumAmperage
,
767 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
768 fallbackValue
: connectorMinimumAmperage
,
771 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
772 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
775 const phase3FluctuatedValue
= isNotEmptyString(
776 currentPerPhaseSampledValueTemplates
.L3
?.value
,
778 ? getRandomFloatFluctuatedRounded(
779 getLimitFromSampledValueTemplateCustomValue(
780 currentPerPhaseSampledValueTemplates
.L3
?.value
,
781 connectorMaximumAmperage
,
782 connectorMinimumAmperage
,
785 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
786 fallbackValue
: connectorMinimumAmperage
,
789 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
790 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
793 currentMeasurandValues
.L1
=
794 phase1FluctuatedValue
??
795 defaultFluctuatedAmperagePerPhase
??
796 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
797 currentMeasurandValues
.L2
=
798 phase2FluctuatedValue
??
799 defaultFluctuatedAmperagePerPhase
??
800 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
801 currentMeasurandValues
.L3
=
802 phase3FluctuatedValue
??
803 defaultFluctuatedAmperagePerPhase
??
804 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
806 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
807 ? getRandomFloatFluctuatedRounded(
808 getLimitFromSampledValueTemplateCustomValue(
809 currentSampledValueTemplate
.value
,
810 connectorMaximumAmperage
,
811 connectorMinimumAmperage
,
814 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
815 fallbackValue
: connectorMinimumAmperage
,
818 currentSampledValueTemplate
.fluctuationPercent
??
819 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
821 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
822 currentMeasurandValues
.L2
= 0;
823 currentMeasurandValues
.L3
= 0;
825 currentMeasurandValues
.allPhases
= roundTo(
826 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
827 chargingStation
.getNumberOfPhases(),
832 connectorMaximumAmperage
= DCElectricUtils
.amperage(
833 connectorMaximumAvailablePower
,
834 chargingStation
.stationInfo
.voltageOut
!,
836 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
837 ? getRandomFloatFluctuatedRounded(
838 getLimitFromSampledValueTemplateCustomValue(
839 currentSampledValueTemplate
.value
,
840 connectorMaximumAmperage
,
841 connectorMinimumAmperage
,
844 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
845 fallbackValue
: connectorMinimumAmperage
,
848 currentSampledValueTemplate
.fluctuationPercent
??
849 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
851 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
854 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
855 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
857 meterValue
.sampledValue
.push(
858 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
),
860 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
862 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
863 connectorMaximumAmperage
||
864 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
865 connectorMinimumAmperage
||
869 `${chargingStation.logPrefix()} MeterValues measurand ${
870 meterValue.sampledValue[sampledValuesIndex].measurand ??
871 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
872 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
873 meterValue.sampledValue[sampledValuesIndex].value
874 }/${connectorMaximumAmperage}`,
879 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
882 const phaseValue
= `L${phase}`;
883 meterValue
.sampledValue
.push(
885 currentPerPhaseSampledValueTemplates
[
886 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
887 ] ?? currentSampledValueTemplate
,
888 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
890 phaseValue
as MeterValuePhase
,
893 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
895 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
896 connectorMaximumAmperage
||
897 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
898 connectorMinimumAmperage
||
902 `${chargingStation.logPrefix()} MeterValues measurand ${
903 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
904 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
906 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
907 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
908 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
909 }/${connectorMaximumAmperage}`,
914 // Energy.Active.Import.Register measurand (default)
915 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
);
916 if (energySampledValueTemplate
) {
917 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!);
919 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
920 const connectorMaximumAvailablePower
=
921 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
922 const connectorMaximumEnergyRounded
= roundTo(
923 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
926 const connectorMinimumEnergyRounded
= roundTo(
927 energySampledValueTemplate
.minimumValue
?? 0,
930 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
931 ? getRandomFloatFluctuatedRounded(
932 getLimitFromSampledValueTemplateCustomValue(
933 energySampledValueTemplate
.value
,
934 connectorMaximumEnergyRounded
,
935 connectorMinimumEnergyRounded
,
937 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
938 fallbackValue
: connectorMinimumEnergyRounded
,
939 unitMultiplier
: unitDivider
,
942 energySampledValueTemplate
.fluctuationPercent
??
943 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
945 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
946 // Persist previous value on connector
949 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
950 connector
.energyActiveImportRegisterValue
! >= 0 &&
951 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
952 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
954 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
955 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
957 connector
.energyActiveImportRegisterValue
= 0;
958 connector
.transactionEnergyActiveImportRegisterValue
= 0;
961 meterValue
.sampledValue
.push(
963 energySampledValueTemplate
,
965 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
971 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
973 energyValueRounded
> connectorMaximumEnergyRounded
||
974 energyValueRounded
< connectorMinimumEnergyRounded
||
978 `${chargingStation.logPrefix()} MeterValues measurand ${
979 meterValue.sampledValue[sampledValuesIndex].measurand ??
980 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
981 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
986 case OCPPVersion
.VERSION_20
:
987 case OCPPVersion
.VERSION_201
:
990 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
991 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
996 export const buildTransactionEndMeterValue
= (
997 chargingStation
: ChargingStation
,
1001 let meterValue
: MeterValue
;
1002 let sampledValueTemplate
: SampledValueTemplate
| undefined;
1003 let unitDivider
: number;
1004 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1005 case OCPPVersion
.VERSION_16
:
1007 timestamp
: new Date(),
1010 // Energy.Active.Import.Register measurand (default)
1011 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
);
1012 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
1013 meterValue
.sampledValue
.push(
1015 sampledValueTemplate
!,
1016 roundTo((meterStop
?? 0) / unitDivider
, 4),
1017 MeterValueContext
.TRANSACTION_END
,
1021 case OCPPVersion
.VERSION_20
:
1022 case OCPPVersion
.VERSION_201
:
1024 throw new BaseError(
1025 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1026 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
1031 const checkMeasurandPowerDivider
= (
1032 chargingStation
: ChargingStation
,
1033 measurandType
: MeterValueMeasurand
,
1035 if (isUndefined(chargingStation
.powerDivider
)) {
1036 const errMsg
= `MeterValues measurand ${
1037 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1038 }: powerDivider is undefined`;
1039 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1040 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
1041 } else if (chargingStation
?.powerDivider
<= 0) {
1042 const errMsg
= `MeterValues measurand ${
1043 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1044 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1045 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1046 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
);
1050 const getLimitFromSampledValueTemplateCustomValue
= (
1051 value
: string | undefined,
1054 options
?: { limitationEnabled
?: boolean; fallbackValue
?: number; unitMultiplier
?: number },
1058 limitationEnabled
: false,
1064 const parsedValue
= parseInt(value
?? '');
1065 if (options
?.limitationEnabled
) {
1067 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1071 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!;
1074 const getSampledValueTemplate
= (
1075 chargingStation
: ChargingStation
,
1076 connectorId
: number,
1077 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1078 phase
?: MeterValuePhase
,
1079 ): SampledValueTemplate
| undefined => {
1080 const onPhaseStr
= phase
? `on phase ${phase} ` : '';
1081 if (OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
) === false) {
1083 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1088 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1089 getConfigurationKey(
1091 StandardParametersKey
.MeterValuesSampledData
,
1092 )?.value
?.includes(measurand
) === false
1095 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1096 StandardParametersKey.MeterValuesSampledData
1101 const sampledValueTemplates
: SampledValueTemplate
[] =
1102 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
;
1105 isNotEmptyArray(sampledValueTemplates
) === true && index
< sampledValueTemplates
.length
;
1109 OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1110 sampledValueTemplates
[index
]?.measurand
??
1111 MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1115 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1119 sampledValueTemplates
[index
]?.phase
=== phase
&&
1120 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1121 getConfigurationKey(
1123 StandardParametersKey
.MeterValuesSampledData
,
1124 )?.value
?.includes(measurand
) === true
1126 return sampledValueTemplates
[index
];
1129 !sampledValueTemplates
[index
]?.phase
&&
1130 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1131 getConfigurationKey(
1133 StandardParametersKey
.MeterValuesSampledData
,
1134 )?.value
?.includes(measurand
) === true
1136 return sampledValueTemplates
[index
];
1138 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1139 (!sampledValueTemplates
[index
]?.measurand
||
1140 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1142 return sampledValueTemplates
[index
];
1145 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1146 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`;
1147 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`);
1148 throw new BaseError(errorMsg
);
1151 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
1155 const buildSampledValue
= (
1156 sampledValueTemplate
: SampledValueTemplate
,
1158 context
?: MeterValueContext
,
1159 phase
?: MeterValuePhase
,
1160 ): SampledValue
=> {
1161 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1162 const sampledValueLocation
=
1163 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1164 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1166 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1167 unit
: sampledValueTemplate
.unit
,
1169 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1170 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1171 measurand
: sampledValueTemplate
.measurand
,
1173 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1174 ...(!isNullOrUndefined(value
) && { value
: value
.toString() }),
1175 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1179 const getMeasurandDefaultLocation
= (
1180 measurandType
: MeterValueMeasurand
,
1181 ): MeterValueLocation
| undefined => {
1182 switch (measurandType
) {
1183 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1184 return MeterValueLocation
.EV
;
1188 // const getMeasurandDefaultUnit = (
1189 // measurandType: MeterValueMeasurand,
1190 // ): MeterValueUnit | undefined => {
1191 // switch (measurandType) {
1192 // case MeterValueMeasurand.CURRENT_EXPORT:
1193 // case MeterValueMeasurand.CURRENT_IMPORT:
1194 // case MeterValueMeasurand.CURRENT_OFFERED:
1195 // return MeterValueUnit.AMP;
1196 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1197 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1198 // return MeterValueUnit.WATT_HOUR;
1199 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1200 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1201 // case MeterValueMeasurand.POWER_OFFERED:
1202 // return MeterValueUnit.WATT;
1203 // case MeterValueMeasurand.STATE_OF_CHARGE:
1204 // return MeterValueUnit.PERCENT;
1205 // case MeterValueMeasurand.VOLTAGE:
1206 // return MeterValueUnit.VOLT;
1210 export class OCPPServiceUtils
{
1211 public static getMessageTypeString
= getMessageTypeString
;
1212 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
;
1213 public static isIdTagAuthorized
= isIdTagAuthorized
;
1214 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
;
1215 protected static getSampledValueTemplate
= getSampledValueTemplate
;
1216 protected static buildSampledValue
= buildSampledValue
;
1218 protected constructor() {
1219 // This is intentional
1222 public static ajvErrorsToErrorType(errors
: ErrorObject
[] | null | undefined): ErrorType
{
1223 if (isNotEmptyArray(errors
) === true) {
1224 for (const error
of errors
as DefinedError
[]) {
1225 switch (error
.keyword
) {
1227 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
;
1228 case 'dependencies':
1230 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
;
1233 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
;
1237 return ErrorType
.FORMAT_VIOLATION
;
1240 public static isRequestCommandSupported(
1241 chargingStation
: ChargingStation
,
1242 command
: RequestCommand
,
1244 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
);
1246 isRequestCommand
=== true &&
1247 !chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
1251 isRequestCommand
=== true &&
1252 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
]
1254 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
];
1256 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
1260 public static isIncomingRequestCommandSupported(
1261 chargingStation
: ChargingStation
,
1262 command
: IncomingRequestCommand
,
1264 const isIncomingRequestCommand
=
1265 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
);
1267 isIncomingRequestCommand
=== true &&
1268 !chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
1272 isIncomingRequestCommand
=== true &&
1273 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
?.[command
]
1275 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
];
1277 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
1281 public static isMessageTriggerSupported(
1282 chargingStation
: ChargingStation
,
1283 messageTrigger
: MessageTrigger
,
1285 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
);
1286 if (isMessageTrigger
=== true && !chargingStation
.stationInfo
?.messageTriggerSupport
) {
1289 isMessageTrigger
=== true &&
1290 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
]
1292 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
];
1295 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`,
1300 public static isConnectorIdValid(
1301 chargingStation
: ChargingStation
,
1302 ocppCommand
: IncomingRequestCommand
,
1303 connectorId
: number,
1305 if (connectorId
< 0) {
1307 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`,
1314 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1315 for (const key
in obj
) {
1316 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1317 if (isDate(obj
![key
])) {
1318 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1319 (obj
![key
] as string) = (obj
![key
] as Date).toISOString();
1320 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1321 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
1322 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1323 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
);
1328 public static startHeartbeatInterval(chargingStation
: ChargingStation
, interval
: number): void {
1329 if (chargingStation
.heartbeatSetInterval
=== undefined) {
1330 chargingStation
.startHeartbeat();
1331 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1332 chargingStation
.restartHeartbeat();
1336 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1337 relativePath
: string,
1338 ocppVersion
: OCPPVersion
,
1339 moduleName
?: string,
1340 methodName
?: string,
1341 ): JSONSchemaType
<T
> {
1342 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
);
1344 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>;
1346 handleFileException(
1348 FileType
.JsonSchema
,
1349 error
as NodeJS
.ErrnoException
,
1350 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1351 { throwError
: false },
1353 return {} as JSONSchemaType
<T
>;
1357 private static logPrefix
= (
1358 ocppVersion
: OCPPVersion
,
1359 moduleName
?: string,
1360 methodName
?: string,
1363 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1364 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1365 : ` OCPP ${ocppVersion} |`;
1366 return logPrefix(logMsg
);