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
,
23 type ConnectorStatusEnum
,
27 IncomingRequestCommand
,
29 type MeasurandPerPhaseSampledValueTemplates
,
39 type OCPP16ChargePointStatus
,
40 type OCPP16StatusNotificationRequest
,
41 type OCPP20ConnectorStatusEnumType
,
42 type OCPP20StatusNotificationRequest
,
46 type SampledValueTemplate
,
47 StandardParametersKey
,
48 type StatusNotificationRequest
,
49 type StatusNotificationResponse
50 } from
'../../types/index.js'
57 getRandomFloatFluctuatedRounded
,
58 getRandomFloatRounded
,
68 } from
'../../utils/index.js'
70 export const getMessageTypeString
= (messageType
: MessageType
): string => {
71 switch (messageType
) {
72 case MessageType
.CALL_MESSAGE
:
74 case MessageType
.CALL_RESULT_MESSAGE
:
76 case MessageType
.CALL_ERROR_MESSAGE
:
83 const buildStatusNotificationRequest
= (
84 chargingStation
: ChargingStation
,
86 status: ConnectorStatusEnum
,
88 ): StatusNotificationRequest
=> {
89 switch (chargingStation
.stationInfo
?.ocppVersion
) {
90 case OCPPVersion
.VERSION_16
:
93 status: status as OCPP16ChargePointStatus
,
94 errorCode
: ChargePointErrorCode
.NO_ERROR
95 } satisfies OCPP16StatusNotificationRequest
96 case OCPPVersion
.VERSION_20
:
97 case OCPPVersion
.VERSION_201
:
99 timestamp
: new Date(),
100 connectorStatus
: status as OCPP20ConnectorStatusEnumType
,
102 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
104 } satisfies 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
=== false
120 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
123 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
125 connectorStatus
!= null &&
126 chargingStation
.getLocalAuthListEnabled() &&
127 isIdTagLocalAuthorized(chargingStation
, idTag
)
129 connectorStatus
.localAuthorizeIdTag
= idTag
130 connectorStatus
.idTagLocalAuthorized
= true
132 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
133 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
138 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
140 chargingStation
.hasIdTags() &&
142 chargingStation
.idTagsCache
143 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
144 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
!)!)
145 ?.find(tag
=> tag
=== idTag
)
150 const isIdTagRemoteAuthorized
= async (
151 chargingStation
: ChargingStation
,
154 ): Promise
<boolean> => {
155 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
156 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
159 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
161 RequestCommand
.AUTHORIZE
,
166 ).idTagInfo
.status === AuthorizationStatus
.ACCEPTED
170 export const sendAndSetConnectorStatus
= async (
171 chargingStation
: ChargingStation
,
173 status: ConnectorStatusEnum
,
175 options
?: { send
: boolean }
176 ): Promise
<void> => {
177 options
= { send
: true, ...options
}
179 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
180 await chargingStation
.ocppRequestService
.requestHandler
<
181 StatusNotificationRequest
,
182 StatusNotificationResponse
185 RequestCommand
.STATUS_NOTIFICATION
,
186 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
189 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
190 chargingStation
.getConnectorStatus(connectorId
)!.status = status
191 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
193 ...chargingStation
.getConnectorStatus(connectorId
)
197 const checkConnectorStatusTransition
= (
198 chargingStation
: ChargingStation
,
200 status: ConnectorStatusEnum
202 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)?.status
203 let transitionAllowed
= false
204 switch (chargingStation
.stationInfo
?.ocppVersion
) {
205 case OCPPVersion
.VERSION_16
:
207 (connectorId
=== 0 &&
208 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
209 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
212 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
213 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
216 transitionAllowed
= true
219 case OCPPVersion
.VERSION_20
:
220 case OCPPVersion
.VERSION_201
:
222 (connectorId
=== 0 &&
223 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
224 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
227 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
228 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
231 transitionAllowed
= true
236 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
239 if (!transitionAllowed
) {
241 `${chargingStation.logPrefix()} OCPP ${
242 chargingStation.stationInfo.ocppVersion
243 } connector id ${connectorId} status transition from '${
244 chargingStation.getConnectorStatus(connectorId)?.status
245 }' to '${status}' is not allowed`
248 return transitionAllowed
251 export const buildMeterValue
= (
252 chargingStation
: ChargingStation
,
254 transactionId
: number,
258 const connector
= chargingStation
.getConnectorStatus(connectorId
)
259 let meterValue
: MeterValue
260 let socSampledValueTemplate
: SampledValueTemplate
| undefined
261 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
262 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
263 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
264 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
265 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
266 let energySampledValueTemplate
: SampledValueTemplate
| undefined
267 switch (chargingStation
.stationInfo
?.ocppVersion
) {
268 case OCPPVersion
.VERSION_16
:
270 timestamp
: new Date(),
274 socSampledValueTemplate
= getSampledValueTemplate(
277 MeterValueMeasurand
.STATE_OF_CHARGE
279 if (socSampledValueTemplate
!= null) {
280 const socMaximumValue
= 100
281 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
282 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
283 ? getRandomFloatFluctuatedRounded(
284 parseInt(socSampledValueTemplate
.value
),
285 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
287 : getRandomInteger(socMaximumValue
, socMinimumValue
)
288 meterValue
.sampledValue
.push(
289 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
291 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
293 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
294 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
298 `${chargingStation.logPrefix()} MeterValues measurand ${
299 meterValue.sampledValue[sampledValuesIndex].measurand ??
300 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
301 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
302 meterValue.sampledValue[sampledValuesIndex].value
303 }/${socMaximumValue}`
308 voltageSampledValueTemplate
= getSampledValueTemplate(
311 MeterValueMeasurand
.VOLTAGE
313 if (voltageSampledValueTemplate
!= null) {
314 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
315 ? parseInt(voltageSampledValueTemplate
.value
)
316 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
317 chargingStation
.stationInfo
.voltageOut
!
318 const fluctuationPercent
=
319 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
320 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
321 voltageSampledValueTemplateValue
,
325 chargingStation
.getNumberOfPhases() !== 3 ||
326 (chargingStation
.getNumberOfPhases() === 3 &&
327 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
329 meterValue
.sampledValue
.push(
330 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
335 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
338 const phaseLineToNeutralValue
= `L${phase}-N`
339 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
342 MeterValueMeasurand
.VOLTAGE
,
343 phaseLineToNeutralValue
as MeterValuePhase
345 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
346 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
347 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
348 voltagePhaseLineToNeutralSampledValueTemplate
.value
350 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
351 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
352 chargingStation
.stationInfo
.voltageOut
!
353 const fluctuationPhaseToNeutralPercent
=
354 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
355 Constants
.DEFAULT_FLUCTUATION_PERCENT
356 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
357 voltagePhaseLineToNeutralSampledValueTemplateValue
,
358 fluctuationPhaseToNeutralPercent
361 meterValue
.sampledValue
.push(
363 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
364 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
366 phaseLineToNeutralValue
as MeterValuePhase
369 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
370 const phaseLineToLineValue
= `L${phase}-L${
371 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
372 ? (phase + 1) % chargingStation.getNumberOfPhases()
373 : chargingStation.getNumberOfPhases()
375 const voltagePhaseLineToLineValueRounded
= roundTo(
376 Math.sqrt(chargingStation
.getNumberOfPhases()) *
377 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
378 chargingStation
.stationInfo
.voltageOut
!,
381 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
384 MeterValueMeasurand
.VOLTAGE
,
385 phaseLineToLineValue
as MeterValuePhase
387 let voltagePhaseLineToLineMeasurandValue
: number | undefined
388 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
389 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
390 voltagePhaseLineToLineSampledValueTemplate
.value
392 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
393 : voltagePhaseLineToLineValueRounded
394 const fluctuationPhaseLineToLinePercent
=
395 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
396 Constants
.DEFAULT_FLUCTUATION_PERCENT
397 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
398 voltagePhaseLineToLineSampledValueTemplateValue
,
399 fluctuationPhaseLineToLinePercent
402 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
403 voltagePhaseLineToLineValueRounded
,
406 meterValue
.sampledValue
.push(
408 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
409 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
411 phaseLineToLineValue
as MeterValuePhase
417 // Power.Active.Import measurand
418 powerSampledValueTemplate
= getSampledValueTemplate(
421 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
423 if (chargingStation
.getNumberOfPhases() === 3) {
424 powerPerPhaseSampledValueTemplates
= {
425 L1
: getSampledValueTemplate(
428 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
431 L2
: getSampledValueTemplate(
434 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
437 L3
: getSampledValueTemplate(
440 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
445 if (powerSampledValueTemplate
!= null) {
446 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
447 const errMsg
= `MeterValues measurand ${
448 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
449 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
450 chargingStation.templateFile
451 }, cannot calculate ${
452 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
454 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
455 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
456 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
457 const connectorMaximumAvailablePower
=
458 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
459 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
460 const connectorMaximumPowerPerPhase
= Math.round(
461 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
463 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
464 const connectorMinimumPowerPerPhase
= Math.round(
465 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
467 switch (chargingStation
.stationInfo
.currentOutType
) {
469 if (chargingStation
.getNumberOfPhases() === 3) {
470 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
471 powerSampledValueTemplate
.value
473 ? getRandomFloatFluctuatedRounded(
474 getLimitFromSampledValueTemplateCustomValue(
475 powerSampledValueTemplate
.value
,
476 connectorMaximumPower
/ unitDivider
,
477 connectorMinimumPower
/ unitDivider
,
480 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
481 fallbackValue
: connectorMinimumPower
/ unitDivider
483 ) / chargingStation
.getNumberOfPhases(),
484 powerSampledValueTemplate
.fluctuationPercent
??
485 Constants
.DEFAULT_FLUCTUATION_PERCENT
488 const phase1FluctuatedValue
= isNotEmptyString(
489 powerPerPhaseSampledValueTemplates
.L1
?.value
491 ? getRandomFloatFluctuatedRounded(
492 getLimitFromSampledValueTemplateCustomValue(
493 powerPerPhaseSampledValueTemplates
.L1
.value
,
494 connectorMaximumPowerPerPhase
/ unitDivider
,
495 connectorMinimumPowerPerPhase
/ unitDivider
,
498 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
499 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
502 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
503 Constants
.DEFAULT_FLUCTUATION_PERCENT
506 const phase2FluctuatedValue
= isNotEmptyString(
507 powerPerPhaseSampledValueTemplates
.L2
?.value
509 ? getRandomFloatFluctuatedRounded(
510 getLimitFromSampledValueTemplateCustomValue(
511 powerPerPhaseSampledValueTemplates
.L2
.value
,
512 connectorMaximumPowerPerPhase
/ unitDivider
,
513 connectorMinimumPowerPerPhase
/ unitDivider
,
516 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
517 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
520 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
521 Constants
.DEFAULT_FLUCTUATION_PERCENT
524 const phase3FluctuatedValue
= isNotEmptyString(
525 powerPerPhaseSampledValueTemplates
.L3
?.value
527 ? getRandomFloatFluctuatedRounded(
528 getLimitFromSampledValueTemplateCustomValue(
529 powerPerPhaseSampledValueTemplates
.L3
.value
,
530 connectorMaximumPowerPerPhase
/ unitDivider
,
531 connectorMinimumPowerPerPhase
/ unitDivider
,
534 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
535 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
538 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
539 Constants
.DEFAULT_FLUCTUATION_PERCENT
542 powerMeasurandValues
.L1
=
543 phase1FluctuatedValue
??
544 defaultFluctuatedPowerPerPhase
??
545 getRandomFloatRounded(
546 connectorMaximumPowerPerPhase
/ unitDivider
,
547 connectorMinimumPowerPerPhase
/ unitDivider
549 powerMeasurandValues
.L2
=
550 phase2FluctuatedValue
??
551 defaultFluctuatedPowerPerPhase
??
552 getRandomFloatRounded(
553 connectorMaximumPowerPerPhase
/ unitDivider
,
554 connectorMinimumPowerPerPhase
/ unitDivider
556 powerMeasurandValues
.L3
=
557 phase3FluctuatedValue
??
558 defaultFluctuatedPowerPerPhase
??
559 getRandomFloatRounded(
560 connectorMaximumPowerPerPhase
/ unitDivider
,
561 connectorMinimumPowerPerPhase
/ unitDivider
564 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
565 ? getRandomFloatFluctuatedRounded(
566 getLimitFromSampledValueTemplateCustomValue(
567 powerSampledValueTemplate
.value
,
568 connectorMaximumPower
/ unitDivider
,
569 connectorMinimumPower
/ unitDivider
,
572 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
573 fallbackValue
: connectorMinimumPower
/ unitDivider
576 powerSampledValueTemplate
.fluctuationPercent
??
577 Constants
.DEFAULT_FLUCTUATION_PERCENT
579 : getRandomFloatRounded(
580 connectorMaximumPower
/ unitDivider
,
581 connectorMinimumPower
/ unitDivider
583 powerMeasurandValues
.L2
= 0
584 powerMeasurandValues
.L3
= 0
586 powerMeasurandValues
.allPhases
= roundTo(
587 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
592 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
593 ? getRandomFloatFluctuatedRounded(
594 getLimitFromSampledValueTemplateCustomValue(
595 powerSampledValueTemplate
.value
,
596 connectorMaximumPower
/ unitDivider
,
597 connectorMinimumPower
/ unitDivider
,
600 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
601 fallbackValue
: connectorMinimumPower
/ unitDivider
604 powerSampledValueTemplate
.fluctuationPercent
??
605 Constants
.DEFAULT_FLUCTUATION_PERCENT
607 : getRandomFloatRounded(
608 connectorMaximumPower
/ unitDivider
,
609 connectorMinimumPower
/ unitDivider
613 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
614 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
616 meterValue
.sampledValue
.push(
617 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
619 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
620 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
621 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
623 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
624 connectorMaximumPowerRounded
||
625 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
626 connectorMinimumPowerRounded
||
630 `${chargingStation.logPrefix()} MeterValues measurand ${
631 meterValue.sampledValue[sampledValuesIndex].measurand ??
632 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
633 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
634 meterValue.sampledValue[sampledValuesIndex].value
635 }/${connectorMaximumPowerRounded}`
640 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
643 const phaseValue
= `L${phase}-N`
644 meterValue
.sampledValue
.push(
646 powerPerPhaseSampledValueTemplates
[
647 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
648 ] ?? powerSampledValueTemplate
,
649 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
651 phaseValue
as MeterValuePhase
654 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
655 const connectorMaximumPowerPerPhaseRounded
= roundTo(
656 connectorMaximumPowerPerPhase
/ unitDivider
,
659 const connectorMinimumPowerPerPhaseRounded
= roundTo(
660 connectorMinimumPowerPerPhase
/ unitDivider
,
664 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
665 connectorMaximumPowerPerPhaseRounded
||
666 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
667 connectorMinimumPowerPerPhaseRounded
||
671 `${chargingStation.logPrefix()} MeterValues measurand ${
672 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
673 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
675 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
676 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
677 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
678 }/${connectorMaximumPowerPerPhaseRounded}`
683 // Current.Import measurand
684 currentSampledValueTemplate
= getSampledValueTemplate(
687 MeterValueMeasurand
.CURRENT_IMPORT
689 if (chargingStation
.getNumberOfPhases() === 3) {
690 currentPerPhaseSampledValueTemplates
= {
691 L1
: getSampledValueTemplate(
694 MeterValueMeasurand
.CURRENT_IMPORT
,
697 L2
: getSampledValueTemplate(
700 MeterValueMeasurand
.CURRENT_IMPORT
,
703 L3
: getSampledValueTemplate(
706 MeterValueMeasurand
.CURRENT_IMPORT
,
711 if (currentSampledValueTemplate
!= null) {
712 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
713 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
714 const errMsg
= `MeterValues measurand ${
715 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
716 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
717 chargingStation.templateFile
718 }, cannot calculate ${
719 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
721 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
722 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
723 const connectorMaximumAvailablePower
=
724 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
725 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
726 let connectorMaximumAmperage
: number
727 switch (chargingStation
.stationInfo
.currentOutType
) {
729 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
730 chargingStation
.getNumberOfPhases(),
731 connectorMaximumAvailablePower
,
732 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
733 chargingStation
.stationInfo
.voltageOut
!
735 if (chargingStation
.getNumberOfPhases() === 3) {
736 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
737 currentSampledValueTemplate
.value
739 ? getRandomFloatFluctuatedRounded(
740 getLimitFromSampledValueTemplateCustomValue(
741 currentSampledValueTemplate
.value
,
742 connectorMaximumAmperage
,
743 connectorMinimumAmperage
,
746 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
747 fallbackValue
: connectorMinimumAmperage
750 currentSampledValueTemplate
.fluctuationPercent
??
751 Constants
.DEFAULT_FLUCTUATION_PERCENT
754 const phase1FluctuatedValue
= isNotEmptyString(
755 currentPerPhaseSampledValueTemplates
.L1
?.value
757 ? getRandomFloatFluctuatedRounded(
758 getLimitFromSampledValueTemplateCustomValue(
759 currentPerPhaseSampledValueTemplates
.L1
.value
,
760 connectorMaximumAmperage
,
761 connectorMinimumAmperage
,
764 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
765 fallbackValue
: connectorMinimumAmperage
768 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
769 Constants
.DEFAULT_FLUCTUATION_PERCENT
772 const phase2FluctuatedValue
= isNotEmptyString(
773 currentPerPhaseSampledValueTemplates
.L2
?.value
775 ? getRandomFloatFluctuatedRounded(
776 getLimitFromSampledValueTemplateCustomValue(
777 currentPerPhaseSampledValueTemplates
.L2
.value
,
778 connectorMaximumAmperage
,
779 connectorMinimumAmperage
,
782 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
783 fallbackValue
: connectorMinimumAmperage
786 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
787 Constants
.DEFAULT_FLUCTUATION_PERCENT
790 const phase3FluctuatedValue
= isNotEmptyString(
791 currentPerPhaseSampledValueTemplates
.L3
?.value
793 ? getRandomFloatFluctuatedRounded(
794 getLimitFromSampledValueTemplateCustomValue(
795 currentPerPhaseSampledValueTemplates
.L3
.value
,
796 connectorMaximumAmperage
,
797 connectorMinimumAmperage
,
800 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
801 fallbackValue
: connectorMinimumAmperage
804 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
805 Constants
.DEFAULT_FLUCTUATION_PERCENT
808 currentMeasurandValues
.L1
=
809 phase1FluctuatedValue
??
810 defaultFluctuatedAmperagePerPhase
??
811 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
812 currentMeasurandValues
.L2
=
813 phase2FluctuatedValue
??
814 defaultFluctuatedAmperagePerPhase
??
815 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
816 currentMeasurandValues
.L3
=
817 phase3FluctuatedValue
??
818 defaultFluctuatedAmperagePerPhase
??
819 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
821 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
822 ? getRandomFloatFluctuatedRounded(
823 getLimitFromSampledValueTemplateCustomValue(
824 currentSampledValueTemplate
.value
,
825 connectorMaximumAmperage
,
826 connectorMinimumAmperage
,
829 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
830 fallbackValue
: connectorMinimumAmperage
833 currentSampledValueTemplate
.fluctuationPercent
??
834 Constants
.DEFAULT_FLUCTUATION_PERCENT
836 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
837 currentMeasurandValues
.L2
= 0
838 currentMeasurandValues
.L3
= 0
840 currentMeasurandValues
.allPhases
= roundTo(
841 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
842 chargingStation
.getNumberOfPhases(),
847 connectorMaximumAmperage
= DCElectricUtils
.amperage(
848 connectorMaximumAvailablePower
,
849 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
850 chargingStation
.stationInfo
.voltageOut
!
852 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
853 ? getRandomFloatFluctuatedRounded(
854 getLimitFromSampledValueTemplateCustomValue(
855 currentSampledValueTemplate
.value
,
856 connectorMaximumAmperage
,
857 connectorMinimumAmperage
,
860 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
861 fallbackValue
: connectorMinimumAmperage
864 currentSampledValueTemplate
.fluctuationPercent
??
865 Constants
.DEFAULT_FLUCTUATION_PERCENT
867 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
870 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
871 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
873 meterValue
.sampledValue
.push(
874 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
876 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
878 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
879 connectorMaximumAmperage
||
880 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
881 connectorMinimumAmperage
||
885 `${chargingStation.logPrefix()} MeterValues measurand ${
886 meterValue.sampledValue[sampledValuesIndex].measurand ??
887 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
888 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
889 meterValue.sampledValue[sampledValuesIndex].value
890 }/${connectorMaximumAmperage}`
895 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
898 const phaseValue
= `L${phase}`
899 meterValue
.sampledValue
.push(
901 currentPerPhaseSampledValueTemplates
[
902 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
903 ] ?? currentSampledValueTemplate
,
904 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
906 phaseValue
as MeterValuePhase
909 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
911 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
912 connectorMaximumAmperage
||
913 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
914 connectorMinimumAmperage
||
918 `${chargingStation.logPrefix()} MeterValues measurand ${
919 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
920 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
922 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
923 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
924 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
925 }/${connectorMaximumAmperage}`
930 // Energy.Active.Import.Register measurand (default)
931 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
932 if (energySampledValueTemplate
!= null) {
933 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
935 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
936 const connectorMaximumAvailablePower
=
937 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
938 const connectorMaximumEnergyRounded
= roundTo(
939 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
942 const connectorMinimumEnergyRounded
= roundTo(
943 energySampledValueTemplate
.minimumValue
?? 0,
946 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
947 ? getRandomFloatFluctuatedRounded(
948 getLimitFromSampledValueTemplateCustomValue(
949 energySampledValueTemplate
.value
,
950 connectorMaximumEnergyRounded
,
951 connectorMinimumEnergyRounded
,
953 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
954 fallbackValue
: connectorMinimumEnergyRounded
,
955 unitMultiplier
: unitDivider
958 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
960 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
961 // Persist previous value on connector
962 if (connector
!= null) {
964 connector
.energyActiveImportRegisterValue
!= null &&
965 connector
.energyActiveImportRegisterValue
>= 0 &&
966 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
967 connector
.transactionEnergyActiveImportRegisterValue
>= 0
969 connector
.energyActiveImportRegisterValue
+= energyValueRounded
970 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
972 connector
.energyActiveImportRegisterValue
= 0
973 connector
.transactionEnergyActiveImportRegisterValue
= 0
976 meterValue
.sampledValue
.push(
978 energySampledValueTemplate
,
980 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
986 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
988 energyValueRounded
> connectorMaximumEnergyRounded
||
989 energyValueRounded
< connectorMinimumEnergyRounded
||
993 `${chargingStation.logPrefix()} MeterValues measurand ${
994 meterValue.sampledValue[sampledValuesIndex].measurand ??
995 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
996 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1001 case OCPPVersion
.VERSION_20
:
1002 case OCPPVersion
.VERSION_201
:
1004 throw new BaseError(
1005 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1010 export const buildTransactionEndMeterValue
= (
1011 chargingStation
: ChargingStation
,
1012 connectorId
: number,
1013 meterStop
: number | undefined
1015 let meterValue
: MeterValue
1016 let sampledValueTemplate
: SampledValueTemplate
| undefined
1017 let unitDivider
: number
1018 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1019 case OCPPVersion
.VERSION_16
:
1021 timestamp
: new Date(),
1024 // Energy.Active.Import.Register measurand (default)
1025 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1026 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1027 meterValue
.sampledValue
.push(
1029 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1030 sampledValueTemplate
!,
1031 roundTo((meterStop
?? 0) / unitDivider
, 4),
1032 MeterValueContext
.TRANSACTION_END
1036 case OCPPVersion
.VERSION_20
:
1037 case OCPPVersion
.VERSION_201
:
1039 throw new BaseError(
1040 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1045 const checkMeasurandPowerDivider
= (
1046 chargingStation
: ChargingStation
,
1047 measurandType
: MeterValueMeasurand
| undefined
1049 if (chargingStation
.powerDivider
== null) {
1050 const errMsg
= `MeterValues measurand ${
1051 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1052 }: powerDivider is undefined`
1053 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1054 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1055 } else if (chargingStation
.powerDivider
<= 0) {
1056 const errMsg
= `MeterValues measurand ${
1057 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1058 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1059 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1060 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1064 const getLimitFromSampledValueTemplateCustomValue
= (
1065 value
: string | undefined,
1068 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1072 limitationEnabled
: false,
1078 const parsedValue
= parseInt(value
?? '')
1079 if (options
.limitationEnabled
=== true) {
1081 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1082 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1086 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1087 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1090 const getSampledValueTemplate
= (
1091 chargingStation
: ChargingStation
,
1092 connectorId
: number,
1093 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1094 phase
?: MeterValuePhase
1095 ): SampledValueTemplate
| undefined => {
1096 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1097 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1099 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1104 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1105 getConfigurationKey(
1107 StandardParametersKey
.MeterValuesSampledData
1108 )?.value
?.includes(measurand
) === false
1111 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1112 StandardParametersKey.MeterValuesSampledData
1117 const sampledValueTemplates
=
1118 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1119 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1122 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1126 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1127 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1131 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1135 sampledValueTemplates
[index
]?.phase
=== phase
&&
1136 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1137 getConfigurationKey(
1139 StandardParametersKey
.MeterValuesSampledData
1140 )?.value
?.includes(measurand
) === true
1142 return sampledValueTemplates
[index
]
1145 sampledValueTemplates
[index
]?.phase
== null &&
1146 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1147 getConfigurationKey(
1149 StandardParametersKey
.MeterValuesSampledData
1150 )?.value
?.includes(measurand
) === true
1152 return sampledValueTemplates
[index
]
1154 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1155 (sampledValueTemplates
[index
]?.measurand
== null ||
1156 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1158 return sampledValueTemplates
[index
]
1161 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1162 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1163 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1164 throw new BaseError(errorMsg
)
1167 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1171 const buildSampledValue
= (
1172 sampledValueTemplate
: SampledValueTemplate
,
1174 context
?: MeterValueContext
,
1175 phase
?: MeterValuePhase
1176 ): SampledValue
=> {
1177 const sampledValueContext
= context
?? sampledValueTemplate
.context
1178 const sampledValueLocation
=
1179 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1180 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1181 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1183 ...(sampledValueTemplate
.unit
!= null && {
1184 unit
: sampledValueTemplate
.unit
1186 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1187 ...(sampledValueTemplate
.measurand
!= null && {
1188 measurand
: sampledValueTemplate
.measurand
1190 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1191 ...{ value
: value
.toString() },
1192 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1193 } satisfies SampledValue
1196 const getMeasurandDefaultLocation
= (
1197 measurandType
: MeterValueMeasurand
1198 ): MeterValueLocation
| undefined => {
1199 switch (measurandType
) {
1200 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1201 return MeterValueLocation
.EV
1205 // const getMeasurandDefaultUnit = (
1206 // measurandType: MeterValueMeasurand
1207 // ): MeterValueUnit | undefined => {
1208 // switch (measurandType) {
1209 // case MeterValueMeasurand.CURRENT_EXPORT:
1210 // case MeterValueMeasurand.CURRENT_IMPORT:
1211 // case MeterValueMeasurand.CURRENT_OFFERED:
1212 // return MeterValueUnit.AMP
1213 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1214 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1215 // return MeterValueUnit.WATT_HOUR
1216 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1217 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1218 // case MeterValueMeasurand.POWER_OFFERED:
1219 // return MeterValueUnit.WATT
1220 // case MeterValueMeasurand.STATE_OF_CHARGE:
1221 // return MeterValueUnit.PERCENT
1222 // case MeterValueMeasurand.VOLTAGE:
1223 // return MeterValueUnit.VOLT
1227 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1228 export class OCPPServiceUtils
{
1229 public static readonly getMessageTypeString
= getMessageTypeString
1230 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1231 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1232 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1233 protected static getSampledValueTemplate
= getSampledValueTemplate
1234 protected static buildSampledValue
= buildSampledValue
1236 protected constructor () {
1237 // This is intentional
1240 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | undefined | null): ErrorType
{
1241 if (isNotEmptyArray(errors
)) {
1242 for (const error
of errors
as DefinedError
[]) {
1243 switch (error
.keyword
) {
1245 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1246 case 'dependencies':
1248 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1251 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1255 return ErrorType
.FORMAT_VIOLATION
1258 public static isRequestCommandSupported (
1259 chargingStation
: ChargingStation
,
1260 command
: RequestCommand
1262 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1265 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1270 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1272 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1274 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1278 public static isIncomingRequestCommandSupported (
1279 chargingStation
: ChargingStation
,
1280 command
: IncomingRequestCommand
1282 const isIncomingRequestCommand
=
1283 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1285 isIncomingRequestCommand
&&
1286 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1290 isIncomingRequestCommand
&&
1291 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1293 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1295 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1299 public static isMessageTriggerSupported (
1300 chargingStation
: ChargingStation
,
1301 messageTrigger
: MessageTrigger
1303 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1304 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1308 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1310 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1313 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1318 public static isConnectorIdValid (
1319 chargingStation
: ChargingStation
,
1320 ocppCommand
: IncomingRequestCommand
,
1323 if (connectorId
< 0) {
1325 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1332 public static convertDateToISOString
<T
extends JsonType
>(object
: T
): void {
1333 for (const key
in object
) {
1334 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1335 if (isDate(object
![key
])) {
1336 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1337 (object
![key
] as string) = (object
![key
] as Date).toISOString()
1338 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1339 } else if (typeof object
![key
] === 'object' && object
![key
] !== null) {
1340 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1341 OCPPServiceUtils
.convertDateToISOString
<T
>(object
![key
] as T
)
1346 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1347 if (chargingStation
.heartbeatSetInterval
== null) {
1348 chargingStation
.startHeartbeat()
1349 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1350 chargingStation
.restartHeartbeat()
1354 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1355 relativePath
: string,
1356 ocppVersion
: OCPPVersion
,
1357 moduleName
?: string,
1359 ): JSONSchemaType
<T
> {
1360 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1362 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1364 handleFileException(
1366 FileType
.JsonSchema
,
1367 error
as NodeJS
.ErrnoException
,
1368 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1369 { throwError
: false }
1371 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1372 return {} as JSONSchemaType
<T
>
1376 private static readonly logPrefix
= (
1377 ocppVersion
: OCPPVersion
,
1378 moduleName
?: string,
1382 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1383 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1384 : ` OCPP ${ocppVersion} |`
1385 return logPrefix(logMsg
)