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