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