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