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