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