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