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