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