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