9e0fed9d087a1ac26c89f52c7348bb8259c6fc84
[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 // idTag authorization check required
985 if (
986 chargingStation.getAuthorizeRemoteTxRequests() &&
987 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
988 ) {
989 return await this.notifyRemoteStartTransactionRejected(
990 chargingStation,
991 transactionConnectorId,
992 idTag
993 )
994 }
995 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
996 chargingStation,
997 transactionConnectorId,
998 OCPP16ChargePointStatus.Preparing
999 )
1000 if (
1001 chargingProfile != null &&
1002 !this.setRemoteStartTransactionChargingProfile(
1003 chargingStation,
1004 transactionConnectorId,
1005 chargingProfile
1006 )
1007 ) {
1008 return await this.notifyRemoteStartTransactionRejected(
1009 chargingStation,
1010 transactionConnectorId,
1011 idTag
1012 )
1013 }
1014 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1015 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionRemoteStarted = true
1016 if (
1017 (
1018 await chargingStation.ocppRequestService.requestHandler<
1019 OCPP16StartTransactionRequest,
1020 OCPP16StartTransactionResponse
1021 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1022 connectorId: transactionConnectorId,
1023 idTag
1024 })
1025 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
1026 ) {
1027 logger.debug(`
1028 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
1029 chargingStation.stationInfo?.chargingStationId
1030 }#${transactionConnectorId} for idTag '${idTag}'`)
1031 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1032 }
1033 return await this.notifyRemoteStartTransactionRejected(
1034 chargingStation,
1035 transactionConnectorId,
1036 idTag
1037 )
1038 }
1039
1040 private async notifyRemoteStartTransactionRejected (
1041 chargingStation: ChargingStation,
1042 connectorId: number,
1043 idTag: string
1044 ): Promise<GenericResponse> {
1045 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1046 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
1047 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1048 chargingStation,
1049 connectorId,
1050 OCPP16ChargePointStatus.Available
1051 )
1052 }
1053 logger.warn(
1054 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1055 )
1056 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1057 }
1058
1059 private setRemoteStartTransactionChargingProfile (
1060 chargingStation: ChargingStation,
1061 connectorId: number,
1062 chargingProfile: OCPP16ChargingProfile
1063 ): boolean {
1064 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1065 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1066 logger.debug(
1067 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
1068 chargingProfile
1069 )
1070 return true
1071 }
1072 logger.warn(
1073 `${chargingStation.logPrefix()} Not allowed to set ${
1074 chargingProfile.chargingProfilePurpose
1075 } charging profile(s) at remote start transaction`
1076 )
1077 return false
1078 }
1079
1080 private async handleRequestRemoteStopTransaction (
1081 chargingStation: ChargingStation,
1082 commandPayload: RemoteStopTransactionRequest
1083 ): Promise<GenericResponse> {
1084 const { transactionId } = commandPayload
1085 if (chargingStation.hasEvses) {
1086 for (const [evseId, evseStatus] of chargingStation.evses) {
1087 if (evseId > 0) {
1088 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1089 if (connectorStatus.transactionId === transactionId) {
1090 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1091 }
1092 }
1093 }
1094 }
1095 } else {
1096 for (const connectorId of chargingStation.connectors.keys()) {
1097 if (
1098 connectorId > 0 &&
1099 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1100 ) {
1101 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1102 }
1103 }
1104 }
1105 logger.warn(
1106 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1107 )
1108 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1109 }
1110
1111 private handleRequestUpdateFirmware (
1112 chargingStation: ChargingStation,
1113 commandPayload: OCPP16UpdateFirmwareRequest
1114 ): OCPP16UpdateFirmwareResponse {
1115 if (
1116 !OCPP16ServiceUtils.checkFeatureProfile(
1117 chargingStation,
1118 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1119 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1120 )
1121 ) {
1122 logger.warn(
1123 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1124 )
1125 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1126 }
1127 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1128 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1129 const { retrieveDate } = commandPayload
1130 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
1131 logger.warn(
1132 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1133 )
1134 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1135 }
1136 const now = Date.now()
1137 if (retrieveDate.getTime() <= now) {
1138 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1139 } else {
1140 setTimeout(() => {
1141 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1142 }, retrieveDate.getTime() - now)
1143 }
1144 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1145 }
1146
1147 private async updateFirmwareSimulation (
1148 chargingStation: ChargingStation,
1149 maxDelay = 30,
1150 minDelay = 15
1151 ): Promise<void> {
1152 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1153 return
1154 }
1155 if (chargingStation.hasEvses) {
1156 for (const [evseId, evseStatus] of chargingStation.evses) {
1157 if (evseId > 0) {
1158 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1159 if (connectorStatus.transactionStarted === false) {
1160 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1161 chargingStation,
1162 connectorId,
1163 OCPP16ChargePointStatus.Unavailable
1164 )
1165 }
1166 }
1167 }
1168 }
1169 } else {
1170 for (const connectorId of chargingStation.connectors.keys()) {
1171 if (
1172 connectorId > 0 &&
1173 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1174 ) {
1175 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1176 chargingStation,
1177 connectorId,
1178 OCPP16ChargePointStatus.Unavailable
1179 )
1180 }
1181 }
1182 }
1183 await chargingStation.ocppRequestService.requestHandler<
1184 OCPP16FirmwareStatusNotificationRequest,
1185 OCPP16FirmwareStatusNotificationResponse
1186 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1187 status: OCPP16FirmwareStatus.Downloading
1188 })
1189 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1190 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1191 if (
1192 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1193 OCPP16FirmwareStatus.DownloadFailed
1194 ) {
1195 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1196 await chargingStation.ocppRequestService.requestHandler<
1197 OCPP16FirmwareStatusNotificationRequest,
1198 OCPP16FirmwareStatusNotificationResponse
1199 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1200 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1201 })
1202 chargingStation.stationInfo.firmwareStatus =
1203 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1204 return
1205 }
1206 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1207 await chargingStation.ocppRequestService.requestHandler<
1208 OCPP16FirmwareStatusNotificationRequest,
1209 OCPP16FirmwareStatusNotificationResponse
1210 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1211 status: OCPP16FirmwareStatus.Downloaded
1212 })
1213 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1214 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1215 let wasTransactionsStarted = false
1216 let transactionsStarted: boolean
1217 do {
1218 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1219 if (runningTransactions > 0) {
1220 const waitTime = secondsToMilliseconds(15)
1221 logger.debug(
1222 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1223 waitTime
1224 )} before continuing firmware update simulation`
1225 )
1226 await sleep(waitTime)
1227 transactionsStarted = true
1228 wasTransactionsStarted = true
1229 } else {
1230 if (chargingStation.hasEvses) {
1231 for (const [evseId, evseStatus] of chargingStation.evses) {
1232 if (evseId > 0) {
1233 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1234 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1235 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1236 chargingStation,
1237 connectorId,
1238 OCPP16ChargePointStatus.Unavailable
1239 )
1240 }
1241 }
1242 }
1243 }
1244 } else {
1245 for (const connectorId of chargingStation.connectors.keys()) {
1246 if (
1247 connectorId > 0 &&
1248 chargingStation.getConnectorStatus(connectorId)?.status !==
1249 OCPP16ChargePointStatus.Unavailable
1250 ) {
1251 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1252 chargingStation,
1253 connectorId,
1254 OCPP16ChargePointStatus.Unavailable
1255 )
1256 }
1257 }
1258 }
1259 transactionsStarted = false
1260 }
1261 } while (transactionsStarted)
1262 !wasTransactionsStarted &&
1263 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1264 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1265 return
1266 }
1267 await chargingStation.ocppRequestService.requestHandler<
1268 OCPP16FirmwareStatusNotificationRequest,
1269 OCPP16FirmwareStatusNotificationResponse
1270 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1271 status: OCPP16FirmwareStatus.Installing
1272 })
1273 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1274 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1275 if (
1276 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1277 OCPP16FirmwareStatus.InstallationFailed
1278 ) {
1279 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1280 await chargingStation.ocppRequestService.requestHandler<
1281 OCPP16FirmwareStatusNotificationRequest,
1282 OCPP16FirmwareStatusNotificationResponse
1283 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1284 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1285 })
1286 chargingStation.stationInfo.firmwareStatus =
1287 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1288 return
1289 }
1290 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1291 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1292 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1293 }
1294 }
1295
1296 private async handleRequestGetDiagnostics (
1297 chargingStation: ChargingStation,
1298 commandPayload: GetDiagnosticsRequest
1299 ): Promise<GetDiagnosticsResponse> {
1300 if (
1301 !OCPP16ServiceUtils.checkFeatureProfile(
1302 chargingStation,
1303 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1304 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1305 )
1306 ) {
1307 logger.warn(
1308 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1309 )
1310 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1311 }
1312 const { location } = commandPayload
1313 const uri = new URL(location)
1314 if (uri.protocol.startsWith('ftp:')) {
1315 let ftpClient: Client | undefined
1316 try {
1317 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1318 .filter(file => file.endsWith('.log'))
1319 .map(file => join('./', file))
1320 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1321 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1322 ftpClient = new Client()
1323 const accessResponse = await ftpClient.access({
1324 host: uri.host,
1325 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1326 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1327 ...(isNotEmptyString(uri.password) && { password: uri.password })
1328 })
1329 let uploadResponse: FTPResponse | undefined
1330 if (accessResponse.code === 220) {
1331 ftpClient.trackProgress(info => {
1332 logger.info(
1333 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1334 info.bytes / 1024
1335 } bytes transferred from diagnostics archive ${info.name}`
1336 )
1337 chargingStation.ocppRequestService
1338 .requestHandler<
1339 OCPP16DiagnosticsStatusNotificationRequest,
1340 OCPP16DiagnosticsStatusNotificationResponse
1341 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1342 status: OCPP16DiagnosticsStatus.Uploading
1343 })
1344 .catch(error => {
1345 logger.error(
1346 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1347 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1348 }'`,
1349 error
1350 )
1351 })
1352 })
1353 uploadResponse = await ftpClient.uploadFrom(
1354 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1355 `${uri.pathname}${diagnosticsArchive}`
1356 )
1357 if (uploadResponse.code === 226) {
1358 await chargingStation.ocppRequestService.requestHandler<
1359 OCPP16DiagnosticsStatusNotificationRequest,
1360 OCPP16DiagnosticsStatusNotificationResponse
1361 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1362 status: OCPP16DiagnosticsStatus.Uploaded
1363 })
1364 ftpClient.close()
1365 return { fileName: diagnosticsArchive }
1366 }
1367 throw new OCPPError(
1368 ErrorType.GENERIC_ERROR,
1369 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1370 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1371 )
1372 }
1373 throw new OCPPError(
1374 ErrorType.GENERIC_ERROR,
1375 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1376 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1377 )
1378 } catch (error) {
1379 await chargingStation.ocppRequestService.requestHandler<
1380 OCPP16DiagnosticsStatusNotificationRequest,
1381 OCPP16DiagnosticsStatusNotificationResponse
1382 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1383 status: OCPP16DiagnosticsStatus.UploadFailed
1384 })
1385 ftpClient?.close()
1386 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1387 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1388 chargingStation,
1389 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1390 error as Error,
1391 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1392 )!
1393 }
1394 } else {
1395 logger.error(
1396 `${chargingStation.logPrefix()} Unsupported protocol ${
1397 uri.protocol
1398 } to transfer the diagnostic logs archive`
1399 )
1400 await chargingStation.ocppRequestService.requestHandler<
1401 OCPP16DiagnosticsStatusNotificationRequest,
1402 OCPP16DiagnosticsStatusNotificationResponse
1403 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1404 status: OCPP16DiagnosticsStatus.UploadFailed
1405 })
1406 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1407 }
1408 }
1409
1410 private handleRequestTriggerMessage (
1411 chargingStation: ChargingStation,
1412 commandPayload: OCPP16TriggerMessageRequest
1413 ): OCPP16TriggerMessageResponse {
1414 const { requestedMessage, connectorId } = commandPayload
1415 if (
1416 !OCPP16ServiceUtils.checkFeatureProfile(
1417 chargingStation,
1418 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1419 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1420 ) ||
1421 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1422 ) {
1423 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1424 }
1425 if (
1426 !OCPP16ServiceUtils.isConnectorIdValid(
1427 chargingStation,
1428 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1429 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1430 connectorId!
1431 )
1432 ) {
1433 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1434 }
1435 try {
1436 switch (requestedMessage) {
1437 case OCPP16MessageTrigger.BootNotification:
1438 setTimeout(() => {
1439 chargingStation.ocppRequestService
1440 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1441 chargingStation,
1442 OCPP16RequestCommand.BOOT_NOTIFICATION,
1443 chargingStation.bootNotificationRequest,
1444 { skipBufferingOnError: true, triggerMessage: true }
1445 )
1446 .then(response => {
1447 chargingStation.bootNotificationResponse = response
1448 })
1449 .catch(Constants.EMPTY_FUNCTION)
1450 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1451 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1452 case OCPP16MessageTrigger.Heartbeat:
1453 setTimeout(() => {
1454 chargingStation.ocppRequestService
1455 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1456 chargingStation,
1457 OCPP16RequestCommand.HEARTBEAT,
1458 undefined,
1459 {
1460 triggerMessage: true
1461 }
1462 )
1463 .catch(Constants.EMPTY_FUNCTION)
1464 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1465 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1466 case OCPP16MessageTrigger.StatusNotification:
1467 setTimeout(() => {
1468 if (connectorId != null) {
1469 chargingStation.ocppRequestService
1470 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1471 chargingStation,
1472 OCPP16RequestCommand.STATUS_NOTIFICATION,
1473 {
1474 connectorId,
1475 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1476 status: chargingStation.getConnectorStatus(connectorId)?.status
1477 },
1478 {
1479 triggerMessage: true
1480 }
1481 )
1482 .catch(Constants.EMPTY_FUNCTION)
1483 } else if (chargingStation.hasEvses) {
1484 for (const evseStatus of chargingStation.evses.values()) {
1485 for (const [id, connectorStatus] of evseStatus.connectors) {
1486 chargingStation.ocppRequestService
1487 .requestHandler<
1488 OCPP16StatusNotificationRequest,
1489 OCPP16StatusNotificationResponse
1490 >(
1491 chargingStation,
1492 OCPP16RequestCommand.STATUS_NOTIFICATION,
1493 {
1494 connectorId: id,
1495 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1496 status: connectorStatus.status
1497 },
1498 {
1499 triggerMessage: true
1500 }
1501 )
1502 .catch(Constants.EMPTY_FUNCTION)
1503 }
1504 }
1505 } else {
1506 for (const [id, connectorStatus] of chargingStation.connectors) {
1507 chargingStation.ocppRequestService
1508 .requestHandler<
1509 OCPP16StatusNotificationRequest,
1510 OCPP16StatusNotificationResponse
1511 >(
1512 chargingStation,
1513 OCPP16RequestCommand.STATUS_NOTIFICATION,
1514 {
1515 connectorId: id,
1516 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1517 status: connectorStatus.status
1518 },
1519 {
1520 triggerMessage: true
1521 }
1522 )
1523 .catch(Constants.EMPTY_FUNCTION)
1524 }
1525 }
1526 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1527 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1528 default:
1529 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1530 }
1531 } catch (error) {
1532 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1533 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1534 chargingStation,
1535 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1536 error as Error,
1537 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1538 )!
1539 }
1540 }
1541
1542 private handleRequestDataTransfer (
1543 chargingStation: ChargingStation,
1544 commandPayload: OCPP16DataTransferRequest
1545 ): OCPP16DataTransferResponse {
1546 const { vendorId } = commandPayload
1547 try {
1548 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1549 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1550 }
1551 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1552 } catch (error) {
1553 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1554 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1555 chargingStation,
1556 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1557 error as Error,
1558 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1559 )!
1560 }
1561 }
1562
1563 private async handleRequestReserveNow (
1564 chargingStation: ChargingStation,
1565 commandPayload: OCPP16ReserveNowRequest
1566 ): Promise<OCPP16ReserveNowResponse> {
1567 if (
1568 !OCPP16ServiceUtils.checkFeatureProfile(
1569 chargingStation,
1570 OCPP16SupportedFeatureProfiles.Reservation,
1571 OCPP16IncomingRequestCommand.RESERVE_NOW
1572 )
1573 ) {
1574 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1575 }
1576 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1577 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1578 const { reservationId, idTag, connectorId } = commandPayload
1579 let response: OCPP16ReserveNowResponse
1580 try {
1581 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1582 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1583 }
1584 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1585 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1586 }
1587 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1588 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1589 }
1590 await removeExpiredReservations(chargingStation)
1591 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
1592 case OCPP16ChargePointStatus.Faulted:
1593 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1594 break
1595 case OCPP16ChargePointStatus.Preparing:
1596 case OCPP16ChargePointStatus.Charging:
1597 case OCPP16ChargePointStatus.SuspendedEV:
1598 case OCPP16ChargePointStatus.SuspendedEVSE:
1599 case OCPP16ChargePointStatus.Finishing:
1600 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1601 break
1602 case OCPP16ChargePointStatus.Unavailable:
1603 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1604 break
1605 case OCPP16ChargePointStatus.Reserved:
1606 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1607 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1608 break
1609 }
1610 // eslint-disable-next-line no-fallthrough
1611 default:
1612 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1613 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1614 break
1615 }
1616 await chargingStation.addReservation({
1617 id: commandPayload.reservationId,
1618 ...commandPayload
1619 })
1620 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1621 break
1622 }
1623 return response
1624 } catch (error) {
1625 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1626 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1627 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1628 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1629 chargingStation,
1630 OCPP16IncomingRequestCommand.RESERVE_NOW,
1631 error as Error,
1632 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1633 )!
1634 }
1635 }
1636
1637 private async handleRequestCancelReservation (
1638 chargingStation: ChargingStation,
1639 commandPayload: OCPP16CancelReservationRequest
1640 ): Promise<GenericResponse> {
1641 if (
1642 !OCPP16ServiceUtils.checkFeatureProfile(
1643 chargingStation,
1644 OCPP16SupportedFeatureProfiles.Reservation,
1645 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1646 )
1647 ) {
1648 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1649 }
1650 try {
1651 const { reservationId } = commandPayload
1652 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1653 if (reservation == null) {
1654 logger.debug(
1655 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1656 )
1657 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1658 }
1659 await chargingStation.removeReservation(
1660 reservation,
1661 ReservationTerminationReason.RESERVATION_CANCELED
1662 )
1663 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1664 } catch (error) {
1665 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1666 return this.handleIncomingRequestError<GenericResponse>(
1667 chargingStation,
1668 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1669 error as Error,
1670 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1671 )!
1672 }
1673 }
1674 }