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