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