1 import { readFileSync
} from
'node:fs'
2 import { dirname
, join
} from
'node:path'
3 import { fileURLToPath
} from
'node:url'
5 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv'
6 import { isDate
} from
'date-fns'
8 import { OCPP16Constants
} from
'./1.6/OCPP16Constants.js'
9 import { OCPP20Constants
} from
'./2.0/OCPP20Constants.js'
10 import { OCPPConstants
} from
'./OCPPConstants.js'
15 } from
'../../charging-station/index.js'
16 import { BaseError
, OCPPError
} from
'../../exception/index.js'
19 type AuthorizeRequest
,
20 type AuthorizeResponse
,
22 ChargingStationEvents
,
24 type ConnectorStatusEnum
,
28 IncomingRequestCommand
,
30 type MeasurandPerPhaseSampledValueTemplates
,
40 type OCPP16ChargePointStatus
,
41 type OCPP16StatusNotificationRequest
,
42 type OCPP20ConnectorStatusEnumType
,
43 type OCPP20StatusNotificationRequest
,
47 type SampledValueTemplate
,
48 StandardParametersKey
,
49 type StatusNotificationRequest
,
50 type StatusNotificationResponse
51 } from
'../../types/index.js'
58 getRandomFloatFluctuatedRounded
,
59 getRandomFloatRounded
,
69 } from
'../../utils/index.js'
71 export const getMessageTypeString
= (messageType
: MessageType
): string => {
72 switch (messageType
) {
73 case MessageType
.CALL_MESSAGE
:
75 case MessageType
.CALL_RESULT_MESSAGE
:
77 case MessageType
.CALL_ERROR_MESSAGE
:
84 export const buildStatusNotificationRequest
= (
85 chargingStation
: ChargingStation
,
87 status: ConnectorStatusEnum
,
89 ): StatusNotificationRequest
=> {
90 switch (chargingStation
.stationInfo
?.ocppVersion
) {
91 case OCPPVersion
.VERSION_16
:
94 status: status as OCPP16ChargePointStatus
,
95 errorCode
: ChargePointErrorCode
.NO_ERROR
96 } satisfies OCPP16StatusNotificationRequest
97 case OCPPVersion
.VERSION_20
:
98 case OCPPVersion
.VERSION_201
:
100 timestamp
: new Date(),
101 connectorStatus
: status as OCPP20ConnectorStatusEnumType
,
103 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
105 } satisfies OCPP20StatusNotificationRequest
107 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
111 export const isIdTagAuthorized
= async (
112 chargingStation
: ChargingStation
,
115 ): Promise
<boolean> => {
117 !chargingStation
.getLocalAuthListEnabled() &&
118 chargingStation
.stationInfo
?.remoteAuthorization
=== false
121 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
124 if (chargingStation
.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation
, idTag
)) {
125 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
126 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
127 connectorStatus
.localAuthorizeIdTag
= idTag
128 connectorStatus
.idTagLocalAuthorized
= true
130 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
131 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
136 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
138 chargingStation
.hasIdTags() &&
140 chargingStation
.idTagsCache
141 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
142 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
143 ?.find((tag
) => tag
=== idTag
)
148 const isIdTagRemoteAuthorized
= async (
149 chargingStation
: ChargingStation
,
152 ): Promise
<boolean> => {
153 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
157 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
159 RequestCommand
.AUTHORIZE
,
164 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
168 export const sendAndSetConnectorStatus
= async (
169 chargingStation
: ChargingStation
,
171 status: ConnectorStatusEnum
,
173 options
?: { send
: boolean }
174 ): Promise
<void> => {
175 options
= { send
: true, ...options
}
177 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
178 await chargingStation
.ocppRequestService
.requestHandler
<
179 StatusNotificationRequest
,
180 StatusNotificationResponse
183 RequestCommand
.STATUS_NOTIFICATION
,
184 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
187 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
188 chargingStation
.getConnectorStatus(connectorId
)!.status = status
189 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
191 ...chargingStation
.getConnectorStatus(connectorId
)
195 const checkConnectorStatusTransition
= (
196 chargingStation
: ChargingStation
,
198 status: ConnectorStatusEnum
200 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
201 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status
202 let transitionAllowed
= false
203 switch (chargingStation
.stationInfo
?.ocppVersion
) {
204 case OCPPVersion
.VERSION_16
:
206 (connectorId
=== 0 &&
207 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
208 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
211 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
212 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
215 transitionAllowed
= true
218 case OCPPVersion
.VERSION_20
:
219 case OCPPVersion
.VERSION_201
:
221 (connectorId
=== 0 &&
222 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
223 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
226 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
227 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
230 transitionAllowed
= true
235 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
238 if (!transitionAllowed
) {
240 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
241 ?.ocppVersion} connector id ${connectorId} status transition from '${
242 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
243 chargingStation.getConnectorStatus(connectorId)!.status
244 }' to '${status}' is not allowed`
247 return transitionAllowed
250 export const buildMeterValue
= (
251 chargingStation
: ChargingStation
,
253 transactionId
: number,
257 const connector
= chargingStation
.getConnectorStatus(connectorId
)
258 let meterValue
: MeterValue
259 let socSampledValueTemplate
: SampledValueTemplate
| undefined
260 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
261 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
262 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
263 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
264 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
265 let energySampledValueTemplate
: SampledValueTemplate
| undefined
266 switch (chargingStation
.stationInfo
?.ocppVersion
) {
267 case OCPPVersion
.VERSION_16
:
269 timestamp
: new Date(),
273 socSampledValueTemplate
= getSampledValueTemplate(
276 MeterValueMeasurand
.STATE_OF_CHARGE
278 if (socSampledValueTemplate
!= null) {
279 const socMaximumValue
= 100
280 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
281 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
282 ? getRandomFloatFluctuatedRounded(
283 parseInt(socSampledValueTemplate
.value
),
284 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
286 : getRandomInteger(socMaximumValue
, socMinimumValue
)
287 meterValue
.sampledValue
.push(
288 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
290 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
292 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
293 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
297 `${chargingStation.logPrefix()} MeterValues measurand ${
298 meterValue.sampledValue[sampledValuesIndex].measurand ??
299 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
300 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
301 meterValue.sampledValue[sampledValuesIndex].value
302 }/${socMaximumValue}`
307 voltageSampledValueTemplate
= getSampledValueTemplate(
310 MeterValueMeasurand
.VOLTAGE
312 if (voltageSampledValueTemplate
!= null) {
313 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
314 ? parseInt(voltageSampledValueTemplate
.value
)
315 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
316 chargingStation
.stationInfo
.voltageOut
!
317 const fluctuationPercent
=
318 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
319 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
320 voltageSampledValueTemplateValue
,
324 chargingStation
.getNumberOfPhases() !== 3 ||
325 (chargingStation
.getNumberOfPhases() === 3 &&
326 chargingStation
.stationInfo
?.mainVoltageMeterValues
=== true)
328 meterValue
.sampledValue
.push(
329 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
334 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
337 const phaseLineToNeutralValue
= `L${phase}-N`
338 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
341 MeterValueMeasurand
.VOLTAGE
,
342 phaseLineToNeutralValue
as MeterValuePhase
344 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
345 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
346 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
347 voltagePhaseLineToNeutralSampledValueTemplate
.value
349 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
350 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
351 chargingStation
.stationInfo
.voltageOut
!
352 const fluctuationPhaseToNeutralPercent
=
353 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
354 Constants
.DEFAULT_FLUCTUATION_PERCENT
355 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
356 voltagePhaseLineToNeutralSampledValueTemplateValue
,
357 fluctuationPhaseToNeutralPercent
360 meterValue
.sampledValue
.push(
362 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
363 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
365 phaseLineToNeutralValue
as MeterValuePhase
368 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
=== true) {
369 const phaseLineToLineValue
= `L${phase}-L${
370 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
371 ? (phase + 1) % chargingStation.getNumberOfPhases()
372 : chargingStation.getNumberOfPhases()
374 const voltagePhaseLineToLineValueRounded
= roundTo(
375 Math.sqrt(chargingStation
.getNumberOfPhases()) *
376 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
377 chargingStation
.stationInfo
.voltageOut
!,
380 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
383 MeterValueMeasurand
.VOLTAGE
,
384 phaseLineToLineValue
as MeterValuePhase
386 let voltagePhaseLineToLineMeasurandValue
: number | undefined
387 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
388 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
389 voltagePhaseLineToLineSampledValueTemplate
.value
391 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
392 : voltagePhaseLineToLineValueRounded
393 const fluctuationPhaseLineToLinePercent
=
394 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
395 Constants
.DEFAULT_FLUCTUATION_PERCENT
396 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
397 voltagePhaseLineToLineSampledValueTemplateValue
,
398 fluctuationPhaseLineToLinePercent
401 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
402 voltagePhaseLineToLineValueRounded
,
405 meterValue
.sampledValue
.push(
407 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
408 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
410 phaseLineToLineValue
as MeterValuePhase
416 // Power.Active.Import measurand
417 powerSampledValueTemplate
= getSampledValueTemplate(
420 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
422 if (chargingStation
.getNumberOfPhases() === 3) {
423 powerPerPhaseSampledValueTemplates
= {
424 L1
: getSampledValueTemplate(
427 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
430 L2
: getSampledValueTemplate(
433 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
436 L3
: getSampledValueTemplate(
439 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
444 if (powerSampledValueTemplate
!= null) {
445 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
934 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!)
936 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
937 const connectorMaximumAvailablePower
=
938 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
939 const connectorMaximumEnergyRounded
= roundTo(
940 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
943 const connectorMinimumEnergyRounded
= roundTo(
944 energySampledValueTemplate
.minimumValue
?? 0,
947 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
948 ? getRandomFloatFluctuatedRounded(
949 getLimitFromSampledValueTemplateCustomValue(
950 energySampledValueTemplate
.value
,
951 connectorMaximumEnergyRounded
,
952 connectorMinimumEnergyRounded
,
954 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
955 fallbackValue
: connectorMinimumEnergyRounded
,
956 unitMultiplier
: unitDivider
959 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
961 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
962 // Persist previous value on connector
963 if (connector
!= null) {
965 connector
.energyActiveImportRegisterValue
!= null &&
966 connector
.energyActiveImportRegisterValue
>= 0 &&
967 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
968 connector
.transactionEnergyActiveImportRegisterValue
>= 0
970 connector
.energyActiveImportRegisterValue
+= energyValueRounded
971 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
973 connector
.energyActiveImportRegisterValue
= 0
974 connector
.transactionEnergyActiveImportRegisterValue
= 0
977 meterValue
.sampledValue
.push(
979 energySampledValueTemplate
,
981 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
987 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
989 energyValueRounded
> connectorMaximumEnergyRounded
||
990 energyValueRounded
< connectorMinimumEnergyRounded
||
994 `${chargingStation.logPrefix()} MeterValues measurand ${
995 meterValue.sampledValue[sampledValuesIndex].measurand ??
996 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
997 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1002 case OCPPVersion
.VERSION_20
:
1003 case OCPPVersion
.VERSION_201
:
1005 throw new BaseError(
1006 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1011 export const buildTransactionEndMeterValue
= (
1012 chargingStation
: ChargingStation
,
1013 connectorId
: number,
1016 let meterValue
: MeterValue
1017 let sampledValueTemplate
: SampledValueTemplate
| undefined
1018 let unitDivider
: number
1019 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1020 case OCPPVersion
.VERSION_16
:
1022 timestamp
: new Date(),
1025 // Energy.Active.Import.Register measurand (default)
1026 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1027 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1028 meterValue
.sampledValue
.push(
1030 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1031 sampledValueTemplate
!,
1032 roundTo((meterStop
?? 0) / unitDivider
, 4),
1033 MeterValueContext
.TRANSACTION_END
1037 case OCPPVersion
.VERSION_20
:
1038 case OCPPVersion
.VERSION_201
:
1040 throw new BaseError(
1041 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1046 const checkMeasurandPowerDivider
= (
1047 chargingStation
: ChargingStation
,
1048 measurandType
: MeterValueMeasurand
1050 if (chargingStation
.powerDivider
== null) {
1051 const errMsg
= `MeterValues measurand ${
1052 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1053 }: powerDivider is undefined`
1054 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1055 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1056 } else if (chargingStation
?.powerDivider
<= 0) {
1057 const errMsg
= `MeterValues measurand ${
1058 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1059 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1060 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1061 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1065 const getLimitFromSampledValueTemplateCustomValue
= (
1066 value
: string | undefined,
1069 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1073 limitationEnabled
: false,
1079 const parsedValue
= parseInt(value
?? '')
1080 if (options
?.limitationEnabled
=== true) {
1082 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1083 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1087 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1088 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1091 const getSampledValueTemplate
= (
1092 chargingStation
: ChargingStation
,
1093 connectorId
: number,
1094 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1095 phase
?: MeterValuePhase
1096 ): SampledValueTemplate
| undefined => {
1097 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1098 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1100 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1105 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1106 getConfigurationKey(
1108 StandardParametersKey
.MeterValuesSampledData
1109 )?.value
?.includes(measurand
) === false
1112 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1113 StandardParametersKey.MeterValuesSampledData
1118 const sampledValueTemplates
: SampledValueTemplate
[] =
1119 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1120 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1123 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1127 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1128 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1132 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1136 sampledValueTemplates
[index
]?.phase
=== phase
&&
1137 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1138 getConfigurationKey(
1140 StandardParametersKey
.MeterValuesSampledData
1141 )?.value
?.includes(measurand
) === true
1143 return sampledValueTemplates
[index
]
1146 sampledValueTemplates
[index
]?.phase
== null &&
1147 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1148 getConfigurationKey(
1150 StandardParametersKey
.MeterValuesSampledData
1151 )?.value
?.includes(measurand
) === true
1153 return sampledValueTemplates
[index
]
1155 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1156 (sampledValueTemplates
[index
]?.measurand
== null ||
1157 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1159 return sampledValueTemplates
[index
]
1162 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1163 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1164 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1165 throw new BaseError(errorMsg
)
1168 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1172 const buildSampledValue
= (
1173 sampledValueTemplate
: SampledValueTemplate
,
1175 context
?: MeterValueContext
,
1176 phase
?: MeterValuePhase
1177 ): SampledValue
=> {
1178 const sampledValueContext
= context
?? sampledValueTemplate
?.context
1179 const sampledValueLocation
=
1180 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1181 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1182 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
1183 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
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
!= null && { value
: value
.toString() }),
1194 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
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
1341 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
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
)