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 export 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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
447 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
448 const errMsg
= `MeterValues measurand ${
449 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
450 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
451 chargingStation.templateFile
452 }, cannot calculate ${
453 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
455 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
456 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
457 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
458 const connectorMaximumAvailablePower
=
459 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
460 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
461 const connectorMaximumPowerPerPhase
= Math.round(
462 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
464 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
465 const connectorMinimumPowerPerPhase
= Math.round(
466 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
468 switch (chargingStation
.stationInfo
.currentOutType
) {
470 if (chargingStation
.getNumberOfPhases() === 3) {
471 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
472 powerSampledValueTemplate
.value
474 ? getRandomFloatFluctuatedRounded(
475 getLimitFromSampledValueTemplateCustomValue(
476 powerSampledValueTemplate
.value
,
477 connectorMaximumPower
/ unitDivider
,
478 connectorMinimumPower
/ unitDivider
,
481 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
482 fallbackValue
: connectorMinimumPower
/ unitDivider
484 ) / chargingStation
.getNumberOfPhases(),
485 powerSampledValueTemplate
.fluctuationPercent
??
486 Constants
.DEFAULT_FLUCTUATION_PERCENT
489 const phase1FluctuatedValue
= isNotEmptyString(
490 powerPerPhaseSampledValueTemplates
.L1
?.value
492 ? getRandomFloatFluctuatedRounded(
493 getLimitFromSampledValueTemplateCustomValue(
494 powerPerPhaseSampledValueTemplates
.L1
?.value
,
495 connectorMaximumPowerPerPhase
/ unitDivider
,
496 connectorMinimumPowerPerPhase
/ unitDivider
,
499 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
500 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
503 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
504 Constants
.DEFAULT_FLUCTUATION_PERCENT
507 const phase2FluctuatedValue
= isNotEmptyString(
508 powerPerPhaseSampledValueTemplates
.L2
?.value
510 ? getRandomFloatFluctuatedRounded(
511 getLimitFromSampledValueTemplateCustomValue(
512 powerPerPhaseSampledValueTemplates
.L2
?.value
,
513 connectorMaximumPowerPerPhase
/ unitDivider
,
514 connectorMinimumPowerPerPhase
/ unitDivider
,
517 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
518 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
521 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
522 Constants
.DEFAULT_FLUCTUATION_PERCENT
525 const phase3FluctuatedValue
= isNotEmptyString(
526 powerPerPhaseSampledValueTemplates
.L3
?.value
528 ? getRandomFloatFluctuatedRounded(
529 getLimitFromSampledValueTemplateCustomValue(
530 powerPerPhaseSampledValueTemplates
.L3
?.value
,
531 connectorMaximumPowerPerPhase
/ unitDivider
,
532 connectorMinimumPowerPerPhase
/ unitDivider
,
535 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
536 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
539 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
540 Constants
.DEFAULT_FLUCTUATION_PERCENT
543 powerMeasurandValues
.L1
=
544 phase1FluctuatedValue
??
545 defaultFluctuatedPowerPerPhase
??
546 getRandomFloatRounded(
547 connectorMaximumPowerPerPhase
/ unitDivider
,
548 connectorMinimumPowerPerPhase
/ unitDivider
550 powerMeasurandValues
.L2
=
551 phase2FluctuatedValue
??
552 defaultFluctuatedPowerPerPhase
??
553 getRandomFloatRounded(
554 connectorMaximumPowerPerPhase
/ unitDivider
,
555 connectorMinimumPowerPerPhase
/ unitDivider
557 powerMeasurandValues
.L3
=
558 phase3FluctuatedValue
??
559 defaultFluctuatedPowerPerPhase
??
560 getRandomFloatRounded(
561 connectorMaximumPowerPerPhase
/ unitDivider
,
562 connectorMinimumPowerPerPhase
/ unitDivider
565 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
566 ? getRandomFloatFluctuatedRounded(
567 getLimitFromSampledValueTemplateCustomValue(
568 powerSampledValueTemplate
.value
,
569 connectorMaximumPower
/ unitDivider
,
570 connectorMinimumPower
/ unitDivider
,
573 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
574 fallbackValue
: connectorMinimumPower
/ unitDivider
577 powerSampledValueTemplate
.fluctuationPercent
??
578 Constants
.DEFAULT_FLUCTUATION_PERCENT
580 : getRandomFloatRounded(
581 connectorMaximumPower
/ unitDivider
,
582 connectorMinimumPower
/ unitDivider
584 powerMeasurandValues
.L2
= 0
585 powerMeasurandValues
.L3
= 0
587 powerMeasurandValues
.allPhases
= roundTo(
588 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
593 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
594 ? getRandomFloatFluctuatedRounded(
595 getLimitFromSampledValueTemplateCustomValue(
596 powerSampledValueTemplate
.value
,
597 connectorMaximumPower
/ unitDivider
,
598 connectorMinimumPower
/ unitDivider
,
601 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
602 fallbackValue
: connectorMinimumPower
/ unitDivider
605 powerSampledValueTemplate
.fluctuationPercent
??
606 Constants
.DEFAULT_FLUCTUATION_PERCENT
608 : getRandomFloatRounded(
609 connectorMaximumPower
/ unitDivider
,
610 connectorMinimumPower
/ unitDivider
614 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
615 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
617 meterValue
.sampledValue
.push(
618 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
620 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
621 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
622 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
624 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
625 connectorMaximumPowerRounded
||
626 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
627 connectorMinimumPowerRounded
||
631 `${chargingStation.logPrefix()} MeterValues measurand ${
632 meterValue.sampledValue[sampledValuesIndex].measurand ??
633 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
634 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
635 meterValue.sampledValue[sampledValuesIndex].value
636 }/${connectorMaximumPowerRounded}`
641 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
644 const phaseValue
= `L${phase}-N`
645 meterValue
.sampledValue
.push(
647 powerPerPhaseSampledValueTemplates
[
648 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
649 ] ?? powerSampledValueTemplate
,
650 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
652 phaseValue
as MeterValuePhase
655 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
656 const connectorMaximumPowerPerPhaseRounded
= roundTo(
657 connectorMaximumPowerPerPhase
/ unitDivider
,
660 const connectorMinimumPowerPerPhaseRounded
= roundTo(
661 connectorMinimumPowerPerPhase
/ unitDivider
,
665 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
666 connectorMaximumPowerPerPhaseRounded
||
667 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
668 connectorMinimumPowerPerPhaseRounded
||
672 `${chargingStation.logPrefix()} MeterValues measurand ${
673 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
674 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
676 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
677 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
678 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
679 }/${connectorMaximumPowerPerPhaseRounded}`
684 // Current.Import measurand
685 currentSampledValueTemplate
= getSampledValueTemplate(
688 MeterValueMeasurand
.CURRENT_IMPORT
690 if (chargingStation
.getNumberOfPhases() === 3) {
691 currentPerPhaseSampledValueTemplates
= {
692 L1
: getSampledValueTemplate(
695 MeterValueMeasurand
.CURRENT_IMPORT
,
698 L2
: getSampledValueTemplate(
701 MeterValueMeasurand
.CURRENT_IMPORT
,
704 L3
: getSampledValueTemplate(
707 MeterValueMeasurand
.CURRENT_IMPORT
,
712 if (currentSampledValueTemplate
!= null) {
713 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
714 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
715 const errMsg
= `MeterValues measurand ${
716 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
717 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
718 chargingStation.templateFile
719 }, cannot calculate ${
720 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
722 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
723 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
724 const connectorMaximumAvailablePower
=
725 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
726 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
727 let connectorMaximumAmperage
: number
728 switch (chargingStation
.stationInfo
.currentOutType
) {
730 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
731 chargingStation
.getNumberOfPhases(),
732 connectorMaximumAvailablePower
,
733 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
734 chargingStation
.stationInfo
.voltageOut
!
736 if (chargingStation
.getNumberOfPhases() === 3) {
737 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
738 currentSampledValueTemplate
.value
740 ? getRandomFloatFluctuatedRounded(
741 getLimitFromSampledValueTemplateCustomValue(
742 currentSampledValueTemplate
.value
,
743 connectorMaximumAmperage
,
744 connectorMinimumAmperage
,
747 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
748 fallbackValue
: connectorMinimumAmperage
751 currentSampledValueTemplate
.fluctuationPercent
??
752 Constants
.DEFAULT_FLUCTUATION_PERCENT
755 const phase1FluctuatedValue
= isNotEmptyString(
756 currentPerPhaseSampledValueTemplates
.L1
?.value
758 ? getRandomFloatFluctuatedRounded(
759 getLimitFromSampledValueTemplateCustomValue(
760 currentPerPhaseSampledValueTemplates
.L1
?.value
,
761 connectorMaximumAmperage
,
762 connectorMinimumAmperage
,
765 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
766 fallbackValue
: connectorMinimumAmperage
769 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
770 Constants
.DEFAULT_FLUCTUATION_PERCENT
773 const phase2FluctuatedValue
= isNotEmptyString(
774 currentPerPhaseSampledValueTemplates
.L2
?.value
776 ? getRandomFloatFluctuatedRounded(
777 getLimitFromSampledValueTemplateCustomValue(
778 currentPerPhaseSampledValueTemplates
.L2
?.value
,
779 connectorMaximumAmperage
,
780 connectorMinimumAmperage
,
783 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
784 fallbackValue
: connectorMinimumAmperage
787 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
788 Constants
.DEFAULT_FLUCTUATION_PERCENT
791 const phase3FluctuatedValue
= isNotEmptyString(
792 currentPerPhaseSampledValueTemplates
.L3
?.value
794 ? getRandomFloatFluctuatedRounded(
795 getLimitFromSampledValueTemplateCustomValue(
796 currentPerPhaseSampledValueTemplates
.L3
?.value
,
797 connectorMaximumAmperage
,
798 connectorMinimumAmperage
,
801 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
802 fallbackValue
: connectorMinimumAmperage
805 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
806 Constants
.DEFAULT_FLUCTUATION_PERCENT
809 currentMeasurandValues
.L1
=
810 phase1FluctuatedValue
??
811 defaultFluctuatedAmperagePerPhase
??
812 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
813 currentMeasurandValues
.L2
=
814 phase2FluctuatedValue
??
815 defaultFluctuatedAmperagePerPhase
??
816 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
817 currentMeasurandValues
.L3
=
818 phase3FluctuatedValue
??
819 defaultFluctuatedAmperagePerPhase
??
820 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
822 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
823 ? getRandomFloatFluctuatedRounded(
824 getLimitFromSampledValueTemplateCustomValue(
825 currentSampledValueTemplate
.value
,
826 connectorMaximumAmperage
,
827 connectorMinimumAmperage
,
830 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
831 fallbackValue
: connectorMinimumAmperage
834 currentSampledValueTemplate
.fluctuationPercent
??
835 Constants
.DEFAULT_FLUCTUATION_PERCENT
837 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
838 currentMeasurandValues
.L2
= 0
839 currentMeasurandValues
.L3
= 0
841 currentMeasurandValues
.allPhases
= roundTo(
842 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
843 chargingStation
.getNumberOfPhases(),
848 connectorMaximumAmperage
= DCElectricUtils
.amperage(
849 connectorMaximumAvailablePower
,
850 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
851 chargingStation
.stationInfo
.voltageOut
!
853 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
854 ? getRandomFloatFluctuatedRounded(
855 getLimitFromSampledValueTemplateCustomValue(
856 currentSampledValueTemplate
.value
,
857 connectorMaximumAmperage
,
858 connectorMinimumAmperage
,
861 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
862 fallbackValue
: connectorMinimumAmperage
865 currentSampledValueTemplate
.fluctuationPercent
??
866 Constants
.DEFAULT_FLUCTUATION_PERCENT
868 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
871 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
872 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
874 meterValue
.sampledValue
.push(
875 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
877 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
879 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
880 connectorMaximumAmperage
||
881 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
882 connectorMinimumAmperage
||
886 `${chargingStation.logPrefix()} MeterValues measurand ${
887 meterValue.sampledValue[sampledValuesIndex].measurand ??
888 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
889 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
890 meterValue.sampledValue[sampledValuesIndex].value
891 }/${connectorMaximumAmperage}`
896 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
899 const phaseValue
= `L${phase}`
900 meterValue
.sampledValue
.push(
902 currentPerPhaseSampledValueTemplates
[
903 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
904 ] ?? currentSampledValueTemplate
,
905 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
907 phaseValue
as MeterValuePhase
910 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
912 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
913 connectorMaximumAmperage
||
914 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
915 connectorMinimumAmperage
||
919 `${chargingStation.logPrefix()} MeterValues measurand ${
920 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
921 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
923 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
924 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
925 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
926 }/${connectorMaximumAmperage}`
931 // Energy.Active.Import.Register measurand (default)
932 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
933 if (energySampledValueTemplate
!= null) {
934 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
935 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
937 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
938 const connectorMaximumAvailablePower
=
939 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
940 const connectorMaximumEnergyRounded
= roundTo(
941 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
944 const connectorMinimumEnergyRounded
= roundTo(
945 energySampledValueTemplate
.minimumValue
?? 0,
948 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
949 ? getRandomFloatFluctuatedRounded(
950 getLimitFromSampledValueTemplateCustomValue(
951 energySampledValueTemplate
.value
,
952 connectorMaximumEnergyRounded
,
953 connectorMinimumEnergyRounded
,
955 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
956 fallbackValue
: connectorMinimumEnergyRounded
,
957 unitMultiplier
: unitDivider
960 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
962 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
963 // Persist previous value on connector
964 if (connector
!= null) {
966 connector
.energyActiveImportRegisterValue
!= null &&
967 connector
.energyActiveImportRegisterValue
>= 0 &&
968 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
969 connector
.transactionEnergyActiveImportRegisterValue
>= 0
971 connector
.energyActiveImportRegisterValue
+= energyValueRounded
972 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
974 connector
.energyActiveImportRegisterValue
= 0
975 connector
.transactionEnergyActiveImportRegisterValue
= 0
978 meterValue
.sampledValue
.push(
980 energySampledValueTemplate
,
982 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
988 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
990 energyValueRounded
> connectorMaximumEnergyRounded
||
991 energyValueRounded
< connectorMinimumEnergyRounded
||
995 `${chargingStation.logPrefix()} MeterValues measurand ${
996 meterValue.sampledValue[sampledValuesIndex].measurand ??
997 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
998 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1003 case OCPPVersion
.VERSION_20
:
1004 case OCPPVersion
.VERSION_201
:
1006 throw new BaseError(
1007 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1012 export const buildTransactionEndMeterValue
= (
1013 chargingStation
: ChargingStation
,
1014 connectorId
: number,
1015 meterStop
: number | undefined
1017 let meterValue
: MeterValue
1018 let sampledValueTemplate
: SampledValueTemplate
| undefined
1019 let unitDivider
: number
1020 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1021 case OCPPVersion
.VERSION_16
:
1023 timestamp
: new Date(),
1026 // Energy.Active.Import.Register measurand (default)
1027 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1028 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1029 meterValue
.sampledValue
.push(
1031 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1032 sampledValueTemplate
!,
1033 roundTo((meterStop
?? 0) / unitDivider
, 4),
1034 MeterValueContext
.TRANSACTION_END
1038 case OCPPVersion
.VERSION_20
:
1039 case OCPPVersion
.VERSION_201
:
1041 throw new BaseError(
1042 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1047 const checkMeasurandPowerDivider
= (
1048 chargingStation
: ChargingStation
,
1049 measurandType
: MeterValueMeasurand
| undefined
1051 if (chargingStation
.powerDivider
== null) {
1052 const errMsg
= `MeterValues measurand ${
1053 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1054 }: powerDivider is undefined`
1055 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1056 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1057 } else if (chargingStation
.powerDivider
<= 0) {
1058 const errMsg
= `MeterValues measurand ${
1059 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1060 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1061 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1062 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1066 const getLimitFromSampledValueTemplateCustomValue
= (
1067 value
: string | undefined,
1070 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1074 limitationEnabled
: false,
1080 const parsedValue
= parseInt(value
?? '')
1081 if (options
.limitationEnabled
=== true) {
1083 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1084 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1088 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1089 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1092 const getSampledValueTemplate
= (
1093 chargingStation
: ChargingStation
,
1094 connectorId
: number,
1095 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1096 phase
?: MeterValuePhase
1097 ): SampledValueTemplate
| undefined => {
1098 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1099 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1101 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1106 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1107 getConfigurationKey(
1109 StandardParametersKey
.MeterValuesSampledData
1110 )?.value
?.includes(measurand
) === false
1113 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1114 StandardParametersKey.MeterValuesSampledData
1119 const sampledValueTemplates
=
1120 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1121 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1124 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1128 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1129 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1133 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1137 sampledValueTemplates
[index
]?.phase
=== phase
&&
1138 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1139 getConfigurationKey(
1141 StandardParametersKey
.MeterValuesSampledData
1142 )?.value
?.includes(measurand
) === true
1144 return sampledValueTemplates
[index
]
1147 sampledValueTemplates
[index
]?.phase
== null &&
1148 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1149 getConfigurationKey(
1151 StandardParametersKey
.MeterValuesSampledData
1152 )?.value
?.includes(measurand
) === true
1154 return sampledValueTemplates
[index
]
1156 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1157 (sampledValueTemplates
[index
]?.measurand
== null ||
1158 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1160 return sampledValueTemplates
[index
]
1163 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1164 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1165 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1166 throw new BaseError(errorMsg
)
1169 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1173 const buildSampledValue
= (
1174 sampledValueTemplate
: SampledValueTemplate
,
1176 context
?: MeterValueContext
,
1177 phase
?: MeterValuePhase
1178 ): SampledValue
=> {
1179 const sampledValueContext
= context
?? sampledValueTemplate
.context
1180 const sampledValueLocation
=
1181 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1182 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1183 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1185 ...(sampledValueTemplate
.unit
!= null && {
1186 unit
: sampledValueTemplate
.unit
1188 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1189 ...(sampledValueTemplate
.measurand
!= null && {
1190 measurand
: sampledValueTemplate
.measurand
1192 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1193 ...{ value
: value
.toString() },
1194 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1195 } satisfies SampledValue
1198 const getMeasurandDefaultLocation
= (
1199 measurandType
: MeterValueMeasurand
1200 ): MeterValueLocation
| undefined => {
1201 switch (measurandType
) {
1202 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1203 return MeterValueLocation
.EV
1207 // const getMeasurandDefaultUnit = (
1208 // measurandType: MeterValueMeasurand
1209 // ): MeterValueUnit | undefined => {
1210 // switch (measurandType) {
1211 // case MeterValueMeasurand.CURRENT_EXPORT:
1212 // case MeterValueMeasurand.CURRENT_IMPORT:
1213 // case MeterValueMeasurand.CURRENT_OFFERED:
1214 // return MeterValueUnit.AMP
1215 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1216 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1217 // return MeterValueUnit.WATT_HOUR
1218 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1219 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1220 // case MeterValueMeasurand.POWER_OFFERED:
1221 // return MeterValueUnit.WATT
1222 // case MeterValueMeasurand.STATE_OF_CHARGE:
1223 // return MeterValueUnit.PERCENT
1224 // case MeterValueMeasurand.VOLTAGE:
1225 // return MeterValueUnit.VOLT
1229 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1230 export class OCPPServiceUtils
{
1231 public static getMessageTypeString
= getMessageTypeString
1232 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1233 public static isIdTagAuthorized
= isIdTagAuthorized
1234 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1235 protected static getSampledValueTemplate
= getSampledValueTemplate
1236 protected static buildSampledValue
= buildSampledValue
1238 protected constructor () {
1239 // This is intentional
1242 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | null | undefined): ErrorType
{
1243 if (isNotEmptyArray(errors
)) {
1244 for (const error
of errors
as DefinedError
[]) {
1245 switch (error
.keyword
) {
1247 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1248 case 'dependencies':
1250 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1253 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1257 return ErrorType
.FORMAT_VIOLATION
1260 public static isRequestCommandSupported (
1261 chargingStation
: ChargingStation
,
1262 command
: RequestCommand
1264 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1267 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1272 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1274 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1276 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1280 public static isIncomingRequestCommandSupported (
1281 chargingStation
: ChargingStation
,
1282 command
: IncomingRequestCommand
1284 const isIncomingRequestCommand
=
1285 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1287 isIncomingRequestCommand
&&
1288 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1292 isIncomingRequestCommand
&&
1293 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1295 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1297 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1301 public static isMessageTriggerSupported (
1302 chargingStation
: ChargingStation
,
1303 messageTrigger
: MessageTrigger
1305 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1306 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1310 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1312 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1315 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1320 public static isConnectorIdValid (
1321 chargingStation
: ChargingStation
,
1322 ocppCommand
: IncomingRequestCommand
,
1325 if (connectorId
< 0) {
1327 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1334 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1335 for (const key
in obj
) {
1336 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1337 if (isDate(obj
![key
])) {
1338 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1339 (obj
![key
] as string) = (obj
![key
] as Date).toISOString()
1340 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1341 } else if (typeof obj
![key
] === 'object' && obj
![key
] !== null) {
1342 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1343 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
)
1348 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1349 if (chargingStation
.heartbeatSetInterval
== null) {
1350 chargingStation
.startHeartbeat()
1351 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1352 chargingStation
.restartHeartbeat()
1356 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1357 relativePath
: string,
1358 ocppVersion
: OCPPVersion
,
1359 moduleName
?: string,
1361 ): JSONSchemaType
<T
> {
1362 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1364 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1366 handleFileException(
1368 FileType
.JsonSchema
,
1369 error
as NodeJS
.ErrnoException
,
1370 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1371 { throwError
: false }
1373 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1374 return {} as JSONSchemaType
<T
>
1378 private static readonly logPrefix
= (
1379 ocppVersion
: OCPPVersion
,
1380 moduleName
?: string,
1384 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1385 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1386 : ` OCPP ${ocppVersion} |`
1387 return logPrefix(logMsg
)