1 import { readFileSync
} from
'node:fs'
2 import { dirname
, join
} from
'node:path'
3 import { fileURLToPath
} from
'node:url'
5 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv'
6 import { isDate
} from
'date-fns'
8 import { OCPP16Constants
} from
'./1.6/OCPP16Constants.js'
9 import { OCPP20Constants
} from
'./2.0/OCPP20Constants.js'
10 import { OCPPConstants
} from
'./OCPPConstants.js'
15 } from
'../../charging-station/index.js'
16 import { BaseError
, OCPPError
} from
'../../exception/index.js'
19 type AuthorizeRequest
,
20 type AuthorizeResponse
,
22 ChargingStationEvents
,
24 type ConnectorStatusEnum
,
28 IncomingRequestCommand
,
30 type MeasurandPerPhaseSampledValueTemplates
,
40 type OCPP16StatusNotificationRequest
,
41 type OCPP20StatusNotificationRequest
,
45 type SampledValueTemplate
,
46 StandardParametersKey
,
47 type StatusNotificationRequest
,
48 type StatusNotificationResponse
49 } from
'../../types/index.js'
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
67 } from
'../../utils/index.js'
69 export const getMessageTypeString
= (messageType
: MessageType
): string => {
70 switch (messageType
) {
71 case MessageType
.CALL_MESSAGE
:
73 case MessageType
.CALL_RESULT_MESSAGE
:
75 case MessageType
.CALL_ERROR_MESSAGE
:
82 export const buildStatusNotificationRequest
= (
83 chargingStation
: ChargingStation
,
85 status: ConnectorStatusEnum
,
87 ): StatusNotificationRequest
=> {
88 switch (chargingStation
.stationInfo
?.ocppVersion
) {
89 case OCPPVersion
.VERSION_16
:
93 errorCode
: ChargePointErrorCode
.NO_ERROR
94 } satisfies OCPP16StatusNotificationRequest
95 case OCPPVersion
.VERSION_20
:
96 case OCPPVersion
.VERSION_201
:
98 timestamp
: new Date(),
99 connectorStatus
: status,
102 } satisfies OCPP20StatusNotificationRequest
104 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
108 export const isIdTagAuthorized
= async (
109 chargingStation
: ChargingStation
,
112 ): Promise
<boolean> => {
114 !chargingStation
.getLocalAuthListEnabled() &&
115 chargingStation
.stationInfo
?.remoteAuthorization
=== false
118 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
121 if (chargingStation
.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation
, idTag
)) {
122 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
124 connectorStatus
.localAuthorizeIdTag
= idTag
125 connectorStatus
.idTagLocalAuthorized
= true
127 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
128 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
133 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
135 chargingStation
.hasIdTags() &&
137 chargingStation
.idTagsCache
138 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
139 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
140 ?.find((tag
) => tag
=== idTag
)
145 const isIdTagRemoteAuthorized
= async (
146 chargingStation
: ChargingStation
,
149 ): Promise
<boolean> => {
150 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
151 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
154 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
156 RequestCommand
.AUTHORIZE
,
161 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
165 export const sendAndSetConnectorStatus
= async (
166 chargingStation
: ChargingStation
,
168 status: ConnectorStatusEnum
,
170 options
?: { send
: boolean }
171 ): Promise
<void> => {
172 options
= { send
: true, ...options
}
174 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
175 await chargingStation
.ocppRequestService
.requestHandler
<
176 StatusNotificationRequest
,
177 StatusNotificationResponse
180 RequestCommand
.STATUS_NOTIFICATION
,
181 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
184 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
185 chargingStation
.getConnectorStatus(connectorId
)!.status = status
186 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
188 ...chargingStation
.getConnectorStatus(connectorId
)
192 const checkConnectorStatusTransition
= (
193 chargingStation
: ChargingStation
,
195 status: ConnectorStatusEnum
197 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
198 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status
199 let transitionAllowed
= false
200 switch (chargingStation
.stationInfo
?.ocppVersion
) {
201 case OCPPVersion
.VERSION_16
:
203 (connectorId
=== 0 &&
204 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
205 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
208 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
209 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
212 transitionAllowed
= true
215 case OCPPVersion
.VERSION_20
:
216 case OCPPVersion
.VERSION_201
:
218 (connectorId
=== 0 &&
219 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
220 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
223 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
224 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
227 transitionAllowed
= true
232 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
235 if (!transitionAllowed
) {
237 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
238 ?.ocppVersion} connector id ${connectorId} status transition from '${
239 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
240 chargingStation.getConnectorStatus(connectorId)!.status
241 }' to '${status}' is not allowed`
244 return transitionAllowed
247 export const buildMeterValue
= (
248 chargingStation
: ChargingStation
,
250 transactionId
: number,
254 const connector
= chargingStation
.getConnectorStatus(connectorId
)
255 let meterValue
: MeterValue
256 let socSampledValueTemplate
: SampledValueTemplate
| undefined
257 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
258 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
259 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
260 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
261 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
262 let energySampledValueTemplate
: SampledValueTemplate
| undefined
263 switch (chargingStation
.stationInfo
?.ocppVersion
) {
264 case OCPPVersion
.VERSION_16
:
266 timestamp
: new Date(),
270 socSampledValueTemplate
= getSampledValueTemplate(
273 MeterValueMeasurand
.STATE_OF_CHARGE
275 if (socSampledValueTemplate
!= null) {
276 const socMaximumValue
= 100
277 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
278 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
279 ? getRandomFloatFluctuatedRounded(
280 parseInt(socSampledValueTemplate
.value
),
281 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
283 : getRandomInteger(socMaximumValue
, socMinimumValue
)
284 meterValue
.sampledValue
.push(
285 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
287 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
289 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
290 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
294 `${chargingStation.logPrefix()} MeterValues measurand ${
295 meterValue.sampledValue[sampledValuesIndex].measurand ??
296 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
297 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
298 meterValue.sampledValue[sampledValuesIndex].value
299 }/${socMaximumValue}`
304 voltageSampledValueTemplate
= getSampledValueTemplate(
307 MeterValueMeasurand
.VOLTAGE
309 if (voltageSampledValueTemplate
!= null) {
310 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
311 ? parseInt(voltageSampledValueTemplate
.value
)
312 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
313 chargingStation
.stationInfo
.voltageOut
!
314 const fluctuationPercent
=
315 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
316 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
317 voltageSampledValueTemplateValue
,
321 chargingStation
.getNumberOfPhases() !== 3 ||
322 (chargingStation
.getNumberOfPhases() === 3 &&
323 chargingStation
.stationInfo
?.mainVoltageMeterValues
=== true)
325 meterValue
.sampledValue
.push(
326 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
331 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
334 const phaseLineToNeutralValue
= `L${phase}-N`
335 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
338 MeterValueMeasurand
.VOLTAGE
,
339 phaseLineToNeutralValue
as MeterValuePhase
341 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
342 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
343 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
344 voltagePhaseLineToNeutralSampledValueTemplate
.value
346 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
347 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
348 chargingStation
.stationInfo
.voltageOut
!
349 const fluctuationPhaseToNeutralPercent
=
350 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
351 Constants
.DEFAULT_FLUCTUATION_PERCENT
352 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
353 voltagePhaseLineToNeutralSampledValueTemplateValue
,
354 fluctuationPhaseToNeutralPercent
357 meterValue
.sampledValue
.push(
359 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
360 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
362 phaseLineToNeutralValue
as MeterValuePhase
365 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
=== true) {
366 const phaseLineToLineValue
= `L${phase}-L${
367 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
368 ? (phase + 1) % chargingStation.getNumberOfPhases()
369 : chargingStation.getNumberOfPhases()
371 const voltagePhaseLineToLineValueRounded
= roundTo(
372 Math.sqrt(chargingStation
.getNumberOfPhases()) *
373 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
374 chargingStation
.stationInfo
.voltageOut
!,
377 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
380 MeterValueMeasurand
.VOLTAGE
,
381 phaseLineToLineValue
as MeterValuePhase
383 let voltagePhaseLineToLineMeasurandValue
: number | undefined
384 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
385 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
386 voltagePhaseLineToLineSampledValueTemplate
.value
388 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
389 : voltagePhaseLineToLineValueRounded
390 const fluctuationPhaseLineToLinePercent
=
391 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
392 Constants
.DEFAULT_FLUCTUATION_PERCENT
393 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
394 voltagePhaseLineToLineSampledValueTemplateValue
,
395 fluctuationPhaseLineToLinePercent
398 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
399 voltagePhaseLineToLineValueRounded
,
402 meterValue
.sampledValue
.push(
404 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
405 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
407 phaseLineToLineValue
as MeterValuePhase
413 // Power.Active.Import measurand
414 powerSampledValueTemplate
= getSampledValueTemplate(
417 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
419 if (chargingStation
.getNumberOfPhases() === 3) {
420 powerPerPhaseSampledValueTemplates
= {
421 L1
: getSampledValueTemplate(
424 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
427 L2
: getSampledValueTemplate(
430 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
433 L3
: getSampledValueTemplate(
436 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
441 if (powerSampledValueTemplate
!= null) {
442 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
443 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
!)
444 const errMsg
= `MeterValues measurand ${
445 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
446 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
447 chargingStation.templateFile
448 }, cannot calculate ${
449 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
451 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
452 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
453 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
454 const connectorMaximumAvailablePower
=
455 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
456 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
457 const connectorMaximumPowerPerPhase
= Math.round(
458 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
460 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
461 const connectorMinimumPowerPerPhase
= Math.round(
462 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
464 switch (chargingStation
.stationInfo
?.currentOutType
) {
466 if (chargingStation
.getNumberOfPhases() === 3) {
467 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
468 powerSampledValueTemplate
.value
470 ? getRandomFloatFluctuatedRounded(
471 getLimitFromSampledValueTemplateCustomValue(
472 powerSampledValueTemplate
.value
,
473 connectorMaximumPower
/ unitDivider
,
474 connectorMinimumPower
/ unitDivider
,
477 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
478 fallbackValue
: connectorMinimumPower
/ unitDivider
480 ) / chargingStation
.getNumberOfPhases(),
481 powerSampledValueTemplate
.fluctuationPercent
??
482 Constants
.DEFAULT_FLUCTUATION_PERCENT
485 const phase1FluctuatedValue
= isNotEmptyString(
486 powerPerPhaseSampledValueTemplates
.L1
?.value
488 ? getRandomFloatFluctuatedRounded(
489 getLimitFromSampledValueTemplateCustomValue(
490 powerPerPhaseSampledValueTemplates
.L1
?.value
,
491 connectorMaximumPowerPerPhase
/ unitDivider
,
492 connectorMinimumPowerPerPhase
/ unitDivider
,
495 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
496 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
499 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
500 Constants
.DEFAULT_FLUCTUATION_PERCENT
503 const phase2FluctuatedValue
= isNotEmptyString(
504 powerPerPhaseSampledValueTemplates
.L2
?.value
506 ? getRandomFloatFluctuatedRounded(
507 getLimitFromSampledValueTemplateCustomValue(
508 powerPerPhaseSampledValueTemplates
.L2
?.value
,
509 connectorMaximumPowerPerPhase
/ unitDivider
,
510 connectorMinimumPowerPerPhase
/ unitDivider
,
513 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
514 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
517 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
518 Constants
.DEFAULT_FLUCTUATION_PERCENT
521 const phase3FluctuatedValue
= isNotEmptyString(
522 powerPerPhaseSampledValueTemplates
.L3
?.value
524 ? getRandomFloatFluctuatedRounded(
525 getLimitFromSampledValueTemplateCustomValue(
526 powerPerPhaseSampledValueTemplates
.L3
?.value
,
527 connectorMaximumPowerPerPhase
/ unitDivider
,
528 connectorMinimumPowerPerPhase
/ unitDivider
,
531 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
532 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
535 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
536 Constants
.DEFAULT_FLUCTUATION_PERCENT
539 powerMeasurandValues
.L1
=
540 phase1FluctuatedValue
??
541 defaultFluctuatedPowerPerPhase
??
542 getRandomFloatRounded(
543 connectorMaximumPowerPerPhase
/ unitDivider
,
544 connectorMinimumPowerPerPhase
/ unitDivider
546 powerMeasurandValues
.L2
=
547 phase2FluctuatedValue
??
548 defaultFluctuatedPowerPerPhase
??
549 getRandomFloatRounded(
550 connectorMaximumPowerPerPhase
/ unitDivider
,
551 connectorMinimumPowerPerPhase
/ unitDivider
553 powerMeasurandValues
.L3
=
554 phase3FluctuatedValue
??
555 defaultFluctuatedPowerPerPhase
??
556 getRandomFloatRounded(
557 connectorMaximumPowerPerPhase
/ unitDivider
,
558 connectorMinimumPowerPerPhase
/ unitDivider
561 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
562 ? getRandomFloatFluctuatedRounded(
563 getLimitFromSampledValueTemplateCustomValue(
564 powerSampledValueTemplate
.value
,
565 connectorMaximumPower
/ unitDivider
,
566 connectorMinimumPower
/ unitDivider
,
569 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
570 fallbackValue
: connectorMinimumPower
/ unitDivider
573 powerSampledValueTemplate
.fluctuationPercent
??
574 Constants
.DEFAULT_FLUCTUATION_PERCENT
576 : getRandomFloatRounded(
577 connectorMaximumPower
/ unitDivider
,
578 connectorMinimumPower
/ unitDivider
580 powerMeasurandValues
.L2
= 0
581 powerMeasurandValues
.L3
= 0
583 powerMeasurandValues
.allPhases
= roundTo(
584 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
589 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
590 ? getRandomFloatFluctuatedRounded(
591 getLimitFromSampledValueTemplateCustomValue(
592 powerSampledValueTemplate
.value
,
593 connectorMaximumPower
/ unitDivider
,
594 connectorMinimumPower
/ unitDivider
,
597 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
598 fallbackValue
: connectorMinimumPower
/ unitDivider
601 powerSampledValueTemplate
.fluctuationPercent
??
602 Constants
.DEFAULT_FLUCTUATION_PERCENT
604 : getRandomFloatRounded(
605 connectorMaximumPower
/ unitDivider
,
606 connectorMinimumPower
/ unitDivider
610 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
611 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
613 meterValue
.sampledValue
.push(
614 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
616 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
617 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
618 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
620 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
621 connectorMaximumPowerRounded
||
622 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
623 connectorMinimumPowerRounded
||
627 `${chargingStation.logPrefix()} MeterValues measurand ${
628 meterValue.sampledValue[sampledValuesIndex].measurand ??
629 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
630 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
631 meterValue.sampledValue[sampledValuesIndex].value
632 }/${connectorMaximumPowerRounded}`
637 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
640 const phaseValue
= `L${phase}-N`
641 meterValue
.sampledValue
.push(
643 powerPerPhaseSampledValueTemplates
[
644 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
645 ] ?? powerSampledValueTemplate
,
646 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
648 phaseValue
as MeterValuePhase
651 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
652 const connectorMaximumPowerPerPhaseRounded
= roundTo(
653 connectorMaximumPowerPerPhase
/ unitDivider
,
656 const connectorMinimumPowerPerPhaseRounded
= roundTo(
657 connectorMinimumPowerPerPhase
/ unitDivider
,
661 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
662 connectorMaximumPowerPerPhaseRounded
||
663 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
664 connectorMinimumPowerPerPhaseRounded
||
668 `${chargingStation.logPrefix()} MeterValues measurand ${
669 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
670 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
672 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
673 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
674 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
675 }/${connectorMaximumPowerPerPhaseRounded}`
680 // Current.Import measurand
681 currentSampledValueTemplate
= getSampledValueTemplate(
684 MeterValueMeasurand
.CURRENT_IMPORT
686 if (chargingStation
.getNumberOfPhases() === 3) {
687 currentPerPhaseSampledValueTemplates
= {
688 L1
: getSampledValueTemplate(
691 MeterValueMeasurand
.CURRENT_IMPORT
,
694 L2
: getSampledValueTemplate(
697 MeterValueMeasurand
.CURRENT_IMPORT
,
700 L3
: getSampledValueTemplate(
703 MeterValueMeasurand
.CURRENT_IMPORT
,
708 if (currentSampledValueTemplate
!= null) {
709 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
710 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
!)
711 const errMsg
= `MeterValues measurand ${
712 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
713 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
714 chargingStation.templateFile
715 }, cannot calculate ${
716 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
718 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
719 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
720 const connectorMaximumAvailablePower
=
721 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
722 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
723 let connectorMaximumAmperage
: number
724 switch (chargingStation
.stationInfo
?.currentOutType
) {
726 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
727 chargingStation
.getNumberOfPhases(),
728 connectorMaximumAvailablePower
,
729 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
730 chargingStation
.stationInfo
.voltageOut
!
732 if (chargingStation
.getNumberOfPhases() === 3) {
733 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
734 currentSampledValueTemplate
.value
736 ? getRandomFloatFluctuatedRounded(
737 getLimitFromSampledValueTemplateCustomValue(
738 currentSampledValueTemplate
.value
,
739 connectorMaximumAmperage
,
740 connectorMinimumAmperage
,
743 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
744 fallbackValue
: connectorMinimumAmperage
747 currentSampledValueTemplate
.fluctuationPercent
??
748 Constants
.DEFAULT_FLUCTUATION_PERCENT
751 const phase1FluctuatedValue
= isNotEmptyString(
752 currentPerPhaseSampledValueTemplates
.L1
?.value
754 ? getRandomFloatFluctuatedRounded(
755 getLimitFromSampledValueTemplateCustomValue(
756 currentPerPhaseSampledValueTemplates
.L1
?.value
,
757 connectorMaximumAmperage
,
758 connectorMinimumAmperage
,
761 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
762 fallbackValue
: connectorMinimumAmperage
765 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
766 Constants
.DEFAULT_FLUCTUATION_PERCENT
769 const phase2FluctuatedValue
= isNotEmptyString(
770 currentPerPhaseSampledValueTemplates
.L2
?.value
772 ? getRandomFloatFluctuatedRounded(
773 getLimitFromSampledValueTemplateCustomValue(
774 currentPerPhaseSampledValueTemplates
.L2
?.value
,
775 connectorMaximumAmperage
,
776 connectorMinimumAmperage
,
779 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
780 fallbackValue
: connectorMinimumAmperage
783 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
784 Constants
.DEFAULT_FLUCTUATION_PERCENT
787 const phase3FluctuatedValue
= isNotEmptyString(
788 currentPerPhaseSampledValueTemplates
.L3
?.value
790 ? getRandomFloatFluctuatedRounded(
791 getLimitFromSampledValueTemplateCustomValue(
792 currentPerPhaseSampledValueTemplates
.L3
?.value
,
793 connectorMaximumAmperage
,
794 connectorMinimumAmperage
,
797 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
798 fallbackValue
: connectorMinimumAmperage
801 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
802 Constants
.DEFAULT_FLUCTUATION_PERCENT
805 currentMeasurandValues
.L1
=
806 phase1FluctuatedValue
??
807 defaultFluctuatedAmperagePerPhase
??
808 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
809 currentMeasurandValues
.L2
=
810 phase2FluctuatedValue
??
811 defaultFluctuatedAmperagePerPhase
??
812 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
813 currentMeasurandValues
.L3
=
814 phase3FluctuatedValue
??
815 defaultFluctuatedAmperagePerPhase
??
816 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
818 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
819 ? getRandomFloatFluctuatedRounded(
820 getLimitFromSampledValueTemplateCustomValue(
821 currentSampledValueTemplate
.value
,
822 connectorMaximumAmperage
,
823 connectorMinimumAmperage
,
826 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
827 fallbackValue
: connectorMinimumAmperage
830 currentSampledValueTemplate
.fluctuationPercent
??
831 Constants
.DEFAULT_FLUCTUATION_PERCENT
833 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
834 currentMeasurandValues
.L2
= 0
835 currentMeasurandValues
.L3
= 0
837 currentMeasurandValues
.allPhases
= roundTo(
838 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
839 chargingStation
.getNumberOfPhases(),
844 connectorMaximumAmperage
= DCElectricUtils
.amperage(
845 connectorMaximumAvailablePower
,
846 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
847 chargingStation
.stationInfo
.voltageOut
!
849 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
850 ? getRandomFloatFluctuatedRounded(
851 getLimitFromSampledValueTemplateCustomValue(
852 currentSampledValueTemplate
.value
,
853 connectorMaximumAmperage
,
854 connectorMinimumAmperage
,
857 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
858 fallbackValue
: connectorMinimumAmperage
861 currentSampledValueTemplate
.fluctuationPercent
??
862 Constants
.DEFAULT_FLUCTUATION_PERCENT
864 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
867 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
868 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
870 meterValue
.sampledValue
.push(
871 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
873 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
875 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
876 connectorMaximumAmperage
||
877 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
878 connectorMinimumAmperage
||
882 `${chargingStation.logPrefix()} MeterValues measurand ${
883 meterValue.sampledValue[sampledValuesIndex].measurand ??
884 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
885 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
886 meterValue.sampledValue[sampledValuesIndex].value
887 }/${connectorMaximumAmperage}`
892 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
895 const phaseValue
= `L${phase}`
896 meterValue
.sampledValue
.push(
898 currentPerPhaseSampledValueTemplates
[
899 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
900 ] ?? currentSampledValueTemplate
,
901 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
903 phaseValue
as MeterValuePhase
906 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
908 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
909 connectorMaximumAmperage
||
910 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
911 connectorMinimumAmperage
||
915 `${chargingStation.logPrefix()} MeterValues measurand ${
916 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
917 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
919 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
920 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
921 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
922 }/${connectorMaximumAmperage}`
927 // Energy.Active.Import.Register measurand (default)
928 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
929 if (energySampledValueTemplate
!= null) {
930 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
931 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!)
933 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
934 const connectorMaximumAvailablePower
=
935 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
936 const connectorMaximumEnergyRounded
= roundTo(
937 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
940 const connectorMinimumEnergyRounded
= roundTo(
941 energySampledValueTemplate
.minimumValue
?? 0,
944 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
945 ? getRandomFloatFluctuatedRounded(
946 getLimitFromSampledValueTemplateCustomValue(
947 energySampledValueTemplate
.value
,
948 connectorMaximumEnergyRounded
,
949 connectorMinimumEnergyRounded
,
951 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
952 fallbackValue
: connectorMinimumEnergyRounded
,
953 unitMultiplier
: unitDivider
956 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
958 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
959 // Persist previous value on connector
960 if (connector
!= null) {
962 connector
.energyActiveImportRegisterValue
!= null &&
963 connector
.energyActiveImportRegisterValue
>= 0 &&
964 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
965 connector
.transactionEnergyActiveImportRegisterValue
>= 0
967 connector
.energyActiveImportRegisterValue
+= energyValueRounded
968 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
970 connector
.energyActiveImportRegisterValue
= 0
971 connector
.transactionEnergyActiveImportRegisterValue
= 0
974 meterValue
.sampledValue
.push(
976 energySampledValueTemplate
,
978 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
984 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
986 energyValueRounded
> connectorMaximumEnergyRounded
||
987 energyValueRounded
< connectorMinimumEnergyRounded
||
991 `${chargingStation.logPrefix()} MeterValues measurand ${
992 meterValue.sampledValue[sampledValuesIndex].measurand ??
993 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
994 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
999 case OCPPVersion
.VERSION_20
:
1000 case OCPPVersion
.VERSION_201
:
1002 throw new BaseError(
1003 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1008 export const buildTransactionEndMeterValue
= (
1009 chargingStation
: ChargingStation
,
1010 connectorId
: number,
1013 let meterValue
: MeterValue
1014 let sampledValueTemplate
: SampledValueTemplate
| undefined
1015 let unitDivider
: number
1016 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1017 case OCPPVersion
.VERSION_16
:
1019 timestamp
: new Date(),
1022 // Energy.Active.Import.Register measurand (default)
1023 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1024 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1025 meterValue
.sampledValue
.push(
1027 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1028 sampledValueTemplate
!,
1029 roundTo((meterStop
?? 0) / unitDivider
, 4),
1030 MeterValueContext
.TRANSACTION_END
1034 case OCPPVersion
.VERSION_20
:
1035 case OCPPVersion
.VERSION_201
:
1037 throw new BaseError(
1038 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1043 const checkMeasurandPowerDivider
= (
1044 chargingStation
: ChargingStation
,
1045 measurandType
: MeterValueMeasurand
1047 if (chargingStation
.powerDivider
== null) {
1048 const errMsg
= `MeterValues measurand ${
1049 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1050 }: powerDivider is undefined`
1051 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1052 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1053 } else if (chargingStation
?.powerDivider
<= 0) {
1054 const errMsg
= `MeterValues measurand ${
1055 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1056 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1057 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1058 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1062 const getLimitFromSampledValueTemplateCustomValue
= (
1063 value
: string | undefined,
1066 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1070 limitationEnabled
: false,
1076 const parsedValue
= parseInt(value
?? '')
1077 if (options
?.limitationEnabled
=== true) {
1079 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1080 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1084 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1085 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1088 const getSampledValueTemplate
= (
1089 chargingStation
: ChargingStation
,
1090 connectorId
: number,
1091 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1092 phase
?: MeterValuePhase
1093 ): SampledValueTemplate
| undefined => {
1094 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1095 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1097 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1102 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1103 getConfigurationKey(
1105 StandardParametersKey
.MeterValuesSampledData
1106 )?.value
?.includes(measurand
) === false
1109 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1110 StandardParametersKey.MeterValuesSampledData
1115 const sampledValueTemplates
: SampledValueTemplate
[] =
1116 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1117 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1120 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1124 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1125 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1129 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1133 sampledValueTemplates
[index
]?.phase
=== phase
&&
1134 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1135 getConfigurationKey(
1137 StandardParametersKey
.MeterValuesSampledData
1138 )?.value
?.includes(measurand
) === true
1140 return sampledValueTemplates
[index
]
1143 sampledValueTemplates
[index
]?.phase
== null &&
1144 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1145 getConfigurationKey(
1147 StandardParametersKey
.MeterValuesSampledData
1148 )?.value
?.includes(measurand
) === true
1150 return sampledValueTemplates
[index
]
1152 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1153 (sampledValueTemplates
[index
]?.measurand
== null ||
1154 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1156 return sampledValueTemplates
[index
]
1159 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1160 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1161 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1162 throw new BaseError(errorMsg
)
1165 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1169 const buildSampledValue
= (
1170 sampledValueTemplate
: SampledValueTemplate
,
1172 context
?: MeterValueContext
,
1173 phase
?: MeterValuePhase
1174 ): SampledValue
=> {
1175 const sampledValueContext
= context
?? sampledValueTemplate
?.context
1176 const sampledValueLocation
=
1177 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1178 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1179 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
1180 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1182 ...(sampledValueTemplate
.unit
!= null && {
1183 unit
: sampledValueTemplate
.unit
1185 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1186 ...(sampledValueTemplate
.measurand
!= null && {
1187 measurand
: sampledValueTemplate
.measurand
1189 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1190 ...(value
!= null && { value
: value
.toString() }),
1191 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1195 const getMeasurandDefaultLocation
= (
1196 measurandType
: MeterValueMeasurand
1197 ): MeterValueLocation
| undefined => {
1198 switch (measurandType
) {
1199 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1200 return MeterValueLocation
.EV
1204 // const getMeasurandDefaultUnit = (
1205 // measurandType: MeterValueMeasurand
1206 // ): MeterValueUnit | undefined => {
1207 // switch (measurandType) {
1208 // case MeterValueMeasurand.CURRENT_EXPORT:
1209 // case MeterValueMeasurand.CURRENT_IMPORT:
1210 // case MeterValueMeasurand.CURRENT_OFFERED:
1211 // return MeterValueUnit.AMP
1212 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1213 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1214 // return MeterValueUnit.WATT_HOUR
1215 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1216 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1217 // case MeterValueMeasurand.POWER_OFFERED:
1218 // return MeterValueUnit.WATT
1219 // case MeterValueMeasurand.STATE_OF_CHARGE:
1220 // return MeterValueUnit.PERCENT
1221 // case MeterValueMeasurand.VOLTAGE:
1222 // return MeterValueUnit.VOLT
1226 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1227 export class OCPPServiceUtils
{
1228 public static getMessageTypeString
= getMessageTypeString
1229 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1230 public static isIdTagAuthorized
= isIdTagAuthorized
1231 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1232 protected static getSampledValueTemplate
= getSampledValueTemplate
1233 protected static buildSampledValue
= buildSampledValue
1235 protected constructor () {
1236 // This is intentional
1239 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | null | undefined): ErrorType
{
1240 if (isNotEmptyArray(errors
)) {
1241 for (const error
of errors
as DefinedError
[]) {
1242 switch (error
.keyword
) {
1244 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1245 case 'dependencies':
1247 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1250 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1254 return ErrorType
.FORMAT_VIOLATION
1257 public static isRequestCommandSupported (
1258 chargingStation
: ChargingStation
,
1259 command
: RequestCommand
1261 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1264 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1269 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1271 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
]
1273 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1277 public static isIncomingRequestCommandSupported (
1278 chargingStation
: ChargingStation
,
1279 command
: IncomingRequestCommand
1281 const isIncomingRequestCommand
=
1282 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1284 isIncomingRequestCommand
&&
1285 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1289 isIncomingRequestCommand
&&
1290 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
?.[command
] != null
1292 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
]
1294 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1298 public static isMessageTriggerSupported (
1299 chargingStation
: ChargingStation
,
1300 messageTrigger
: MessageTrigger
1302 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1303 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1307 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1309 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
]
1312 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1317 public static isConnectorIdValid (
1318 chargingStation
: ChargingStation
,
1319 ocppCommand
: IncomingRequestCommand
,
1322 if (connectorId
< 0) {
1324 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1331 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1332 for (const key
in obj
) {
1333 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1334 if (isDate(obj
![key
])) {
1335 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1336 (obj
![key
] as string) = (obj
![key
] as Date).toISOString()
1337 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1338 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
1339 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1340 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
)
1345 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1346 if (chargingStation
.heartbeatSetInterval
== null) {
1347 chargingStation
.startHeartbeat()
1348 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1349 chargingStation
.restartHeartbeat()
1353 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1354 relativePath
: string,
1355 ocppVersion
: OCPPVersion
,
1356 moduleName
?: string,
1358 ): JSONSchemaType
<T
> {
1359 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1361 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1363 handleFileException(
1365 FileType
.JsonSchema
,
1366 error
as NodeJS
.ErrnoException
,
1367 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1368 { throwError
: false }
1370 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1371 return {} as JSONSchemaType
<T
>
1375 private static readonly logPrefix
= (
1376 ocppVersion
: OCPPVersion
,
1377 moduleName
?: string,
1381 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1382 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1383 : ` OCPP ${ocppVersion} |`
1384 return logPrefix(logMsg
)