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