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 for (const [index
, chargingProfile
] of chargingStation
170 .getConnectorStatus(connectorId
)
171 ?.chargingProfiles
?.entries() ?? []) {
173 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
174 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
175 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
177 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
178 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
183 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
)
186 public static clearChargingProfiles
= (
187 chargingStation
: ChargingStation
,
188 commandPayload
: OCPP16ClearChargingProfileRequest
,
189 chargingProfiles
: OCPP16ChargingProfile
[] | undefined
191 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
192 let clearedCP
= false
193 if (isNotEmptyArray(chargingProfiles
)) {
194 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
195 let clearCurrentCP
= false
196 if (chargingProfile
.chargingProfileId
=== id
) {
197 clearCurrentCP
= true
199 if (chargingProfilePurpose
== null && chargingProfile
.stackLevel
=== stackLevel
) {
200 clearCurrentCP
= true
203 stackLevel
== null &&
204 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
206 clearCurrentCP
= true
209 chargingProfile
.stackLevel
=== stackLevel
&&
210 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
212 clearCurrentCP
= true
214 if (clearCurrentCP
) {
215 chargingProfiles
.splice(index
, 1)
217 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
227 public static composeChargingSchedules
= (
228 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
229 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
230 compositeInterval
: Interval
231 ): OCPP16ChargingSchedule
| undefined => {
232 if (chargingScheduleHigher
== null && chargingScheduleLower
== null) {
235 if (chargingScheduleHigher
!= null && chargingScheduleLower
== null) {
236 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
)
238 if (chargingScheduleHigher
== null && chargingScheduleLower
!= null) {
239 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
)
241 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
242 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
243 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
)
244 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
245 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
246 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
)
247 const compositeChargingScheduleHigherInterval
: Interval
= {
248 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
249 start
: compositeChargingScheduleHigher
!.startSchedule
!,
251 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
252 compositeChargingScheduleHigher
!.startSchedule
!,
253 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
254 compositeChargingScheduleHigher
!.duration
!
257 const compositeChargingScheduleLowerInterval
: Interval
= {
258 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
259 start
: compositeChargingScheduleLower
!.startSchedule
!,
261 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
262 compositeChargingScheduleLower
!.startSchedule
!,
263 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
264 compositeChargingScheduleLower
!.duration
!
267 const higherFirst
= isBefore(
268 compositeChargingScheduleHigherInterval
.start
,
269 compositeChargingScheduleLowerInterval
.start
272 !areIntervalsOverlapping(
273 compositeChargingScheduleHigherInterval
,
274 compositeChargingScheduleLowerInterval
278 ...compositeChargingScheduleLower
,
279 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
280 ...compositeChargingScheduleHigher
!,
281 startSchedule
: higherFirst
282 ? (compositeChargingScheduleHigherInterval
.start
as Date)
283 : (compositeChargingScheduleLowerInterval
.start
as Date),
284 duration
: higherFirst
285 ? differenceInSeconds(
286 compositeChargingScheduleLowerInterval
.end
,
287 compositeChargingScheduleHigherInterval
.start
289 : differenceInSeconds(
290 compositeChargingScheduleHigherInterval
.end
,
291 compositeChargingScheduleLowerInterval
.start
293 chargingSchedulePeriod
: [
294 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
295 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
298 startPeriod
: higherFirst
300 : schedulePeriod
.startPeriod
+
302 compositeChargingScheduleHigherInterval
.start
,
303 compositeChargingScheduleLowerInterval
.start
307 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
308 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
311 startPeriod
: higherFirst
312 ? schedulePeriod
.startPeriod
+
314 compositeChargingScheduleLowerInterval
.start
,
315 compositeChargingScheduleHigherInterval
.start
320 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
324 ...compositeChargingScheduleLower
,
325 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
326 ...compositeChargingScheduleHigher
!,
327 startSchedule
: higherFirst
328 ? (compositeChargingScheduleHigherInterval
.start
as Date)
329 : (compositeChargingScheduleLowerInterval
.start
as Date),
330 duration
: higherFirst
331 ? differenceInSeconds(
332 compositeChargingScheduleLowerInterval
.end
,
333 compositeChargingScheduleHigherInterval
.start
335 : differenceInSeconds(
336 compositeChargingScheduleHigherInterval
.end
,
337 compositeChargingScheduleLowerInterval
.start
339 chargingSchedulePeriod
: [
340 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
341 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
344 startPeriod
: higherFirst
346 : schedulePeriod
.startPeriod
+
348 compositeChargingScheduleHigherInterval
.start
,
349 compositeChargingScheduleLowerInterval
.start
353 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
354 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
355 .filter((schedulePeriod
, index
) => {
360 compositeChargingScheduleLowerInterval
.start
,
361 schedulePeriod
.startPeriod
364 start
: compositeChargingScheduleLowerInterval
.start
,
365 end
: compositeChargingScheduleHigherInterval
.end
373 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
374 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
377 compositeChargingScheduleLowerInterval
.start
,
378 schedulePeriod
.startPeriod
381 start
: compositeChargingScheduleLowerInterval
.start
,
382 end
: compositeChargingScheduleHigherInterval
.end
387 compositeChargingScheduleLowerInterval
.start
,
388 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
389 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
392 start
: compositeChargingScheduleLowerInterval
.start
,
393 end
: compositeChargingScheduleHigherInterval
.end
403 compositeChargingScheduleLowerInterval
.start
,
404 schedulePeriod
.startPeriod
407 start
: compositeChargingScheduleHigherInterval
.start
,
408 end
: compositeChargingScheduleLowerInterval
.end
416 .map((schedulePeriod
, index
) => {
417 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
418 schedulePeriod
.startPeriod
= 0
422 startPeriod
: higherFirst
423 ? schedulePeriod
.startPeriod
+
425 compositeChargingScheduleLowerInterval
.start
,
426 compositeChargingScheduleHigherInterval
.start
431 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
435 public static isConfigurationKeyVisible (key
: ConfigurationKey
): boolean {
436 if (key
.visible
== null) {
442 public static hasReservation
= (
443 chargingStation
: ChargingStation
,
447 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
)
448 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0)
450 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
451 OCPP16ChargePointStatus
.Reserved
&&
452 connectorReservation
!= null &&
453 !hasReservationExpired(connectorReservation
) &&
454 connectorReservation
.idTag
=== idTag
) ||
455 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
456 chargingStationReservation
!= null &&
457 !hasReservationExpired(chargingStationReservation
) &&
458 chargingStationReservation
.idTag
=== idTag
)
461 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
462 connectorReservation
?? chargingStationReservation
469 public static parseJsonSchemaFile
<T
extends JsonType
>(
470 relativePath
: string,
473 ): JSONSchemaType
<T
> {
474 return super.parseJsonSchemaFile
<T
>(
476 OCPPVersion
.VERSION_16
,
482 private static readonly composeChargingSchedule
= (
483 chargingSchedule
: OCPP16ChargingSchedule
,
484 compositeInterval
: Interval
485 ): OCPP16ChargingSchedule
| undefined => {
486 const chargingScheduleInterval
: Interval
= {
487 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
488 start
: chargingSchedule
.startSchedule
!,
489 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
490 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!)
492 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
493 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
)
494 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
497 startSchedule
: compositeInterval
.start
as Date,
498 duration
: differenceInSeconds(
499 chargingScheduleInterval
.end
,
500 compositeInterval
.start
as Date
502 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
503 .filter((schedulePeriod
, index
) => {
506 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
507 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
514 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
516 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
521 chargingScheduleInterval
.start
,
522 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
531 .map((schedulePeriod
, index
) => {
532 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
533 schedulePeriod
.startPeriod
= 0
535 return schedulePeriod
539 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
542 duration
: differenceInSeconds(
543 compositeInterval
.end
as Date,
544 chargingScheduleInterval
.start
546 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
548 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
549 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
555 return chargingSchedule