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