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