1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv'
7 areIntervalsOverlapping
,
14 import { OCPP16Constants
} from
'./OCPP16Constants.js'
19 } from
'../../../charging-station/index.js'
21 type ConfigurationKey
,
24 OCPP16AuthorizationStatus
,
25 type OCPP16AvailabilityType
,
26 type OCPP16ChangeAvailabilityResponse
,
27 OCPP16ChargePointStatus
,
28 type OCPP16ChargingProfile
,
29 type OCPP16ChargingSchedule
,
30 type OCPP16ClearChargingProfileRequest
,
31 type OCPP16IncomingRequestCommand
,
32 type OCPP16MeterValue
,
33 OCPP16MeterValueContext
,
35 type OCPP16RequestCommand
,
36 OCPP16StandardParametersKey
,
37 OCPP16StopTransactionReason
,
38 type OCPP16SupportedFeatureProfiles
,
40 } from
'../../../types/index.js'
41 import { convertToDate
, isNotEmptyArray
, logger
, roundTo
} from
'../../../utils/index.js'
42 import { OCPPServiceUtils
} from
'../OCPPServiceUtils.js'
44 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
45 public static checkFeatureProfile (
46 chargingStation
: ChargingStation
,
47 featureProfile
: OCPP16SupportedFeatureProfiles
,
48 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
50 if (hasFeatureProfile(chargingStation
, featureProfile
) === false) {
52 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
53 OCPP16StandardParametersKey.SupportedFeatureProfiles
61 public static buildTransactionBeginMeterValue (
62 chargingStation
: ChargingStation
,
64 meterStart
: number | undefined
66 const meterValue
: OCPP16MeterValue
= {
67 timestamp
: new Date(),
70 // Energy.Active.Import.Register measurand (default)
71 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
76 sampledValueTemplate
?.unit
=== OCPP16MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
77 meterValue
.sampledValue
.push(
78 OCPP16ServiceUtils
.buildSampledValue(
79 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
80 sampledValueTemplate
!,
81 roundTo((meterStart
?? 0) / unitDivider
, 4),
82 OCPP16MeterValueContext
.TRANSACTION_BEGIN
88 public static buildTransactionDataMeterValues (
89 transactionBeginMeterValue
: OCPP16MeterValue
,
90 transactionEndMeterValue
: OCPP16MeterValue
91 ): OCPP16MeterValue
[] {
92 const meterValues
: OCPP16MeterValue
[] = []
93 meterValues
.push(transactionBeginMeterValue
)
94 meterValues
.push(transactionEndMeterValue
)
98 public static remoteStopTransaction
= async (
99 chargingStation
: ChargingStation
,
101 ): Promise
<GenericResponse
> => {
102 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
105 OCPP16ChargePointStatus
.Finishing
107 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
109 OCPP16StopTransactionReason
.REMOTE
111 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
112 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
114 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
117 public static changeAvailability
= async (
118 chargingStation
: ChargingStation
,
119 connectorIds
: number[],
120 chargePointStatus
: OCPP16ChargePointStatus
,
121 availabilityType
: OCPP16AvailabilityType
122 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
123 const responses
: OCPP16ChangeAvailabilityResponse
[] = []
124 for (const connectorId
of connectorIds
) {
125 let response
: OCPP16ChangeAvailabilityResponse
=
126 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
127 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
128 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
129 if (connectorStatus
.transactionStarted
=== true) {
130 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
132 connectorStatus
.availability
= availabilityType
133 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
134 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
140 responses
.push(response
)
142 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
143 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
145 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
148 public static setChargingProfile (
149 chargingStation
: ChargingStation
,
151 cp
: OCPP16ChargingProfile
153 if (chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
== null) {
155 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
157 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
158 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= []
160 if (!Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
162 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`
164 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
165 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= []
167 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
168 cp
.validFrom
= convertToDate(cp
.validFrom
)!
169 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170 cp
.validTo
= convertToDate(cp
.validTo
)!
171 let cpReplaced
= false
172 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
173 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
174 for (const [index
, chargingProfile
] of chargingStation
175 .getConnectorStatus(connectorId
)!
176 .chargingProfiles
!.entries()) {
178 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
179 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
180 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
182 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
188 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
)
191 public static clearChargingProfiles
= (
192 chargingStation
: ChargingStation
,
193 commandPayload
: OCPP16ClearChargingProfileRequest
,
194 chargingProfiles
: OCPP16ChargingProfile
[] | undefined
196 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
197 let clearedCP
= false
198 if (isNotEmptyArray(chargingProfiles
)) {
199 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
200 let clearCurrentCP
= false
201 if (chargingProfile
.chargingProfileId
=== id
) {
202 clearCurrentCP
= true
204 if (chargingProfilePurpose
== null && chargingProfile
.stackLevel
=== stackLevel
) {
205 clearCurrentCP
= true
208 stackLevel
== null &&
209 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
211 clearCurrentCP
= true
214 chargingProfile
.stackLevel
=== stackLevel
&&
215 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
217 clearCurrentCP
= true
219 if (clearCurrentCP
) {
220 chargingProfiles
.splice(index
, 1)
222 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
232 public static composeChargingSchedules
= (
233 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
234 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
235 compositeInterval
: Interval
236 ): OCPP16ChargingSchedule
| undefined => {
237 if (chargingScheduleHigher
== null && chargingScheduleLower
== null) {
240 if (chargingScheduleHigher
!= null && chargingScheduleLower
== null) {
241 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
)
243 if (chargingScheduleHigher
== null && chargingScheduleLower
!= null) {
244 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
)
246 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
247 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
248 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
)
249 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
250 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
251 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
)
252 const compositeChargingScheduleHigherInterval
: Interval
= {
253 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
254 start
: compositeChargingScheduleHigher
!.startSchedule
!,
256 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
257 compositeChargingScheduleHigher
!.startSchedule
!,
258 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
259 compositeChargingScheduleHigher
!.duration
!
262 const compositeChargingScheduleLowerInterval
: Interval
= {
263 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
264 start
: compositeChargingScheduleLower
!.startSchedule
!,
266 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
267 compositeChargingScheduleLower
!.startSchedule
!,
268 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
269 compositeChargingScheduleLower
!.duration
!
272 const higherFirst
= isBefore(
273 compositeChargingScheduleHigherInterval
.start
,
274 compositeChargingScheduleLowerInterval
.start
277 !areIntervalsOverlapping(
278 compositeChargingScheduleHigherInterval
,
279 compositeChargingScheduleLowerInterval
283 ...compositeChargingScheduleLower
,
284 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
285 ...compositeChargingScheduleHigher
!,
286 startSchedule
: higherFirst
287 ? (compositeChargingScheduleHigherInterval
.start
as Date)
288 : (compositeChargingScheduleLowerInterval
.start
as Date),
289 duration
: higherFirst
290 ? differenceInSeconds(
291 compositeChargingScheduleLowerInterval
.end
,
292 compositeChargingScheduleHigherInterval
.start
294 : differenceInSeconds(
295 compositeChargingScheduleHigherInterval
.end
,
296 compositeChargingScheduleLowerInterval
.start
298 chargingSchedulePeriod
: [
299 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
300 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
303 startPeriod
: higherFirst
305 : schedulePeriod
.startPeriod
+
307 compositeChargingScheduleHigherInterval
.start
,
308 compositeChargingScheduleLowerInterval
.start
312 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
313 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
316 startPeriod
: higherFirst
317 ? schedulePeriod
.startPeriod
+
319 compositeChargingScheduleLowerInterval
.start
,
320 compositeChargingScheduleHigherInterval
.start
325 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
329 ...compositeChargingScheduleLower
,
330 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
331 ...compositeChargingScheduleHigher
!,
332 startSchedule
: higherFirst
333 ? (compositeChargingScheduleHigherInterval
.start
as Date)
334 : (compositeChargingScheduleLowerInterval
.start
as Date),
335 duration
: higherFirst
336 ? differenceInSeconds(
337 compositeChargingScheduleLowerInterval
.end
,
338 compositeChargingScheduleHigherInterval
.start
340 : differenceInSeconds(
341 compositeChargingScheduleHigherInterval
.end
,
342 compositeChargingScheduleLowerInterval
.start
344 chargingSchedulePeriod
: [
345 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
346 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
349 startPeriod
: higherFirst
351 : schedulePeriod
.startPeriod
+
353 compositeChargingScheduleHigherInterval
.start
,
354 compositeChargingScheduleLowerInterval
.start
358 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
359 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
360 .filter((schedulePeriod
, index
) => {
365 compositeChargingScheduleLowerInterval
.start
,
366 schedulePeriod
.startPeriod
369 start
: compositeChargingScheduleLowerInterval
.start
,
370 end
: compositeChargingScheduleHigherInterval
.end
378 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
379 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
382 compositeChargingScheduleLowerInterval
.start
,
383 schedulePeriod
.startPeriod
386 start
: compositeChargingScheduleLowerInterval
.start
,
387 end
: compositeChargingScheduleHigherInterval
.end
392 compositeChargingScheduleLowerInterval
.start
,
393 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
394 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
397 start
: compositeChargingScheduleLowerInterval
.start
,
398 end
: compositeChargingScheduleHigherInterval
.end
408 compositeChargingScheduleLowerInterval
.start
,
409 schedulePeriod
.startPeriod
412 start
: compositeChargingScheduleHigherInterval
.start
,
413 end
: compositeChargingScheduleLowerInterval
.end
421 .map((schedulePeriod
, index
) => {
422 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
423 schedulePeriod
.startPeriod
= 0
427 startPeriod
: higherFirst
428 ? schedulePeriod
.startPeriod
+
430 compositeChargingScheduleLowerInterval
.start
,
431 compositeChargingScheduleHigherInterval
.start
436 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
440 public static isConfigurationKeyVisible (key
: ConfigurationKey
): boolean {
441 if (key
.visible
== null) {
447 public static hasReservation
= (
448 chargingStation
: ChargingStation
,
452 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
)
453 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0)
455 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
456 OCPP16ChargePointStatus
.Reserved
&&
457 connectorReservation
!= null &&
458 !hasReservationExpired(connectorReservation
) &&
459 connectorReservation
.idTag
=== idTag
) ||
460 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
461 chargingStationReservation
!= null &&
462 !hasReservationExpired(chargingStationReservation
) &&
463 chargingStationReservation
.idTag
=== idTag
)
466 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
467 connectorReservation
?? chargingStationReservation
474 public static parseJsonSchemaFile
<T
extends JsonType
>(
475 relativePath
: string,
478 ): JSONSchemaType
<T
> {
479 return super.parseJsonSchemaFile
<T
>(
481 OCPPVersion
.VERSION_16
,
487 private static readonly composeChargingSchedule
= (
488 chargingSchedule
: OCPP16ChargingSchedule
,
489 compositeInterval
: Interval
490 ): OCPP16ChargingSchedule
| undefined => {
491 const chargingScheduleInterval
: Interval
= {
492 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
493 start
: chargingSchedule
.startSchedule
!,
494 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
495 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!)
497 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
498 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
499 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
502 startSchedule
: compositeInterval
.start
as Date,
503 duration
: differenceInSeconds(
504 chargingScheduleInterval
.end
,
505 compositeInterval
.start
as Date
507 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
508 .filter((schedulePeriod
, index
) => {
511 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
512 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
519 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
521 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
526 chargingScheduleInterval
.start
,
527 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
536 .map((schedulePeriod
, index
) => {
537 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
538 schedulePeriod
.startPeriod
= 0
540 return schedulePeriod
544 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
547 duration
: differenceInSeconds(
548 compositeInterval
.end
as Date,
549 chargingScheduleInterval
.start
551 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter(schedulePeriod
=>
553 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
554 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
560 return chargingSchedule