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 { 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 let cpReplaced
= false
168 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
169 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170 for (const [index
, chargingProfile
] of chargingStation
171 .getConnectorStatus(connectorId
)!
172 .chargingProfiles
!.entries()) {
174 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
175 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
176 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
178 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
179 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
184 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
)
187 public static clearChargingProfiles
= (
188 chargingStation
: ChargingStation
,
189 commandPayload
: OCPP16ClearChargingProfileRequest
,
190 chargingProfiles
: OCPP16ChargingProfile
[] | undefined
192 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
193 let clearedCP
= false
194 if (isNotEmptyArray(chargingProfiles
)) {
195 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
196 let clearCurrentCP
= false
197 if (chargingProfile
.chargingProfileId
=== id
) {
198 clearCurrentCP
= true
200 if (chargingProfilePurpose
== null && chargingProfile
.stackLevel
=== stackLevel
) {
201 clearCurrentCP
= true
204 stackLevel
== null &&
205 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
207 clearCurrentCP
= true
210 chargingProfile
.stackLevel
=== stackLevel
&&
211 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
213 clearCurrentCP
= true
215 if (clearCurrentCP
) {
216 chargingProfiles
.splice(index
, 1)
218 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
228 public static composeChargingSchedules
= (
229 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
230 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
231 compositeInterval
: Interval
232 ): OCPP16ChargingSchedule
| undefined => {
233 if (chargingScheduleHigher
== null && chargingScheduleLower
== null) {
236 if (chargingScheduleHigher
!= null && chargingScheduleLower
== null) {
237 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
)
239 if (chargingScheduleHigher
== null && chargingScheduleLower
!= null) {
240 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
)
242 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
243 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
244 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
)
245 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
246 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
247 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
)
248 const compositeChargingScheduleHigherInterval
: Interval
= {
249 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
250 start
: compositeChargingScheduleHigher
!.startSchedule
!,
252 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
253 compositeChargingScheduleHigher
!.startSchedule
!,
254 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
255 compositeChargingScheduleHigher
!.duration
!
258 const compositeChargingScheduleLowerInterval
: Interval
= {
259 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
260 start
: compositeChargingScheduleLower
!.startSchedule
!,
262 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
263 compositeChargingScheduleLower
!.startSchedule
!,
264 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
265 compositeChargingScheduleLower
!.duration
!
268 const higherFirst
= isBefore(
269 compositeChargingScheduleHigherInterval
.start
,
270 compositeChargingScheduleLowerInterval
.start
273 !areIntervalsOverlapping(
274 compositeChargingScheduleHigherInterval
,
275 compositeChargingScheduleLowerInterval
279 ...compositeChargingScheduleLower
,
280 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
281 ...compositeChargingScheduleHigher
!,
282 startSchedule
: higherFirst
283 ? (compositeChargingScheduleHigherInterval
.start
as Date)
284 : (compositeChargingScheduleLowerInterval
.start
as Date),
285 duration
: higherFirst
286 ? differenceInSeconds(
287 compositeChargingScheduleLowerInterval
.end
,
288 compositeChargingScheduleHigherInterval
.start
290 : differenceInSeconds(
291 compositeChargingScheduleHigherInterval
.end
,
292 compositeChargingScheduleLowerInterval
.start
294 chargingSchedulePeriod
: [
295 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
296 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
299 startPeriod
: higherFirst
301 : schedulePeriod
.startPeriod
+
303 compositeChargingScheduleHigherInterval
.start
,
304 compositeChargingScheduleLowerInterval
.start
308 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
309 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
312 startPeriod
: higherFirst
313 ? schedulePeriod
.startPeriod
+
315 compositeChargingScheduleLowerInterval
.start
,
316 compositeChargingScheduleHigherInterval
.start
321 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
325 ...compositeChargingScheduleLower
,
326 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
327 ...compositeChargingScheduleHigher
!,
328 startSchedule
: higherFirst
329 ? (compositeChargingScheduleHigherInterval
.start
as Date)
330 : (compositeChargingScheduleLowerInterval
.start
as Date),
331 duration
: higherFirst
332 ? differenceInSeconds(
333 compositeChargingScheduleLowerInterval
.end
,
334 compositeChargingScheduleHigherInterval
.start
336 : differenceInSeconds(
337 compositeChargingScheduleHigherInterval
.end
,
338 compositeChargingScheduleLowerInterval
.start
340 chargingSchedulePeriod
: [
341 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
342 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map(schedulePeriod
=> {
345 startPeriod
: higherFirst
347 : schedulePeriod
.startPeriod
+
349 compositeChargingScheduleHigherInterval
.start
,
350 compositeChargingScheduleLowerInterval
.start
354 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
355 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
356 .filter((schedulePeriod
, index
) => {
361 compositeChargingScheduleLowerInterval
.start
,
362 schedulePeriod
.startPeriod
365 start
: compositeChargingScheduleLowerInterval
.start
,
366 end
: compositeChargingScheduleHigherInterval
.end
374 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
375 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
378 compositeChargingScheduleLowerInterval
.start
,
379 schedulePeriod
.startPeriod
382 start
: compositeChargingScheduleLowerInterval
.start
,
383 end
: compositeChargingScheduleHigherInterval
.end
388 compositeChargingScheduleLowerInterval
.start
,
389 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
390 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
393 start
: compositeChargingScheduleLowerInterval
.start
,
394 end
: compositeChargingScheduleHigherInterval
.end
404 compositeChargingScheduleLowerInterval
.start
,
405 schedulePeriod
.startPeriod
408 start
: compositeChargingScheduleHigherInterval
.start
,
409 end
: compositeChargingScheduleLowerInterval
.end
417 .map((schedulePeriod
, index
) => {
418 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
419 schedulePeriod
.startPeriod
= 0
423 startPeriod
: higherFirst
424 ? schedulePeriod
.startPeriod
+
426 compositeChargingScheduleLowerInterval
.start
,
427 compositeChargingScheduleHigherInterval
.start
432 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
436 public static isConfigurationKeyVisible (key
: ConfigurationKey
): boolean {
437 if (key
.visible
== null) {
443 public static hasReservation
= (
444 chargingStation
: ChargingStation
,
448 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
)
449 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0)
451 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
452 OCPP16ChargePointStatus
.Reserved
&&
453 connectorReservation
!= null &&
454 !hasReservationExpired(connectorReservation
) &&
455 connectorReservation
.idTag
=== idTag
) ||
456 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
457 chargingStationReservation
!= null &&
458 !hasReservationExpired(chargingStationReservation
) &&
459 chargingStationReservation
.idTag
=== idTag
)
462 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
463 connectorReservation
?? chargingStationReservation
470 public static parseJsonSchemaFile
<T
extends JsonType
>(
471 relativePath
: string,
474 ): JSONSchemaType
<T
> {
475 return super.parseJsonSchemaFile
<T
>(
477 OCPPVersion
.VERSION_16
,
483 private static readonly composeChargingSchedule
= (
484 chargingSchedule
: OCPP16ChargingSchedule
,
485 compositeInterval
: Interval
486 ): OCPP16ChargingSchedule
| undefined => {
487 const chargingScheduleInterval
: Interval
= {
488 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
489 start
: chargingSchedule
.startSchedule
!,
490 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
491 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!)
493 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
494 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
495 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
498 startSchedule
: compositeInterval
.start
as Date,
499 duration
: differenceInSeconds(
500 chargingScheduleInterval
.end
,
501 compositeInterval
.start
as Date
503 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
504 .filter((schedulePeriod
, index
) => {
507 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
508 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
515 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
517 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
522 chargingScheduleInterval
.start
,
523 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
532 .map((schedulePeriod
, index
) => {
533 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
534 schedulePeriod
.startPeriod
= 0
536 return schedulePeriod
540 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
543 duration
: differenceInSeconds(
544 compositeInterval
.end
as Date,
545 chargingScheduleInterval
.start
547 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter(schedulePeriod
=>
549 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
550 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
556 return chargingSchedule