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