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