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