fix: send preparing connector status before `StartTransaction`
[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 this.notifyRemoteStartTransactionRejected(
1157 chargingStation,
1158 transactionConnectorId,
1159 idTag
1160 )
1161 }
1162 if (
1163 !chargingStation.isChargingStationAvailable() ||
1164 !chargingStation.isConnectorAvailable(transactionConnectorId)
1165 ) {
1166 return 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 this.notifyRemoteStartTransactionRejected(
1178 chargingStation,
1179 transactionConnectorId,
1180 idTag
1181 )
1182 }
1183 if (
1184 chargingProfile != null &&
1185 !this.setRemoteStartTransactionChargingProfile(
1186 chargingStation,
1187 transactionConnectorId,
1188 chargingProfile
1189 )
1190 ) {
1191 return this.notifyRemoteStartTransactionRejected(
1192 chargingStation,
1193 transactionConnectorId,
1194 idTag
1195 )
1196 }
1197 logger.debug(
1198 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${transactionConnectorId}}, idTag '${idTag}'`
1199 )
1200 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1201 }
1202
1203 private notifyRemoteStartTransactionRejected (
1204 chargingStation: ChargingStation,
1205 connectorId: number,
1206 idTag: string
1207 ): GenericResponse {
1208 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1209 logger.debug(
1210 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1211 )
1212 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1213 }
1214
1215 private setRemoteStartTransactionChargingProfile (
1216 chargingStation: ChargingStation,
1217 connectorId: number,
1218 chargingProfile: OCPP16ChargingProfile
1219 ): boolean {
1220 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1221 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1222 logger.debug(
1223 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${chargingStation.stationInfo?.chargingStationId}#${connectorId}`,
1224 chargingProfile
1225 )
1226 return true
1227 }
1228 logger.debug(
1229 `${chargingStation.logPrefix()} Not allowed to set ${
1230 chargingProfile.chargingProfilePurpose
1231 } charging profile(s) at remote start transaction`
1232 )
1233 return false
1234 }
1235
1236 private handleRequestRemoteStopTransaction (
1237 chargingStation: ChargingStation,
1238 commandPayload: RemoteStopTransactionRequest
1239 ): GenericResponse {
1240 const { transactionId } = commandPayload
1241 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1242 logger.debug(
1243 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1244 )
1245 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1246 }
1247 logger.debug(
1248 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
1249 )
1250 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1251 }
1252
1253 private handleRequestUpdateFirmware (
1254 chargingStation: ChargingStation,
1255 commandPayload: OCPP16UpdateFirmwareRequest
1256 ): OCPP16UpdateFirmwareResponse {
1257 if (
1258 !OCPP16ServiceUtils.checkFeatureProfile(
1259 chargingStation,
1260 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1261 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1262 )
1263 ) {
1264 logger.warn(
1265 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1266 )
1267 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1268 }
1269 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1270 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1271 const { retrieveDate } = commandPayload
1272 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
1273 logger.warn(
1274 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1275 )
1276 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1277 }
1278 const now = Date.now()
1279 if (retrieveDate.getTime() <= now) {
1280 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1281 } else {
1282 setTimeout(() => {
1283 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1284 }, retrieveDate.getTime() - now)
1285 }
1286 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1287 }
1288
1289 private async updateFirmwareSimulation (
1290 chargingStation: ChargingStation,
1291 maxDelay = 30,
1292 minDelay = 15
1293 ): Promise<void> {
1294 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1295 return
1296 }
1297 if (chargingStation.hasEvses) {
1298 for (const [evseId, evseStatus] of chargingStation.evses) {
1299 if (evseId > 0) {
1300 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1301 if (connectorStatus.transactionStarted === false) {
1302 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1303 chargingStation,
1304 connectorId,
1305 OCPP16ChargePointStatus.Unavailable
1306 )
1307 }
1308 }
1309 }
1310 }
1311 } else {
1312 for (const connectorId of chargingStation.connectors.keys()) {
1313 if (
1314 connectorId > 0 &&
1315 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1316 ) {
1317 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1318 chargingStation,
1319 connectorId,
1320 OCPP16ChargePointStatus.Unavailable
1321 )
1322 }
1323 }
1324 }
1325 await chargingStation.ocppRequestService.requestHandler<
1326 OCPP16FirmwareStatusNotificationRequest,
1327 OCPP16FirmwareStatusNotificationResponse
1328 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1329 status: OCPP16FirmwareStatus.Downloading
1330 })
1331 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1332 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1333 if (
1334 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1335 OCPP16FirmwareStatus.DownloadFailed
1336 ) {
1337 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1338 await chargingStation.ocppRequestService.requestHandler<
1339 OCPP16FirmwareStatusNotificationRequest,
1340 OCPP16FirmwareStatusNotificationResponse
1341 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1342 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1343 })
1344 chargingStation.stationInfo.firmwareStatus =
1345 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1346 return
1347 }
1348 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1349 await chargingStation.ocppRequestService.requestHandler<
1350 OCPP16FirmwareStatusNotificationRequest,
1351 OCPP16FirmwareStatusNotificationResponse
1352 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1353 status: OCPP16FirmwareStatus.Downloaded
1354 })
1355 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1356 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1357 let wasTransactionsStarted = false
1358 let transactionsStarted: boolean
1359 do {
1360 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1361 if (runningTransactions > 0) {
1362 const waitTime = secondsToMilliseconds(15)
1363 logger.debug(
1364 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1365 waitTime
1366 )} before continuing firmware update simulation`
1367 )
1368 await sleep(waitTime)
1369 transactionsStarted = true
1370 wasTransactionsStarted = true
1371 } else {
1372 if (chargingStation.hasEvses) {
1373 for (const [evseId, evseStatus] of chargingStation.evses) {
1374 if (evseId > 0) {
1375 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1376 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1377 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1378 chargingStation,
1379 connectorId,
1380 OCPP16ChargePointStatus.Unavailable
1381 )
1382 }
1383 }
1384 }
1385 }
1386 } else {
1387 for (const connectorId of chargingStation.connectors.keys()) {
1388 if (
1389 connectorId > 0 &&
1390 chargingStation.getConnectorStatus(connectorId)?.status !==
1391 OCPP16ChargePointStatus.Unavailable
1392 ) {
1393 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1394 chargingStation,
1395 connectorId,
1396 OCPP16ChargePointStatus.Unavailable
1397 )
1398 }
1399 }
1400 }
1401 transactionsStarted = false
1402 }
1403 } while (transactionsStarted)
1404 !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
1405 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1406 return
1407 }
1408 await chargingStation.ocppRequestService.requestHandler<
1409 OCPP16FirmwareStatusNotificationRequest,
1410 OCPP16FirmwareStatusNotificationResponse
1411 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1412 status: OCPP16FirmwareStatus.Installing
1413 })
1414 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1415 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1416 if (
1417 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1418 OCPP16FirmwareStatus.InstallationFailed
1419 ) {
1420 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1421 await chargingStation.ocppRequestService.requestHandler<
1422 OCPP16FirmwareStatusNotificationRequest,
1423 OCPP16FirmwareStatusNotificationResponse
1424 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1425 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1426 })
1427 chargingStation.stationInfo.firmwareStatus =
1428 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1429 return
1430 }
1431 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1432 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1433 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1434 }
1435 }
1436
1437 private async handleRequestGetDiagnostics (
1438 chargingStation: ChargingStation,
1439 commandPayload: GetDiagnosticsRequest
1440 ): Promise<GetDiagnosticsResponse> {
1441 if (
1442 !OCPP16ServiceUtils.checkFeatureProfile(
1443 chargingStation,
1444 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1445 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1446 )
1447 ) {
1448 logger.warn(
1449 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1450 )
1451 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1452 }
1453 const { location } = commandPayload
1454 const uri = new URL(location)
1455 if (uri.protocol.startsWith('ftp:')) {
1456 let ftpClient: Client | undefined
1457 try {
1458 const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
1459 ConfigurationSection.log
1460 )
1461 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1462 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1463 .filter(file => file.endsWith(extname(logConfiguration.file!)))
1464 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1465 .map(file => join(dirname(logConfiguration.file!), file))
1466 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1467 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1468 ftpClient = new Client()
1469 const accessResponse = await ftpClient.access({
1470 host: uri.hostname,
1471 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1472 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1473 ...(isNotEmptyString(uri.password) && { password: uri.password })
1474 })
1475 let uploadResponse: FTPResponse | undefined
1476 if (accessResponse.code === 220) {
1477 ftpClient.trackProgress(info => {
1478 logger.info(
1479 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1480 info.bytes / 1024
1481 } bytes transferred from diagnostics archive ${info.name}`
1482 )
1483 chargingStation.ocppRequestService
1484 .requestHandler<
1485 OCPP16DiagnosticsStatusNotificationRequest,
1486 OCPP16DiagnosticsStatusNotificationResponse
1487 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1488 status: OCPP16DiagnosticsStatus.Uploading
1489 })
1490 .catch((error: unknown) => {
1491 logger.error(
1492 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1493 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1494 }'`,
1495 error
1496 )
1497 })
1498 })
1499 uploadResponse = await ftpClient.uploadFrom(
1500 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1501 `${uri.pathname}${diagnosticsArchive}`
1502 )
1503 if (uploadResponse.code === 226) {
1504 await chargingStation.ocppRequestService.requestHandler<
1505 OCPP16DiagnosticsStatusNotificationRequest,
1506 OCPP16DiagnosticsStatusNotificationResponse
1507 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1508 status: OCPP16DiagnosticsStatus.Uploaded
1509 })
1510 ftpClient.close()
1511 return { fileName: diagnosticsArchive }
1512 }
1513 throw new OCPPError(
1514 ErrorType.GENERIC_ERROR,
1515 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1516 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1517 )
1518 }
1519 throw new OCPPError(
1520 ErrorType.GENERIC_ERROR,
1521 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1522 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1523 )
1524 } catch (error) {
1525 await chargingStation.ocppRequestService.requestHandler<
1526 OCPP16DiagnosticsStatusNotificationRequest,
1527 OCPP16DiagnosticsStatusNotificationResponse
1528 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1529 status: OCPP16DiagnosticsStatus.UploadFailed
1530 })
1531 ftpClient?.close()
1532 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1533 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1534 chargingStation,
1535 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1536 error as Error,
1537 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1538 )!
1539 }
1540 } else {
1541 logger.error(
1542 `${chargingStation.logPrefix()} Unsupported protocol ${
1543 uri.protocol
1544 } to transfer the diagnostic logs archive`
1545 )
1546 await chargingStation.ocppRequestService.requestHandler<
1547 OCPP16DiagnosticsStatusNotificationRequest,
1548 OCPP16DiagnosticsStatusNotificationResponse
1549 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1550 status: OCPP16DiagnosticsStatus.UploadFailed
1551 })
1552 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1553 }
1554 }
1555
1556 private handleRequestTriggerMessage (
1557 chargingStation: ChargingStation,
1558 commandPayload: OCPP16TriggerMessageRequest
1559 ): OCPP16TriggerMessageResponse {
1560 const { requestedMessage, connectorId } = commandPayload
1561 if (
1562 !OCPP16ServiceUtils.checkFeatureProfile(
1563 chargingStation,
1564 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1565 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1566 ) ||
1567 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1568 ) {
1569 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1570 }
1571 if (
1572 !OCPP16ServiceUtils.isConnectorIdValid(
1573 chargingStation,
1574 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1575 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1576 connectorId!
1577 )
1578 ) {
1579 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1580 }
1581 switch (requestedMessage) {
1582 case OCPP16MessageTrigger.BootNotification:
1583 case OCPP16MessageTrigger.Heartbeat:
1584 case OCPP16MessageTrigger.StatusNotification:
1585 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1586 default:
1587 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1588 }
1589 }
1590
1591 private handleRequestDataTransfer (
1592 chargingStation: ChargingStation,
1593 commandPayload: OCPP16DataTransferRequest
1594 ): OCPP16DataTransferResponse {
1595 const { vendorId } = commandPayload
1596 try {
1597 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1598 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1599 }
1600 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1601 } catch (error) {
1602 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1603 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1604 chargingStation,
1605 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1606 error as Error,
1607 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1608 )!
1609 }
1610 }
1611
1612 private async handleRequestReserveNow (
1613 chargingStation: ChargingStation,
1614 commandPayload: OCPP16ReserveNowRequest
1615 ): Promise<OCPP16ReserveNowResponse> {
1616 if (
1617 !OCPP16ServiceUtils.checkFeatureProfile(
1618 chargingStation,
1619 OCPP16SupportedFeatureProfiles.Reservation,
1620 OCPP16IncomingRequestCommand.RESERVE_NOW
1621 )
1622 ) {
1623 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1624 }
1625 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1626 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1627 const { reservationId, idTag, connectorId } = commandPayload
1628 let response: OCPP16ReserveNowResponse
1629 try {
1630 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1631 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1632 }
1633 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1634 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1635 }
1636 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1637 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1638 }
1639 await removeExpiredReservations(chargingStation)
1640 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
1641 case OCPP16ChargePointStatus.Faulted:
1642 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1643 break
1644 case OCPP16ChargePointStatus.Preparing:
1645 case OCPP16ChargePointStatus.Charging:
1646 case OCPP16ChargePointStatus.SuspendedEV:
1647 case OCPP16ChargePointStatus.SuspendedEVSE:
1648 case OCPP16ChargePointStatus.Finishing:
1649 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1650 break
1651 case OCPP16ChargePointStatus.Unavailable:
1652 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1653 break
1654 case OCPP16ChargePointStatus.Reserved:
1655 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1656 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1657 break
1658 }
1659 // eslint-disable-next-line no-fallthrough
1660 default:
1661 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1662 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1663 break
1664 }
1665 await chargingStation.addReservation({
1666 id: commandPayload.reservationId,
1667 ...commandPayload
1668 })
1669 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1670 break
1671 }
1672 return response
1673 } catch (error) {
1674 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1675 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1676 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1677 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1678 chargingStation,
1679 OCPP16IncomingRequestCommand.RESERVE_NOW,
1680 error as Error,
1681 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1682 )!
1683 }
1684 }
1685
1686 private async handleRequestCancelReservation (
1687 chargingStation: ChargingStation,
1688 commandPayload: OCPP16CancelReservationRequest
1689 ): Promise<GenericResponse> {
1690 if (
1691 !OCPP16ServiceUtils.checkFeatureProfile(
1692 chargingStation,
1693 OCPP16SupportedFeatureProfiles.Reservation,
1694 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1695 )
1696 ) {
1697 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1698 }
1699 try {
1700 const { reservationId } = commandPayload
1701 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1702 if (reservation == null) {
1703 logger.debug(
1704 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1705 )
1706 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1707 }
1708 await chargingStation.removeReservation(
1709 reservation,
1710 ReservationTerminationReason.RESERVATION_CANCELED
1711 )
1712 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1713 } catch (error) {
1714 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1715 return this.handleIncomingRequestError<GenericResponse>(
1716 chargingStation,
1717 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1718 error as Error,
1719 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1720 )!
1721 }
1722 }
1723 }