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