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