refactor: cleanup some unneeded conditions
[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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
695 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
696 if (
697 isEmptyArray(connectorStatus.chargingProfiles) &&
698 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
699 ) {
700 return OCPP16Constants.OCPP_RESPONSE_REJECTED
701 }
702 const currentDate = new Date()
703 const compositeScheduleInterval: Interval = {
704 start: currentDate,
705 end: addSeconds(currentDate, duration)
706 }
707 // Get charging profiles sorted by connector id then stack level
708 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
709 chargingStation,
710 connectorId
711 )
712 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
713 let compositeSchedule: OCPP16ChargingSchedule | undefined
714 for (const chargingProfile of chargingProfiles) {
715 if (chargingProfile.chargingSchedule.startSchedule == null) {
716 logger.debug(
717 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
718 chargingProfile.chargingProfileId
719 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
720 )
721 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
722 chargingProfile.chargingSchedule.startSchedule = connectorStatus.transactionStart
723 }
724 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
725 logger.warn(
726 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
727 chargingProfile.chargingProfileId
728 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
729 )
730 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
731 chargingProfile.chargingSchedule.startSchedule = convertToDate(
732 chargingProfile.chargingSchedule.startSchedule
733 )!
734 }
735 if (chargingProfile.chargingSchedule.duration == null) {
736 logger.debug(
737 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
738 chargingProfile.chargingProfileId
739 } has no duration defined and will be set to the maximum time allowed`
740 )
741 // OCPP specifies that if duration is not defined, it should be infinite
742 chargingProfile.chargingSchedule.duration = differenceInSeconds(
743 maxTime,
744 chargingProfile.chargingSchedule.startSchedule
745 )
746 }
747 if (
748 !prepareChargingProfileKind(
749 connectorStatus,
750 chargingProfile,
751 compositeScheduleInterval.start as Date,
752 chargingStation.logPrefix()
753 )
754 ) {
755 continue
756 }
757 if (
758 !canProceedChargingProfile(
759 chargingProfile,
760 compositeScheduleInterval.start as Date,
761 chargingStation.logPrefix()
762 )
763 ) {
764 continue
765 }
766 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
767 previousCompositeSchedule,
768 chargingProfile.chargingSchedule,
769 compositeScheduleInterval
770 )
771 previousCompositeSchedule = compositeSchedule
772 }
773 if (compositeSchedule != null) {
774 return {
775 status: GenericStatus.Accepted,
776 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
777 scheduleStart: compositeSchedule.startSchedule!,
778 connectorId,
779 chargingSchedule: compositeSchedule
780 }
781 }
782 return OCPP16Constants.OCPP_RESPONSE_REJECTED
783 }
784
785 private handleRequestClearChargingProfile (
786 chargingStation: ChargingStation,
787 commandPayload: OCPP16ClearChargingProfileRequest
788 ): OCPP16ClearChargingProfileResponse {
789 if (
790 !OCPP16ServiceUtils.checkFeatureProfile(
791 chargingStation,
792 OCPP16SupportedFeatureProfiles.SmartCharging,
793 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
794 )
795 ) {
796 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
797 }
798 const { connectorId } = commandPayload
799 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
800 if (!chargingStation.hasConnector(connectorId!)) {
801 logger.error(
802 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
803 )
804 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
805 }
806 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
807 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
808 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
809 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
810 connectorStatus!.chargingProfiles = []
811 logger.debug(
812 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
813 )
814 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
815 }
816 if (connectorId == null) {
817 let clearedCP = false
818 if (chargingStation.hasEvses) {
819 for (const evseStatus of chargingStation.evses.values()) {
820 for (const status of evseStatus.connectors.values()) {
821 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
822 chargingStation,
823 commandPayload,
824 status.chargingProfiles
825 )
826 }
827 }
828 } else {
829 for (const id of chargingStation.connectors.keys()) {
830 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
831 chargingStation,
832 commandPayload,
833 chargingStation.getConnectorStatus(id)?.chargingProfiles
834 )
835 }
836 }
837 if (clearedCP) {
838 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
839 }
840 }
841 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
842 }
843
844 private async handleRequestChangeAvailability (
845 chargingStation: ChargingStation,
846 commandPayload: OCPP16ChangeAvailabilityRequest
847 ): Promise<OCPP16ChangeAvailabilityResponse> {
848 const { connectorId, type } = commandPayload
849 if (!chargingStation.hasConnector(connectorId)) {
850 logger.error(
851 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
852 )
853 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
854 }
855 const chargePointStatus: OCPP16ChargePointStatus =
856 type === OCPP16AvailabilityType.Operative
857 ? OCPP16ChargePointStatus.Available
858 : OCPP16ChargePointStatus.Unavailable
859 if (connectorId === 0) {
860 let response: OCPP16ChangeAvailabilityResponse
861 if (chargingStation.hasEvses) {
862 for (const evseStatus of chargingStation.evses.values()) {
863 response = await OCPP16ServiceUtils.changeAvailability(
864 chargingStation,
865 [...evseStatus.connectors.keys()],
866 chargePointStatus,
867 type
868 )
869 }
870 } else {
871 response = await OCPP16ServiceUtils.changeAvailability(
872 chargingStation,
873 [...chargingStation.connectors.keys()],
874 chargePointStatus,
875 type
876 )
877 }
878 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
879 return response!
880 } else if (
881 connectorId > 0 &&
882 (chargingStation.isChargingStationAvailable() ||
883 (!chargingStation.isChargingStationAvailable() &&
884 type === OCPP16AvailabilityType.Inoperative))
885 ) {
886 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
887 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
888 chargingStation.getConnectorStatus(connectorId)!.availability = type
889 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
890 }
891 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
892 chargingStation.getConnectorStatus(connectorId)!.availability = type
893 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
894 chargingStation,
895 connectorId,
896 chargePointStatus
897 )
898 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
899 }
900 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
901 }
902
903 private async handleRequestRemoteStartTransaction (
904 chargingStation: ChargingStation,
905 commandPayload: RemoteStartTransactionRequest
906 ): Promise<GenericResponse> {
907 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
908 if (!chargingStation.hasConnector(transactionConnectorId)) {
909 return await this.notifyRemoteStartTransactionRejected(
910 chargingStation,
911 transactionConnectorId,
912 idTag
913 )
914 }
915 if (
916 !chargingStation.isChargingStationAvailable() ||
917 !chargingStation.isConnectorAvailable(transactionConnectorId)
918 ) {
919 return await this.notifyRemoteStartTransactionRejected(
920 chargingStation,
921 transactionConnectorId,
922 idTag
923 )
924 }
925 const remoteStartTransactionLogMsg = `
926 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${chargingStation.stationInfo
927 ?.chargingStationId}#${transactionConnectorId} for idTag '${idTag}'`
928 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
929 chargingStation,
930 transactionConnectorId,
931 OCPP16ChargePointStatus.Preparing
932 )
933 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
934 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!
935 // Authorization check required
936 if (
937 chargingStation.getAuthorizeRemoteTxRequests() &&
938 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
939 ) {
940 // Authorization successful, start transaction
941 if (
942 (chargingProfile != null &&
943 this.setRemoteStartTransactionChargingProfile(
944 chargingStation,
945 transactionConnectorId,
946 chargingProfile
947 )) ||
948 chargingProfile == null
949 ) {
950 connectorStatus.transactionRemoteStarted = true
951 if (
952 (
953 await chargingStation.ocppRequestService.requestHandler<
954 OCPP16StartTransactionRequest,
955 OCPP16StartTransactionResponse
956 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
957 connectorId: transactionConnectorId,
958 idTag
959 })
960 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
961 ) {
962 logger.debug(remoteStartTransactionLogMsg)
963 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
964 }
965 return await this.notifyRemoteStartTransactionRejected(
966 chargingStation,
967 transactionConnectorId,
968 idTag
969 )
970 }
971 return await this.notifyRemoteStartTransactionRejected(
972 chargingStation,
973 transactionConnectorId,
974 idTag
975 )
976 }
977 // No authorization check required, start transaction
978 if (
979 (chargingProfile != null &&
980 this.setRemoteStartTransactionChargingProfile(
981 chargingStation,
982 transactionConnectorId,
983 chargingProfile
984 )) ||
985 chargingProfile == null
986 ) {
987 connectorStatus.transactionRemoteStarted = true
988 if (
989 (
990 await chargingStation.ocppRequestService.requestHandler<
991 OCPP16StartTransactionRequest,
992 OCPP16StartTransactionResponse
993 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
994 connectorId: transactionConnectorId,
995 idTag
996 })
997 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
998 ) {
999 logger.debug(remoteStartTransactionLogMsg)
1000 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1001 }
1002 return await this.notifyRemoteStartTransactionRejected(
1003 chargingStation,
1004 transactionConnectorId,
1005 idTag
1006 )
1007 }
1008 return await this.notifyRemoteStartTransactionRejected(
1009 chargingStation,
1010 transactionConnectorId,
1011 idTag
1012 )
1013 }
1014
1015 private async notifyRemoteStartTransactionRejected (
1016 chargingStation: ChargingStation,
1017 connectorId: number,
1018 idTag: string
1019 ): Promise<GenericResponse> {
1020 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1021 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
1022 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1023 chargingStation,
1024 connectorId,
1025 OCPP16ChargePointStatus.Available
1026 )
1027 }
1028 logger.warn(
1029 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1030 )
1031 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1032 }
1033
1034 private setRemoteStartTransactionChargingProfile (
1035 chargingStation: ChargingStation,
1036 connectorId: number,
1037 chargingProfile: OCPP16ChargingProfile
1038 ): boolean {
1039 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1040 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1041 logger.debug(
1042 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
1043 chargingProfile
1044 )
1045 return true
1046 }
1047 logger.warn(
1048 `${chargingStation.logPrefix()} Not allowed to set ${
1049 chargingProfile.chargingProfilePurpose
1050 } charging profile(s) at remote start transaction`
1051 )
1052 return false
1053 }
1054
1055 private async handleRequestRemoteStopTransaction (
1056 chargingStation: ChargingStation,
1057 commandPayload: RemoteStopTransactionRequest
1058 ): Promise<GenericResponse> {
1059 const { transactionId } = commandPayload
1060 if (chargingStation.hasEvses) {
1061 for (const [evseId, evseStatus] of chargingStation.evses) {
1062 if (evseId > 0) {
1063 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1064 if (connectorStatus.transactionId === transactionId) {
1065 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1066 }
1067 }
1068 }
1069 }
1070 } else {
1071 for (const connectorId of chargingStation.connectors.keys()) {
1072 if (
1073 connectorId > 0 &&
1074 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1075 ) {
1076 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
1077 }
1078 }
1079 }
1080 logger.warn(
1081 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1082 )
1083 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1084 }
1085
1086 private handleRequestUpdateFirmware (
1087 chargingStation: ChargingStation,
1088 commandPayload: OCPP16UpdateFirmwareRequest
1089 ): OCPP16UpdateFirmwareResponse {
1090 if (
1091 !OCPP16ServiceUtils.checkFeatureProfile(
1092 chargingStation,
1093 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1094 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1095 )
1096 ) {
1097 logger.warn(
1098 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1099 )
1100 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1101 }
1102 let { retrieveDate } = commandPayload
1103 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
1104 logger.warn(
1105 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1106 )
1107 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1108 }
1109 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1110 retrieveDate = convertToDate(retrieveDate)!
1111 const now = Date.now()
1112 if (retrieveDate.getTime() <= now) {
1113 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1114 } else {
1115 setTimeout(() => {
1116 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1117 }, retrieveDate.getTime() - now)
1118 }
1119 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1120 }
1121
1122 private async updateFirmwareSimulation (
1123 chargingStation: ChargingStation,
1124 maxDelay = 30,
1125 minDelay = 15
1126 ): Promise<void> {
1127 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1128 return
1129 }
1130 if (chargingStation.hasEvses) {
1131 for (const [evseId, evseStatus] of chargingStation.evses) {
1132 if (evseId > 0) {
1133 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1134 if (connectorStatus.transactionStarted === false) {
1135 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1136 chargingStation,
1137 connectorId,
1138 OCPP16ChargePointStatus.Unavailable
1139 )
1140 }
1141 }
1142 }
1143 }
1144 } else {
1145 for (const connectorId of chargingStation.connectors.keys()) {
1146 if (
1147 connectorId > 0 &&
1148 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1149 ) {
1150 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1151 chargingStation,
1152 connectorId,
1153 OCPP16ChargePointStatus.Unavailable
1154 )
1155 }
1156 }
1157 }
1158 await chargingStation.ocppRequestService.requestHandler<
1159 OCPP16FirmwareStatusNotificationRequest,
1160 OCPP16FirmwareStatusNotificationResponse
1161 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1162 status: OCPP16FirmwareStatus.Downloading
1163 })
1164 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1165 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1166 if (
1167 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1168 OCPP16FirmwareStatus.DownloadFailed
1169 ) {
1170 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1171 await chargingStation.ocppRequestService.requestHandler<
1172 OCPP16FirmwareStatusNotificationRequest,
1173 OCPP16FirmwareStatusNotificationResponse
1174 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1175 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1176 })
1177 chargingStation.stationInfo.firmwareStatus =
1178 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1179 return
1180 }
1181 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1182 await chargingStation.ocppRequestService.requestHandler<
1183 OCPP16FirmwareStatusNotificationRequest,
1184 OCPP16FirmwareStatusNotificationResponse
1185 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1186 status: OCPP16FirmwareStatus.Downloaded
1187 })
1188 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1189 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1190 let wasTransactionsStarted = false
1191 let transactionsStarted: boolean
1192 do {
1193 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1194 if (runningTransactions > 0) {
1195 const waitTime = secondsToMilliseconds(15)
1196 logger.debug(
1197 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1198 waitTime
1199 )} before continuing firmware update simulation`
1200 )
1201 await sleep(waitTime)
1202 transactionsStarted = true
1203 wasTransactionsStarted = true
1204 } else {
1205 if (chargingStation.hasEvses) {
1206 for (const [evseId, evseStatus] of chargingStation.evses) {
1207 if (evseId > 0) {
1208 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1209 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1210 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1211 chargingStation,
1212 connectorId,
1213 OCPP16ChargePointStatus.Unavailable
1214 )
1215 }
1216 }
1217 }
1218 }
1219 } else {
1220 for (const connectorId of chargingStation.connectors.keys()) {
1221 if (
1222 connectorId > 0 &&
1223 chargingStation.getConnectorStatus(connectorId)?.status !==
1224 OCPP16ChargePointStatus.Unavailable
1225 ) {
1226 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1227 chargingStation,
1228 connectorId,
1229 OCPP16ChargePointStatus.Unavailable
1230 )
1231 }
1232 }
1233 }
1234 transactionsStarted = false
1235 }
1236 } while (transactionsStarted)
1237 !wasTransactionsStarted &&
1238 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1239 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1240 return
1241 }
1242 await chargingStation.ocppRequestService.requestHandler<
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1246 status: OCPP16FirmwareStatus.Installing
1247 })
1248 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1249 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1250 if (
1251 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1252 OCPP16FirmwareStatus.InstallationFailed
1253 ) {
1254 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1255 await chargingStation.ocppRequestService.requestHandler<
1256 OCPP16FirmwareStatusNotificationRequest,
1257 OCPP16FirmwareStatusNotificationResponse
1258 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1259 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1260 })
1261 chargingStation.stationInfo.firmwareStatus =
1262 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1263 return
1264 }
1265 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1266 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1267 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1268 }
1269 }
1270
1271 private async handleRequestGetDiagnostics (
1272 chargingStation: ChargingStation,
1273 commandPayload: GetDiagnosticsRequest
1274 ): Promise<GetDiagnosticsResponse> {
1275 if (
1276 !OCPP16ServiceUtils.checkFeatureProfile(
1277 chargingStation,
1278 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1279 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1280 )
1281 ) {
1282 logger.warn(
1283 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1284 )
1285 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1286 }
1287 const { location } = commandPayload
1288 const uri = new URL(location)
1289 if (uri.protocol.startsWith('ftp:')) {
1290 let ftpClient: Client | undefined
1291 try {
1292 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1293 .filter((file) => file.endsWith('.log'))
1294 .map((file) => join('./', file))
1295 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1296 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1297 ftpClient = new Client()
1298 const accessResponse = await ftpClient.access({
1299 host: uri.host,
1300 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1301 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1302 ...(isNotEmptyString(uri.password) && { password: uri.password })
1303 })
1304 let uploadResponse: FTPResponse | undefined
1305 if (accessResponse.code === 220) {
1306 ftpClient.trackProgress((info) => {
1307 logger.info(
1308 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1309 info.bytes / 1024
1310 } bytes transferred from diagnostics archive ${info.name}`
1311 )
1312 chargingStation.ocppRequestService
1313 .requestHandler<
1314 OCPP16DiagnosticsStatusNotificationRequest,
1315 OCPP16DiagnosticsStatusNotificationResponse
1316 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1317 status: OCPP16DiagnosticsStatus.Uploading
1318 })
1319 .catch((error) => {
1320 logger.error(
1321 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1322 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1323 }'`,
1324 error
1325 )
1326 })
1327 })
1328 uploadResponse = await ftpClient.uploadFrom(
1329 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1330 `${uri.pathname}${diagnosticsArchive}`
1331 )
1332 if (uploadResponse.code === 226) {
1333 await chargingStation.ocppRequestService.requestHandler<
1334 OCPP16DiagnosticsStatusNotificationRequest,
1335 OCPP16DiagnosticsStatusNotificationResponse
1336 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1337 status: OCPP16DiagnosticsStatus.Uploaded
1338 })
1339 ftpClient.close()
1340 return { fileName: diagnosticsArchive }
1341 }
1342 throw new OCPPError(
1343 ErrorType.GENERIC_ERROR,
1344 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1345 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1346 )
1347 }
1348 throw new OCPPError(
1349 ErrorType.GENERIC_ERROR,
1350 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1351 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1352 )
1353 } catch (error) {
1354 await chargingStation.ocppRequestService.requestHandler<
1355 OCPP16DiagnosticsStatusNotificationRequest,
1356 OCPP16DiagnosticsStatusNotificationResponse
1357 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1358 status: OCPP16DiagnosticsStatus.UploadFailed
1359 })
1360 ftpClient?.close()
1361 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1362 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1363 chargingStation,
1364 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1365 error as Error,
1366 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1367 )!
1368 }
1369 } else {
1370 logger.error(
1371 `${chargingStation.logPrefix()} Unsupported protocol ${
1372 uri.protocol
1373 } to transfer the diagnostic logs archive`
1374 )
1375 await chargingStation.ocppRequestService.requestHandler<
1376 OCPP16DiagnosticsStatusNotificationRequest,
1377 OCPP16DiagnosticsStatusNotificationResponse
1378 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1379 status: OCPP16DiagnosticsStatus.UploadFailed
1380 })
1381 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1382 }
1383 }
1384
1385 private handleRequestTriggerMessage (
1386 chargingStation: ChargingStation,
1387 commandPayload: OCPP16TriggerMessageRequest
1388 ): OCPP16TriggerMessageResponse {
1389 const { requestedMessage, connectorId } = commandPayload
1390 if (
1391 !OCPP16ServiceUtils.checkFeatureProfile(
1392 chargingStation,
1393 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1394 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1395 ) ||
1396 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1397 ) {
1398 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1399 }
1400 if (
1401 !OCPP16ServiceUtils.isConnectorIdValid(
1402 chargingStation,
1403 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1404 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1405 connectorId!
1406 )
1407 ) {
1408 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1409 }
1410 try {
1411 switch (requestedMessage) {
1412 case OCPP16MessageTrigger.BootNotification:
1413 setTimeout(() => {
1414 chargingStation.ocppRequestService
1415 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1416 chargingStation,
1417 OCPP16RequestCommand.BOOT_NOTIFICATION,
1418 chargingStation.bootNotificationRequest,
1419 { skipBufferingOnError: true, triggerMessage: true }
1420 )
1421 .then((response) => {
1422 chargingStation.bootNotificationResponse = response
1423 })
1424 .catch(Constants.EMPTY_FUNCTION)
1425 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1426 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1427 case OCPP16MessageTrigger.Heartbeat:
1428 setTimeout(() => {
1429 chargingStation.ocppRequestService
1430 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1431 chargingStation,
1432 OCPP16RequestCommand.HEARTBEAT,
1433 undefined,
1434 {
1435 triggerMessage: true
1436 }
1437 )
1438 .catch(Constants.EMPTY_FUNCTION)
1439 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1440 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1441 case OCPP16MessageTrigger.StatusNotification:
1442 setTimeout(() => {
1443 if (connectorId != null) {
1444 chargingStation.ocppRequestService
1445 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1446 chargingStation,
1447 OCPP16RequestCommand.STATUS_NOTIFICATION,
1448 {
1449 connectorId,
1450 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1451 status: chargingStation.getConnectorStatus(connectorId)?.status
1452 },
1453 {
1454 triggerMessage: true
1455 }
1456 )
1457 .catch(Constants.EMPTY_FUNCTION)
1458 } else {
1459 if (chargingStation.hasEvses) {
1460 for (const evseStatus of chargingStation.evses.values()) {
1461 for (const [id, connectorStatus] of evseStatus.connectors) {
1462 chargingStation.ocppRequestService
1463 .requestHandler<
1464 OCPP16StatusNotificationRequest,
1465 OCPP16StatusNotificationResponse
1466 >(
1467 chargingStation,
1468 OCPP16RequestCommand.STATUS_NOTIFICATION,
1469 {
1470 connectorId: id,
1471 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1472 status: connectorStatus.status
1473 },
1474 {
1475 triggerMessage: true
1476 }
1477 )
1478 .catch(Constants.EMPTY_FUNCTION)
1479 }
1480 }
1481 } else {
1482 for (const id of chargingStation.connectors.keys()) {
1483 chargingStation.ocppRequestService
1484 .requestHandler<
1485 OCPP16StatusNotificationRequest,
1486 OCPP16StatusNotificationResponse
1487 >(
1488 chargingStation,
1489 OCPP16RequestCommand.STATUS_NOTIFICATION,
1490 {
1491 connectorId: id,
1492 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1493 status: chargingStation.getConnectorStatus(id)?.status
1494 },
1495 {
1496 triggerMessage: true
1497 }
1498 )
1499 .catch(Constants.EMPTY_FUNCTION)
1500 }
1501 }
1502 }
1503 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1504 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1505 default:
1506 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1507 }
1508 } catch (error) {
1509 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1510 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1511 chargingStation,
1512 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1513 error as Error,
1514 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1515 )!
1516 }
1517 }
1518
1519 private handleRequestDataTransfer (
1520 chargingStation: ChargingStation,
1521 commandPayload: OCPP16DataTransferRequest
1522 ): OCPP16DataTransferResponse {
1523 const { vendorId } = commandPayload
1524 try {
1525 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1526 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1527 }
1528 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1529 } catch (error) {
1530 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1531 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1532 chargingStation,
1533 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1534 error as Error,
1535 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1536 )!
1537 }
1538 }
1539
1540 private async handleRequestReserveNow (
1541 chargingStation: ChargingStation,
1542 commandPayload: OCPP16ReserveNowRequest
1543 ): Promise<OCPP16ReserveNowResponse> {
1544 if (
1545 !OCPP16ServiceUtils.checkFeatureProfile(
1546 chargingStation,
1547 OCPP16SupportedFeatureProfiles.Reservation,
1548 OCPP16IncomingRequestCommand.RESERVE_NOW
1549 )
1550 ) {
1551 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1552 }
1553 const { reservationId, idTag, connectorId } = commandPayload
1554 let response: OCPP16ReserveNowResponse
1555 try {
1556 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1557 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1558 }
1559 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1560 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1561 }
1562 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1563 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1564 }
1565 await removeExpiredReservations(chargingStation)
1566 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1567 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1568 case OCPP16ChargePointStatus.Faulted:
1569 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1570 break
1571 case OCPP16ChargePointStatus.Preparing:
1572 case OCPP16ChargePointStatus.Charging:
1573 case OCPP16ChargePointStatus.SuspendedEV:
1574 case OCPP16ChargePointStatus.SuspendedEVSE:
1575 case OCPP16ChargePointStatus.Finishing:
1576 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1577 break
1578 case OCPP16ChargePointStatus.Unavailable:
1579 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1580 break
1581 case OCPP16ChargePointStatus.Reserved:
1582 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1583 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1584 break
1585 }
1586 // eslint-disable-next-line no-fallthrough
1587 default:
1588 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1589 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1590 break
1591 }
1592 await chargingStation.addReservation({
1593 id: commandPayload.reservationId,
1594 ...commandPayload
1595 })
1596 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1597 break
1598 }
1599 return response
1600 } catch (error) {
1601 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1602 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1603 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1604 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1605 chargingStation,
1606 OCPP16IncomingRequestCommand.RESERVE_NOW,
1607 error as Error,
1608 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1609 )!
1610 }
1611 }
1612
1613 private async handleRequestCancelReservation (
1614 chargingStation: ChargingStation,
1615 commandPayload: OCPP16CancelReservationRequest
1616 ): Promise<GenericResponse> {
1617 if (
1618 !OCPP16ServiceUtils.checkFeatureProfile(
1619 chargingStation,
1620 OCPP16SupportedFeatureProfiles.Reservation,
1621 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1622 )
1623 ) {
1624 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1625 }
1626 try {
1627 const { reservationId } = commandPayload
1628 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1629 if (reservation == null) {
1630 logger.debug(
1631 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1632 )
1633 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1634 }
1635 await chargingStation.removeReservation(
1636 reservation,
1637 ReservationTerminationReason.RESERVATION_CANCELED
1638 )
1639 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1640 } catch (error) {
1641 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1642 return this.handleIncomingRequestError<GenericResponse>(
1643 chargingStation,
1644 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1645 error as Error,
1646 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1647 )!
1648 }
1649 }
1650 }