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