12431266172c3d9c468c9fde322ff860991bfe00
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import { createWriteStream, readdirSync } from 'node:fs'
4 import { dirname, join, resolve } from 'node:path'
5 import { URL, fileURLToPath } from 'node:url'
6
7 import type { ValidateFunction } from 'ajv'
8 import { Client, type FTPResponse } from 'basic-ftp'
9 import {
10 type Interval,
11 addSeconds,
12 differenceInSeconds,
13 isDate,
14 secondsToMilliseconds
15 } from 'date-fns'
16 import { maxTime } from 'date-fns/constants'
17 import { create } from 'tar'
18
19 import { OCPP16Constants } from './OCPP16Constants.js'
20 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
21 import {
22 type ChargingStation,
23 canProceedChargingProfile,
24 checkChargingStation,
25 getConfigurationKey,
26 getConnectorChargingProfiles,
27 prepareChargingProfileKind,
28 removeExpiredReservations,
29 setConfigurationKeyValue
30 } from '../../../charging-station/index.js'
31 import { OCPPError } from '../../../exception/index.js'
32 import {
33 type ChangeConfigurationRequest,
34 type ChangeConfigurationResponse,
35 ErrorType,
36 type GenericResponse,
37 GenericStatus,
38 type GetConfigurationRequest,
39 type GetConfigurationResponse,
40 type GetDiagnosticsRequest,
41 type GetDiagnosticsResponse,
42 type IncomingRequestHandler,
43 type JsonType,
44 OCPP16AuthorizationStatus,
45 OCPP16AvailabilityType,
46 type OCPP16BootNotificationRequest,
47 type OCPP16BootNotificationResponse,
48 type OCPP16CancelReservationRequest,
49 type OCPP16ChangeAvailabilityRequest,
50 type OCPP16ChangeAvailabilityResponse,
51 OCPP16ChargePointErrorCode,
52 OCPP16ChargePointStatus,
53 type OCPP16ChargingProfile,
54 OCPP16ChargingProfilePurposeType,
55 type OCPP16ChargingSchedule,
56 type OCPP16ClearCacheRequest,
57 type OCPP16ClearChargingProfileRequest,
58 type OCPP16ClearChargingProfileResponse,
59 type OCPP16DataTransferRequest,
60 type OCPP16DataTransferResponse,
61 OCPP16DataTransferVendorId,
62 OCPP16DiagnosticsStatus,
63 type OCPP16DiagnosticsStatusNotificationRequest,
64 type OCPP16DiagnosticsStatusNotificationResponse,
65 OCPP16FirmwareStatus,
66 type OCPP16FirmwareStatusNotificationRequest,
67 type OCPP16FirmwareStatusNotificationResponse,
68 type OCPP16GetCompositeScheduleRequest,
69 type OCPP16GetCompositeScheduleResponse,
70 type OCPP16HeartbeatRequest,
71 type OCPP16HeartbeatResponse,
72 OCPP16IncomingRequestCommand,
73 OCPP16MessageTrigger,
74 OCPP16RequestCommand,
75 type OCPP16ReserveNowRequest,
76 type OCPP16ReserveNowResponse,
77 OCPP16StandardParametersKey,
78 type OCPP16StartTransactionRequest,
79 type OCPP16StartTransactionResponse,
80 type OCPP16StatusNotificationRequest,
81 type OCPP16StatusNotificationResponse,
82 OCPP16StopTransactionReason,
83 OCPP16SupportedFeatureProfiles,
84 type OCPP16TriggerMessageRequest,
85 type OCPP16TriggerMessageResponse,
86 type OCPP16UpdateFirmwareRequest,
87 type OCPP16UpdateFirmwareResponse,
88 type OCPPConfigurationKey,
89 OCPPVersion,
90 type RemoteStartTransactionRequest,
91 type RemoteStopTransactionRequest,
92 ReservationTerminationReason,
93 type ResetRequest,
94 type SetChargingProfileRequest,
95 type SetChargingProfileResponse,
96 type UnlockConnectorRequest,
97 type UnlockConnectorResponse
98 } from '../../../types/index.js'
99 import {
100 Constants,
101 convertToDate,
102 convertToInt,
103 formatDurationMilliSeconds,
104 getRandomInteger,
105 isEmptyArray,
106 isNotEmptyArray,
107 isNotEmptyString,
108 logger,
109 sleep
110 } from '../../../utils/index.js'
111 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
112
113 const moduleName = 'OCPP16IncomingRequestService'
114
115 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
116 protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
117
118 private readonly incomingRequestHandlers: Map<
119 OCPP16IncomingRequestCommand,
120 IncomingRequestHandler
121 >
122
123 public constructor () {
124 // if (new.target.name === moduleName) {
125 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
126 // }
127 super(OCPPVersion.VERSION_16)
128 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
129 [
130 OCPP16IncomingRequestCommand.RESET,
131 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
132 ],
133 [
134 OCPP16IncomingRequestCommand.CLEAR_CACHE,
135 this.handleRequestClearCache.bind(this) as IncomingRequestHandler
136 ],
137 [
138 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
139 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
140 ],
141 [
142 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
143 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
144 ],
145 [
146 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
147 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
148 ],
149 [
150 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
151 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
152 ],
153 [
154 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
155 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
156 ],
157 [
158 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
159 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
160 ],
161 [
162 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
163 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
164 ],
165 [
166 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
167 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
168 ],
169 [
170 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
171 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
172 ],
173 [
174 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
175 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
176 ],
177 [
178 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
179 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
180 ],
181 [
182 OCPP16IncomingRequestCommand.DATA_TRANSFER,
183 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
184 ],
185 [
186 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
187 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
188 ],
189 [
190 OCPP16IncomingRequestCommand.RESERVE_NOW,
191 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
192 ],
193 [
194 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
195 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
196 ]
197 ])
198 this.payloadValidateFunctions = new Map<
199 OCPP16IncomingRequestCommand,
200 ValidateFunction<JsonType>
201 >([
202 [
203 OCPP16IncomingRequestCommand.RESET,
204 this.ajv
205 .compile(
206 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
207 'assets/json-schemas/ocpp/1.6/Reset.json',
208 moduleName,
209 'constructor'
210 )
211 )
212 .bind(this)
213 ],
214 [
215 OCPP16IncomingRequestCommand.CLEAR_CACHE,
216 this.ajv
217 .compile(
218 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
219 'assets/json-schemas/ocpp/1.6/ClearCache.json',
220 moduleName,
221 'constructor'
222 )
223 )
224 .bind(this)
225 ],
226 [
227 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
228 this.ajv
229 .compile(
230 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
231 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
232 moduleName,
233 'constructor'
234 )
235 )
236 .bind(this)
237 ],
238 [
239 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
240 this.ajv
241 .compile(
242 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
243 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
244 moduleName,
245 'constructor'
246 )
247 )
248 .bind(this)
249 ],
250 [
251 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
252 this.ajv
253 .compile(
254 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
255 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
256 moduleName,
257 'constructor'
258 )
259 )
260 .bind(this)
261 ],
262 [
263 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
264 this.ajv
265 .compile(
266 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
267 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
268 moduleName,
269 'constructor'
270 )
271 )
272 .bind(this)
273 ],
274 [
275 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
276 this.ajv
277 .compile(
278 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
279 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
280 moduleName,
281 'constructor'
282 )
283 )
284 .bind(this)
285 ],
286 [
287 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
288 this.ajv
289 .compile(
290 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
291 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
292 moduleName,
293 'constructor'
294 )
295 )
296 .bind(this)
297 ],
298 [
299 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
300 this.ajv
301 .compile(
302 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
303 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
304 moduleName,
305 'constructor'
306 )
307 )
308 .bind(this)
309 ],
310 [
311 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
312 this.ajv
313 .compile(
314 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
315 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
316 moduleName,
317 'constructor'
318 )
319 )
320 .bind(this)
321 ],
322 [
323 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
324 this.ajv
325 .compile(
326 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
327 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
328 moduleName,
329 'constructor'
330 )
331 )
332 .bind(this)
333 ],
334 [
335 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
336 this.ajv
337 .compile(
338 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
339 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
340 moduleName,
341 'constructor'
342 )
343 )
344 .bind(this)
345 ],
346 [
347 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
348 this.ajv
349 .compile(
350 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
351 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
352 moduleName,
353 'constructor'
354 )
355 )
356 .bind(this)
357 ],
358 [
359 OCPP16IncomingRequestCommand.DATA_TRANSFER,
360 this.ajv
361 .compile(
362 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
363 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
364 moduleName,
365 'constructor'
366 )
367 )
368 .bind(this)
369 ],
370 [
371 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
372 this.ajv
373 .compile(
374 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
375 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
376 moduleName,
377 'constructor'
378 )
379 )
380 .bind(this)
381 ],
382 [
383 OCPP16IncomingRequestCommand.RESERVE_NOW,
384 this.ajv
385 .compile(
386 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
387 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
388 moduleName,
389 'constructor'
390 )
391 )
392 .bind(this)
393 ],
394 [
395 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
396 this.ajv
397 .compile(
398 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
399 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
400 moduleName,
401 'constructor'
402 )
403 )
404 .bind(this)
405 ]
406 ])
407 this.on(
408 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
409 (chargingStation: ChargingStation, connectorId: number, idTag: string) => {
410 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
411 chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
412 chargingStation.ocppRequestService
413 .requestHandler<OCPP16StartTransactionRequest, OCPP16StartTransactionResponse>(
414 chargingStation,
415 OCPP16RequestCommand.START_TRANSACTION,
416 {
417 connectorId,
418 idTag
419 }
420 )
421 .then(response => {
422 if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
423 logger.debug(
424 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
425 )
426 } else {
427 logger.debug(
428 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
429 )
430 }
431 })
432 .catch(error => {
433 logger.error(
434 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
435 error
436 )
437 })
438 }
439 )
440 this.validatePayload = this.validatePayload.bind(this)
441 }
442
443 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
444 chargingStation: ChargingStation,
445 messageId: string,
446 commandName: OCPP16IncomingRequestCommand,
447 commandPayload: ReqType
448 ): Promise<void> {
449 let response: ResType
450 if (
451 chargingStation.stationInfo?.ocppStrictCompliance === true &&
452 chargingStation.inPendingState() &&
453 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
454 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
455 ) {
456 throw new OCPPError(
457 ErrorType.SECURITY_ERROR,
458 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
459 commandPayload,
460 undefined,
461 2
462 )} while the charging station is in pending state on the central server`,
463 commandName,
464 commandPayload
465 )
466 }
467 if (
468 chargingStation.isRegistered() ||
469 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
470 chargingStation.inUnknownState())
471 ) {
472 if (
473 this.incomingRequestHandlers.has(commandName) &&
474 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
475 ) {
476 try {
477 this.validatePayload(chargingStation, commandName, commandPayload)
478 // Call the method to build the response
479 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
480 response = (await this.incomingRequestHandlers.get(commandName)!(
481 chargingStation,
482 commandPayload
483 )) as ResType
484 } catch (error) {
485 // Log
486 logger.error(
487 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
488 error
489 )
490 throw error
491 }
492 } else {
493 // Throw exception
494 throw new OCPPError(
495 ErrorType.NOT_IMPLEMENTED,
496 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
497 commandPayload,
498 undefined,
499 2
500 )}`,
501 commandName,
502 commandPayload
503 )
504 }
505 } else {
506 throw new OCPPError(
507 ErrorType.SECURITY_ERROR,
508 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
509 commandPayload,
510 undefined,
511 2
512 )} while the charging station is not registered on the central server.`,
513 commandName,
514 commandPayload
515 )
516 }
517 // Send the built response
518 await chargingStation.ocppRequestService.sendResponse(
519 chargingStation,
520 messageId,
521 response,
522 commandName
523 )
524 }
525
526 private validatePayload (
527 chargingStation: ChargingStation,
528 commandName: OCPP16IncomingRequestCommand,
529 commandPayload: JsonType
530 ): boolean {
531 if (this.payloadValidateFunctions.has(commandName)) {
532 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
533 }
534 logger.warn(
535 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
536 )
537 return false
538 }
539
540 // Simulate charging station restart
541 private handleRequestReset (
542 chargingStation: ChargingStation,
543 commandPayload: ResetRequest
544 ): GenericResponse {
545 const { type } = commandPayload
546 chargingStation
547 .reset(`${type}Reset` as OCPP16StopTransactionReason)
548 .catch(Constants.EMPTY_FUNCTION)
549 logger.info(
550 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
551 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
552 chargingStation.stationInfo!.resetTime!
553 )}`
554 )
555 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
556 }
557
558 private async handleRequestUnlockConnector (
559 chargingStation: ChargingStation,
560 commandPayload: UnlockConnectorRequest
561 ): Promise<UnlockConnectorResponse> {
562 const { connectorId } = commandPayload
563 if (!chargingStation.hasConnector(connectorId)) {
564 logger.error(
565 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
566 )
567 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
568 }
569 if (connectorId === 0) {
570 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
571 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
572 }
573 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
574 const stopResponse = await chargingStation.stopTransactionOnConnector(
575 connectorId,
576 OCPP16StopTransactionReason.UNLOCK_COMMAND
577 )
578 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
579 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
580 }
581 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
582 }
583 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
584 chargingStation,
585 connectorId,
586 OCPP16ChargePointStatus.Available
587 )
588 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
589 }
590
591 private handleRequestGetConfiguration (
592 chargingStation: ChargingStation,
593 commandPayload: GetConfigurationRequest
594 ): GetConfigurationResponse {
595 const { key } = commandPayload
596 const configurationKey: OCPPConfigurationKey[] = []
597 const unknownKey: string[] = []
598 if (key == null) {
599 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
600 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
601 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
602 continue
603 }
604 configurationKey.push({
605 key: configKey.key,
606 readonly: configKey.readonly,
607 value: configKey.value
608 })
609 }
610 } else if (isNotEmptyArray(key)) {
611 for (const k of key) {
612 const keyFound = getConfigurationKey(chargingStation, k, true)
613 if (keyFound != null) {
614 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
615 continue
616 }
617 configurationKey.push({
618 key: keyFound.key,
619 readonly: keyFound.readonly,
620 value: keyFound.value
621 })
622 } else {
623 unknownKey.push(k)
624 }
625 }
626 }
627 return {
628 configurationKey,
629 unknownKey
630 }
631 }
632
633 private handleRequestChangeConfiguration (
634 chargingStation: ChargingStation,
635 commandPayload: ChangeConfigurationRequest
636 ): ChangeConfigurationResponse {
637 const { key, value } = commandPayload
638 const keyToChange = getConfigurationKey(chargingStation, key, true)
639 if (keyToChange?.readonly === true) {
640 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
641 } else if (keyToChange?.readonly === false) {
642 let valueChanged = false
643 if (keyToChange.value !== value) {
644 setConfigurationKeyValue(chargingStation, key, value, true)
645 valueChanged = true
646 }
647 let triggerHeartbeatRestart = false
648 if (
649 (keyToChange.key as OCPP16StandardParametersKey) ===
650 OCPP16StandardParametersKey.HeartBeatInterval &&
651 valueChanged
652 ) {
653 setConfigurationKeyValue(
654 chargingStation,
655 OCPP16StandardParametersKey.HeartbeatInterval,
656 value
657 )
658 triggerHeartbeatRestart = true
659 }
660 if (
661 (keyToChange.key as OCPP16StandardParametersKey) ===
662 OCPP16StandardParametersKey.HeartbeatInterval &&
663 valueChanged
664 ) {
665 setConfigurationKeyValue(
666 chargingStation,
667 OCPP16StandardParametersKey.HeartBeatInterval,
668 value
669 )
670 triggerHeartbeatRestart = true
671 }
672 if (triggerHeartbeatRestart) {
673 chargingStation.restartHeartbeat()
674 }
675 if (
676 (keyToChange.key as OCPP16StandardParametersKey) ===
677 OCPP16StandardParametersKey.WebSocketPingInterval &&
678 valueChanged
679 ) {
680 chargingStation.restartWebSocketPing()
681 }
682 if (keyToChange.reboot === true) {
683 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
684 }
685 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
686 }
687 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
688 }
689
690 private handleRequestSetChargingProfile (
691 chargingStation: ChargingStation,
692 commandPayload: SetChargingProfileRequest
693 ): SetChargingProfileResponse {
694 if (
695 !OCPP16ServiceUtils.checkFeatureProfile(
696 chargingStation,
697 OCPP16SupportedFeatureProfiles.SmartCharging,
698 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
699 )
700 ) {
701 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
702 }
703 const { connectorId, csChargingProfiles } = commandPayload
704 if (!chargingStation.hasConnector(connectorId)) {
705 logger.error(
706 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
707 )
708 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
709 }
710 if (
711 csChargingProfiles.chargingProfilePurpose ===
712 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
713 connectorId !== 0
714 ) {
715 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
716 }
717 if (
718 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
719 connectorId === 0
720 ) {
721 logger.error(
722 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
723 )
724 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
725 }
726 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
727 if (
728 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
729 connectorId > 0 &&
730 connectorStatus?.transactionStarted === false
731 ) {
732 logger.error(
733 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
734 )
735 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
736 }
737 if (
738 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
739 connectorId > 0 &&
740 connectorStatus?.transactionStarted === true &&
741 csChargingProfiles.transactionId !== connectorStatus.transactionId
742 ) {
743 logger.error(
744 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
745 csChargingProfiles.transactionId
746 } than the started transaction id ${connectorStatus.transactionId}`
747 )
748 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
749 }
750 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
751 logger.debug(
752 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
753 csChargingProfiles
754 )
755 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
756 }
757
758 private handleRequestGetCompositeSchedule (
759 chargingStation: ChargingStation,
760 commandPayload: OCPP16GetCompositeScheduleRequest
761 ): OCPP16GetCompositeScheduleResponse {
762 if (
763 !OCPP16ServiceUtils.checkFeatureProfile(
764 chargingStation,
765 OCPP16SupportedFeatureProfiles.SmartCharging,
766 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
767 )
768 ) {
769 return OCPP16Constants.OCPP_RESPONSE_REJECTED
770 }
771 const { connectorId, duration, chargingRateUnit } = commandPayload
772 if (!chargingStation.hasConnector(connectorId)) {
773 logger.error(
774 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
775 )
776 return OCPP16Constants.OCPP_RESPONSE_REJECTED
777 }
778 if (connectorId === 0) {
779 logger.error(
780 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
781 )
782 return OCPP16Constants.OCPP_RESPONSE_REJECTED
783 }
784 if (chargingRateUnit != null) {
785 logger.warn(
786 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
787 )
788 }
789 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
790 if (
791 isEmptyArray(connectorStatus?.chargingProfiles) &&
792 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
793 ) {
794 return OCPP16Constants.OCPP_RESPONSE_REJECTED
795 }
796 const currentDate = new Date()
797 const compositeScheduleInterval: Interval = {
798 start: currentDate,
799 end: addSeconds(currentDate, duration)
800 }
801 // Get charging profiles sorted by connector id then stack level
802 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
803 chargingStation,
804 connectorId
805 )
806 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
807 let compositeSchedule: OCPP16ChargingSchedule | undefined
808 for (const chargingProfile of chargingProfiles) {
809 if (chargingProfile.chargingSchedule.startSchedule == null) {
810 logger.debug(
811 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
812 chargingProfile.chargingProfileId
813 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
814 )
815 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
816 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
817 }
818 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
819 logger.warn(
820 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
821 chargingProfile.chargingProfileId
822 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
823 )
824 chargingProfile.chargingSchedule.startSchedule = convertToDate(
825 chargingProfile.chargingSchedule.startSchedule
826 )
827 }
828 if (chargingProfile.chargingSchedule.duration == null) {
829 logger.debug(
830 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
831 chargingProfile.chargingProfileId
832 } has no duration defined and will be set to the maximum time allowed`
833 )
834 // OCPP specifies that if duration is not defined, it should be infinite
835 chargingProfile.chargingSchedule.duration = differenceInSeconds(
836 maxTime,
837 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
838 chargingProfile.chargingSchedule.startSchedule!
839 )
840 }
841 if (
842 !prepareChargingProfileKind(
843 connectorStatus,
844 chargingProfile,
845 compositeScheduleInterval.start,
846 chargingStation.logPrefix()
847 )
848 ) {
849 continue
850 }
851 if (
852 !canProceedChargingProfile(
853 chargingProfile,
854 compositeScheduleInterval.start,
855 chargingStation.logPrefix()
856 )
857 ) {
858 continue
859 }
860 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
861 previousCompositeSchedule,
862 chargingProfile.chargingSchedule,
863 compositeScheduleInterval
864 )
865 previousCompositeSchedule = compositeSchedule
866 }
867 if (compositeSchedule != null) {
868 return {
869 status: GenericStatus.Accepted,
870 scheduleStart: compositeSchedule.startSchedule,
871 connectorId,
872 chargingSchedule: compositeSchedule
873 }
874 }
875 return OCPP16Constants.OCPP_RESPONSE_REJECTED
876 }
877
878 private handleRequestClearChargingProfile (
879 chargingStation: ChargingStation,
880 commandPayload: OCPP16ClearChargingProfileRequest
881 ): OCPP16ClearChargingProfileResponse {
882 if (
883 !OCPP16ServiceUtils.checkFeatureProfile(
884 chargingStation,
885 OCPP16SupportedFeatureProfiles.SmartCharging,
886 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
887 )
888 ) {
889 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
890 }
891 const { connectorId } = commandPayload
892 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
893 if (!chargingStation.hasConnector(connectorId!)) {
894 logger.error(
895 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
896 )
897 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
898 }
899 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
900 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
901 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
902 connectorStatus.chargingProfiles = []
903 logger.debug(
904 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
905 )
906 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
907 }
908 if (connectorId == null) {
909 let clearedCP = false
910 if (chargingStation.hasEvses) {
911 for (const evseStatus of chargingStation.evses.values()) {
912 for (const status of evseStatus.connectors.values()) {
913 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
914 chargingStation,
915 commandPayload,
916 status.chargingProfiles
917 )
918 }
919 }
920 } else {
921 for (const id of chargingStation.connectors.keys()) {
922 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
923 chargingStation,
924 commandPayload,
925 chargingStation.getConnectorStatus(id)?.chargingProfiles
926 )
927 }
928 }
929 if (clearedCP) {
930 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
931 }
932 }
933 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
934 }
935
936 private async handleRequestChangeAvailability (
937 chargingStation: ChargingStation,
938 commandPayload: OCPP16ChangeAvailabilityRequest
939 ): Promise<OCPP16ChangeAvailabilityResponse> {
940 const { connectorId, type } = commandPayload
941 if (!chargingStation.hasConnector(connectorId)) {
942 logger.error(
943 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
944 )
945 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
946 }
947 const chargePointStatus: OCPP16ChargePointStatus =
948 type === OCPP16AvailabilityType.Operative
949 ? OCPP16ChargePointStatus.Available
950 : OCPP16ChargePointStatus.Unavailable
951 if (connectorId === 0) {
952 let response: OCPP16ChangeAvailabilityResponse | undefined
953 if (chargingStation.hasEvses) {
954 for (const evseStatus of chargingStation.evses.values()) {
955 response = await OCPP16ServiceUtils.changeAvailability(
956 chargingStation,
957 [...evseStatus.connectors.keys()],
958 chargePointStatus,
959 type
960 )
961 }
962 } else {
963 response = await OCPP16ServiceUtils.changeAvailability(
964 chargingStation,
965 [...chargingStation.connectors.keys()],
966 chargePointStatus,
967 type
968 )
969 }
970 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
971 return response!
972 } else if (
973 connectorId > 0 &&
974 (chargingStation.isChargingStationAvailable() ||
975 (!chargingStation.isChargingStationAvailable() &&
976 type === OCPP16AvailabilityType.Inoperative))
977 ) {
978 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
979 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
980 chargingStation.getConnectorStatus(connectorId)!.availability = type
981 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
982 }
983 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
984 chargingStation.getConnectorStatus(connectorId)!.availability = type
985 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
986 chargingStation,
987 connectorId,
988 chargePointStatus
989 )
990 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
991 }
992 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
993 }
994
995 private async handleRequestRemoteStartTransaction (
996 chargingStation: ChargingStation,
997 commandPayload: RemoteStartTransactionRequest
998 ): Promise<GenericResponse> {
999 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1000 if (!chargingStation.hasConnector(transactionConnectorId)) {
1001 return await this.notifyRemoteStartTransactionRejected(
1002 chargingStation,
1003 transactionConnectorId,
1004 idTag
1005 )
1006 }
1007 if (
1008 !chargingStation.isChargingStationAvailable() ||
1009 !chargingStation.isConnectorAvailable(transactionConnectorId)
1010 ) {
1011 return await this.notifyRemoteStartTransactionRejected(
1012 chargingStation,
1013 transactionConnectorId,
1014 idTag
1015 )
1016 }
1017 // idTag authorization check required
1018 if (
1019 chargingStation.getAuthorizeRemoteTxRequests() &&
1020 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
1021 ) {
1022 return await this.notifyRemoteStartTransactionRejected(
1023 chargingStation,
1024 transactionConnectorId,
1025 idTag
1026 )
1027 }
1028 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1029 chargingStation,
1030 transactionConnectorId,
1031 OCPP16ChargePointStatus.Preparing
1032 )
1033 if (
1034 chargingProfile != null &&
1035 !this.setRemoteStartTransactionChargingProfile(
1036 chargingStation,
1037 transactionConnectorId,
1038 chargingProfile
1039 )
1040 ) {
1041 return await this.notifyRemoteStartTransactionRejected(
1042 chargingStation,
1043 transactionConnectorId,
1044 idTag
1045 )
1046 }
1047 this.emit(
1048 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
1049 chargingStation,
1050 transactionConnectorId,
1051 idTag
1052 )
1053 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1054 }
1055
1056 private async notifyRemoteStartTransactionRejected (
1057 chargingStation: ChargingStation,
1058 connectorId: number,
1059 idTag: string
1060 ): Promise<GenericResponse> {
1061 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1062 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
1063 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1064 chargingStation,
1065 connectorId,
1066 OCPP16ChargePointStatus.Available
1067 )
1068 }
1069 logger.warn(
1070 `${chargingStation.logPrefix()} Remote start transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1071 )
1072 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1073 }
1074
1075 private setRemoteStartTransactionChargingProfile (
1076 chargingStation: ChargingStation,
1077 connectorId: number,
1078 chargingProfile: OCPP16ChargingProfile
1079 ): boolean {
1080 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1081 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1082 logger.debug(
1083 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
1084 chargingProfile
1085 )
1086 return true
1087 }
1088 logger.warn(
1089 `${chargingStation.logPrefix()} Not allowed to set ${
1090 chargingProfile.chargingProfilePurpose
1091 } charging profile(s) at remote start transaction`
1092 )
1093 return false
1094 }
1095
1096 private async handleRequestRemoteStopTransaction (
1097 chargingStation: ChargingStation,
1098 commandPayload: RemoteStopTransactionRequest
1099 ): Promise<GenericResponse> {
1100 const { transactionId } = commandPayload
1101 if (chargingStation.hasEvses) {
1102 for (const [evseId, evseStatus] of chargingStation.evses) {
1103 if (evseId > 0) {
1104 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1105 if (connectorStatus.transactionId === transactionId) {
1106 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1107 }
1108 }
1109 }
1110 }
1111 } else {
1112 for (const connectorId of chargingStation.connectors.keys()) {
1113 if (
1114 connectorId > 0 &&
1115 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1116 ) {
1117 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1118 }
1119 }
1120 }
1121 logger.warn(
1122 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1123 )
1124 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1125 }
1126
1127 private handleRequestUpdateFirmware (
1128 chargingStation: ChargingStation,
1129 commandPayload: OCPP16UpdateFirmwareRequest
1130 ): OCPP16UpdateFirmwareResponse {
1131 if (
1132 !OCPP16ServiceUtils.checkFeatureProfile(
1133 chargingStation,
1134 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1135 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1136 )
1137 ) {
1138 logger.warn(
1139 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1140 )
1141 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1142 }
1143 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1144 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1145 const { retrieveDate } = commandPayload
1146 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
1147 logger.warn(
1148 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1149 )
1150 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1151 }
1152 const now = Date.now()
1153 if (retrieveDate.getTime() <= now) {
1154 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1155 } else {
1156 setTimeout(() => {
1157 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1158 }, retrieveDate.getTime() - now)
1159 }
1160 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1161 }
1162
1163 private async updateFirmwareSimulation (
1164 chargingStation: ChargingStation,
1165 maxDelay = 30,
1166 minDelay = 15
1167 ): Promise<void> {
1168 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1169 return
1170 }
1171 if (chargingStation.hasEvses) {
1172 for (const [evseId, evseStatus] of chargingStation.evses) {
1173 if (evseId > 0) {
1174 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1175 if (connectorStatus.transactionStarted === false) {
1176 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1177 chargingStation,
1178 connectorId,
1179 OCPP16ChargePointStatus.Unavailable
1180 )
1181 }
1182 }
1183 }
1184 }
1185 } else {
1186 for (const connectorId of chargingStation.connectors.keys()) {
1187 if (
1188 connectorId > 0 &&
1189 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1190 ) {
1191 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1192 chargingStation,
1193 connectorId,
1194 OCPP16ChargePointStatus.Unavailable
1195 )
1196 }
1197 }
1198 }
1199 await chargingStation.ocppRequestService.requestHandler<
1200 OCPP16FirmwareStatusNotificationRequest,
1201 OCPP16FirmwareStatusNotificationResponse
1202 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1203 status: OCPP16FirmwareStatus.Downloading
1204 })
1205 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1206 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1207 if (
1208 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1209 OCPP16FirmwareStatus.DownloadFailed
1210 ) {
1211 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1212 await chargingStation.ocppRequestService.requestHandler<
1213 OCPP16FirmwareStatusNotificationRequest,
1214 OCPP16FirmwareStatusNotificationResponse
1215 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1216 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1217 })
1218 chargingStation.stationInfo.firmwareStatus =
1219 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1220 return
1221 }
1222 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1223 await chargingStation.ocppRequestService.requestHandler<
1224 OCPP16FirmwareStatusNotificationRequest,
1225 OCPP16FirmwareStatusNotificationResponse
1226 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1227 status: OCPP16FirmwareStatus.Downloaded
1228 })
1229 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1230 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1231 let wasTransactionsStarted = false
1232 let transactionsStarted: boolean
1233 do {
1234 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1235 if (runningTransactions > 0) {
1236 const waitTime = secondsToMilliseconds(15)
1237 logger.debug(
1238 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1239 waitTime
1240 )} before continuing firmware update simulation`
1241 )
1242 await sleep(waitTime)
1243 transactionsStarted = true
1244 wasTransactionsStarted = true
1245 } else {
1246 if (chargingStation.hasEvses) {
1247 for (const [evseId, evseStatus] of chargingStation.evses) {
1248 if (evseId > 0) {
1249 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1250 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1251 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1252 chargingStation,
1253 connectorId,
1254 OCPP16ChargePointStatus.Unavailable
1255 )
1256 }
1257 }
1258 }
1259 }
1260 } else {
1261 for (const connectorId of chargingStation.connectors.keys()) {
1262 if (
1263 connectorId > 0 &&
1264 chargingStation.getConnectorStatus(connectorId)?.status !==
1265 OCPP16ChargePointStatus.Unavailable
1266 ) {
1267 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1268 chargingStation,
1269 connectorId,
1270 OCPP16ChargePointStatus.Unavailable
1271 )
1272 }
1273 }
1274 }
1275 transactionsStarted = false
1276 }
1277 } while (transactionsStarted)
1278 !wasTransactionsStarted &&
1279 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1280 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1281 return
1282 }
1283 await chargingStation.ocppRequestService.requestHandler<
1284 OCPP16FirmwareStatusNotificationRequest,
1285 OCPP16FirmwareStatusNotificationResponse
1286 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1287 status: OCPP16FirmwareStatus.Installing
1288 })
1289 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1290 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1291 if (
1292 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1293 OCPP16FirmwareStatus.InstallationFailed
1294 ) {
1295 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1296 await chargingStation.ocppRequestService.requestHandler<
1297 OCPP16FirmwareStatusNotificationRequest,
1298 OCPP16FirmwareStatusNotificationResponse
1299 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1300 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1301 })
1302 chargingStation.stationInfo.firmwareStatus =
1303 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1304 return
1305 }
1306 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1307 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1308 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1309 }
1310 }
1311
1312 private async handleRequestGetDiagnostics (
1313 chargingStation: ChargingStation,
1314 commandPayload: GetDiagnosticsRequest
1315 ): Promise<GetDiagnosticsResponse> {
1316 if (
1317 !OCPP16ServiceUtils.checkFeatureProfile(
1318 chargingStation,
1319 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1320 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1321 )
1322 ) {
1323 logger.warn(
1324 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1325 )
1326 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1327 }
1328 const { location } = commandPayload
1329 const uri = new URL(location)
1330 if (uri.protocol.startsWith('ftp:')) {
1331 let ftpClient: Client | undefined
1332 try {
1333 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1334 .filter(file => file.endsWith('.log'))
1335 .map(file => join('./', file))
1336 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1337 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1338 ftpClient = new Client()
1339 const accessResponse = await ftpClient.access({
1340 host: uri.host,
1341 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1342 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1343 ...(isNotEmptyString(uri.password) && { password: uri.password })
1344 })
1345 let uploadResponse: FTPResponse | undefined
1346 if (accessResponse.code === 220) {
1347 ftpClient.trackProgress(info => {
1348 logger.info(
1349 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1350 info.bytes / 1024
1351 } bytes transferred from diagnostics archive ${info.name}`
1352 )
1353 chargingStation.ocppRequestService
1354 .requestHandler<
1355 OCPP16DiagnosticsStatusNotificationRequest,
1356 OCPP16DiagnosticsStatusNotificationResponse
1357 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1358 status: OCPP16DiagnosticsStatus.Uploading
1359 })
1360 .catch(error => {
1361 logger.error(
1362 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1363 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1364 }'`,
1365 error
1366 )
1367 })
1368 })
1369 uploadResponse = await ftpClient.uploadFrom(
1370 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1371 `${uri.pathname}${diagnosticsArchive}`
1372 )
1373 if (uploadResponse.code === 226) {
1374 await chargingStation.ocppRequestService.requestHandler<
1375 OCPP16DiagnosticsStatusNotificationRequest,
1376 OCPP16DiagnosticsStatusNotificationResponse
1377 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1378 status: OCPP16DiagnosticsStatus.Uploaded
1379 })
1380 ftpClient.close()
1381 return { fileName: diagnosticsArchive }
1382 }
1383 throw new OCPPError(
1384 ErrorType.GENERIC_ERROR,
1385 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1386 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1387 )
1388 }
1389 throw new OCPPError(
1390 ErrorType.GENERIC_ERROR,
1391 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1392 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1393 )
1394 } catch (error) {
1395 await chargingStation.ocppRequestService.requestHandler<
1396 OCPP16DiagnosticsStatusNotificationRequest,
1397 OCPP16DiagnosticsStatusNotificationResponse
1398 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1399 status: OCPP16DiagnosticsStatus.UploadFailed
1400 })
1401 ftpClient?.close()
1402 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1403 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1404 chargingStation,
1405 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1406 error as Error,
1407 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1408 )!
1409 }
1410 } else {
1411 logger.error(
1412 `${chargingStation.logPrefix()} Unsupported protocol ${
1413 uri.protocol
1414 } to transfer the diagnostic logs archive`
1415 )
1416 await chargingStation.ocppRequestService.requestHandler<
1417 OCPP16DiagnosticsStatusNotificationRequest,
1418 OCPP16DiagnosticsStatusNotificationResponse
1419 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1420 status: OCPP16DiagnosticsStatus.UploadFailed
1421 })
1422 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1423 }
1424 }
1425
1426 private handleRequestTriggerMessage (
1427 chargingStation: ChargingStation,
1428 commandPayload: OCPP16TriggerMessageRequest
1429 ): OCPP16TriggerMessageResponse {
1430 const { requestedMessage, connectorId } = commandPayload
1431 if (
1432 !OCPP16ServiceUtils.checkFeatureProfile(
1433 chargingStation,
1434 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1435 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1436 ) ||
1437 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1438 ) {
1439 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1440 }
1441 if (
1442 !OCPP16ServiceUtils.isConnectorIdValid(
1443 chargingStation,
1444 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1445 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1446 connectorId!
1447 )
1448 ) {
1449 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1450 }
1451 try {
1452 switch (requestedMessage) {
1453 case OCPP16MessageTrigger.BootNotification:
1454 setTimeout(() => {
1455 chargingStation.ocppRequestService
1456 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1457 chargingStation,
1458 OCPP16RequestCommand.BOOT_NOTIFICATION,
1459 chargingStation.bootNotificationRequest,
1460 { skipBufferingOnError: true, triggerMessage: true }
1461 )
1462 .then(response => {
1463 chargingStation.bootNotificationResponse = response
1464 })
1465 .catch(Constants.EMPTY_FUNCTION)
1466 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1467 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1468 case OCPP16MessageTrigger.Heartbeat:
1469 setTimeout(() => {
1470 chargingStation.ocppRequestService
1471 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1472 chargingStation,
1473 OCPP16RequestCommand.HEARTBEAT,
1474 undefined,
1475 {
1476 triggerMessage: true
1477 }
1478 )
1479 .catch(Constants.EMPTY_FUNCTION)
1480 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1481 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1482 case OCPP16MessageTrigger.StatusNotification:
1483 setTimeout(() => {
1484 if (connectorId != null) {
1485 chargingStation.ocppRequestService
1486 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1487 chargingStation,
1488 OCPP16RequestCommand.STATUS_NOTIFICATION,
1489 {
1490 connectorId,
1491 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1492 status: chargingStation.getConnectorStatus(connectorId)?.status
1493 },
1494 {
1495 triggerMessage: true
1496 }
1497 )
1498 .catch(Constants.EMPTY_FUNCTION)
1499 } else if (chargingStation.hasEvses) {
1500 for (const evseStatus of chargingStation.evses.values()) {
1501 for (const [id, connectorStatus] of evseStatus.connectors) {
1502 chargingStation.ocppRequestService
1503 .requestHandler<
1504 OCPP16StatusNotificationRequest,
1505 OCPP16StatusNotificationResponse
1506 >(
1507 chargingStation,
1508 OCPP16RequestCommand.STATUS_NOTIFICATION,
1509 {
1510 connectorId: id,
1511 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1512 status: connectorStatus.status
1513 },
1514 {
1515 triggerMessage: true
1516 }
1517 )
1518 .catch(Constants.EMPTY_FUNCTION)
1519 }
1520 }
1521 } else {
1522 for (const [id, connectorStatus] of chargingStation.connectors) {
1523 chargingStation.ocppRequestService
1524 .requestHandler<
1525 OCPP16StatusNotificationRequest,
1526 OCPP16StatusNotificationResponse
1527 >(
1528 chargingStation,
1529 OCPP16RequestCommand.STATUS_NOTIFICATION,
1530 {
1531 connectorId: id,
1532 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1533 status: connectorStatus.status
1534 },
1535 {
1536 triggerMessage: true
1537 }
1538 )
1539 .catch(Constants.EMPTY_FUNCTION)
1540 }
1541 }
1542 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1543 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1544 default:
1545 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1546 }
1547 } catch (error) {
1548 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1549 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1550 chargingStation,
1551 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1552 error as Error,
1553 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1554 )!
1555 }
1556 }
1557
1558 private handleRequestDataTransfer (
1559 chargingStation: ChargingStation,
1560 commandPayload: OCPP16DataTransferRequest
1561 ): OCPP16DataTransferResponse {
1562 const { vendorId } = commandPayload
1563 try {
1564 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1565 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1566 }
1567 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1568 } catch (error) {
1569 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1570 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1571 chargingStation,
1572 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1573 error as Error,
1574 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1575 )!
1576 }
1577 }
1578
1579 private async handleRequestReserveNow (
1580 chargingStation: ChargingStation,
1581 commandPayload: OCPP16ReserveNowRequest
1582 ): Promise<OCPP16ReserveNowResponse> {
1583 if (
1584 !OCPP16ServiceUtils.checkFeatureProfile(
1585 chargingStation,
1586 OCPP16SupportedFeatureProfiles.Reservation,
1587 OCPP16IncomingRequestCommand.RESERVE_NOW
1588 )
1589 ) {
1590 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1591 }
1592 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1593 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1594 const { reservationId, idTag, connectorId } = commandPayload
1595 let response: OCPP16ReserveNowResponse
1596 try {
1597 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1598 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1599 }
1600 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1601 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1602 }
1603 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1604 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1605 }
1606 await removeExpiredReservations(chargingStation)
1607 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
1608 case OCPP16ChargePointStatus.Faulted:
1609 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1610 break
1611 case OCPP16ChargePointStatus.Preparing:
1612 case OCPP16ChargePointStatus.Charging:
1613 case OCPP16ChargePointStatus.SuspendedEV:
1614 case OCPP16ChargePointStatus.SuspendedEVSE:
1615 case OCPP16ChargePointStatus.Finishing:
1616 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1617 break
1618 case OCPP16ChargePointStatus.Unavailable:
1619 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1620 break
1621 case OCPP16ChargePointStatus.Reserved:
1622 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1623 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1624 break
1625 }
1626 // eslint-disable-next-line no-fallthrough
1627 default:
1628 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1629 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1630 break
1631 }
1632 await chargingStation.addReservation({
1633 id: commandPayload.reservationId,
1634 ...commandPayload
1635 })
1636 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1637 break
1638 }
1639 return response
1640 } catch (error) {
1641 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1642 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1643 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1644 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1645 chargingStation,
1646 OCPP16IncomingRequestCommand.RESERVE_NOW,
1647 error as Error,
1648 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1649 )!
1650 }
1651 }
1652
1653 private async handleRequestCancelReservation (
1654 chargingStation: ChargingStation,
1655 commandPayload: OCPP16CancelReservationRequest
1656 ): Promise<GenericResponse> {
1657 if (
1658 !OCPP16ServiceUtils.checkFeatureProfile(
1659 chargingStation,
1660 OCPP16SupportedFeatureProfiles.Reservation,
1661 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1662 )
1663 ) {
1664 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1665 }
1666 try {
1667 const { reservationId } = commandPayload
1668 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1669 if (reservation == null) {
1670 logger.debug(
1671 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1672 )
1673 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1674 }
1675 await chargingStation.removeReservation(
1676 reservation,
1677 ReservationTerminationReason.RESERVATION_CANCELED
1678 )
1679 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1680 } catch (error) {
1681 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1682 return this.handleIncomingRequestError<GenericResponse>(
1683 chargingStation,
1684 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1685 error as Error,
1686 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1687 )!
1688 }
1689 }
1690 }