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