refactor: cleanup boot notification response assignation
[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
c0bbb3ea 423 chargingStation.getConnectorStatus(connectorId!)!.transactionRemoteStarted = true
1b4a545f 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 => {
7b08e7f3 434 if (response.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
1b4a545f 435 logger.debug(
48847bc0
JB
436 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
437 chargingStation.stationInfo?.chargingStationId
438 }#${connectorId} for idTag '${idTag}'`
1b4a545f
JB
439 )
440 } else {
441 logger.debug(
48847bc0
JB
442 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
443 chargingStation.stationInfo?.chargingStationId
444 }#${connectorId} for idTag '${idTag}'`
1b4a545f
JB
445 )
446 }
447 })
ea32ea05 448 .catch((error: unknown) => {
1b4a545f
JB
449 logger.error(
450 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
451 error
452 )
453 })
454 }
5c24bae9
JB
455 }
456 )
2665ed1e
JB
457 this.on(
458 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
459 (
460 chargingStation: ChargingStation,
461 request: RemoteStopTransactionRequest,
462 response: GenericResponse
463 ) => {
464 if (response.status === GenericStatus.Accepted) {
465 const { transactionId } = request
466 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
467 const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)!
468 OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
469 .then(response => {
470 if (response.status === GenericStatus.Accepted) {
471 logger.debug(
48847bc0
JB
472 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${
473 chargingStation.stationInfo?.chargingStationId
474 }#${connectorId} for transaction '${transactionId}'`
2665ed1e
JB
475 )
476 } else {
477 logger.debug(
48847bc0
JB
478 `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${
479 chargingStation.stationInfo?.chargingStationId
480 }#${connectorId} for transaction '${transactionId}'`
2665ed1e
JB
481 )
482 }
483 })
ea32ea05 484 .catch((error: unknown) => {
2665ed1e
JB
485 logger.error(
486 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote stop transaction error:`,
487 error
488 )
489 })
490 }
491 }
492 )
5c24bae9 493 this.on(
1b4a545f 494 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
ef69bc46
JB
495 (
496 chargingStation: ChargingStation,
497 request: OCPP16TriggerMessageRequest,
498 response: OCPP16TriggerMessageResponse
499 ) => {
500 if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
501 return
502 }
1b4a545f 503 const { requestedMessage, connectorId } = request
ea32ea05 504 const errorHandler = (error: unknown): void => {
5c24bae9 505 logger.error(
1b4a545f 506 `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
5c24bae9
JB
507 error
508 )
509 }
1b4a545f
JB
510 switch (requestedMessage) {
511 case OCPP16MessageTrigger.BootNotification:
512 chargingStation.ocppRequestService
ab29e682
JB
513 .requestHandler<
514 OCPP16BootNotificationRequest,
515 OCPP16BootNotificationResponse
516 >(chargingStation, OCPP16RequestCommand.BOOT_NOTIFICATION, chargingStation.bootNotificationRequest as OCPP16BootNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
1b4a545f
JB
517 .catch(errorHandler)
518 break
519 case OCPP16MessageTrigger.Heartbeat:
520 chargingStation.ocppRequestService
521 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
522 chargingStation,
523 OCPP16RequestCommand.HEARTBEAT,
524 undefined,
525 {
526 triggerMessage: true
527 }
528 )
529 .catch(errorHandler)
530 break
531 case OCPP16MessageTrigger.StatusNotification:
532 if (connectorId != null) {
5c24bae9
JB
533 chargingStation.ocppRequestService
534 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
535 chargingStation,
536 OCPP16RequestCommand.STATUS_NOTIFICATION,
537 {
1b4a545f 538 connectorId,
5c24bae9 539 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
314793aa
JB
540 status: chargingStation.getConnectorStatus(connectorId)
541 ?.status as OCPP16ChargePointStatus
5c24bae9
JB
542 },
543 {
544 triggerMessage: true
545 }
546 )
547 .catch(errorHandler)
1b4a545f
JB
548 } else if (chargingStation.hasEvses) {
549 for (const evseStatus of chargingStation.evses.values()) {
550 for (const [id, connectorStatus] of evseStatus.connectors) {
551 chargingStation.ocppRequestService
552 .requestHandler<
553 OCPP16StatusNotificationRequest,
554 OCPP16StatusNotificationResponse
555 >(
556 chargingStation,
557 OCPP16RequestCommand.STATUS_NOTIFICATION,
558 {
559 connectorId: id,
560 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
314793aa 561 status: connectorStatus.status as OCPP16ChargePointStatus
1b4a545f
JB
562 },
563 {
564 triggerMessage: true
565 }
566 )
567 .catch(errorHandler)
568 }
5c24bae9 569 }
1b4a545f
JB
570 } else {
571 for (const [id, connectorStatus] of chargingStation.connectors) {
572 chargingStation.ocppRequestService
573 .requestHandler<
574 OCPP16StatusNotificationRequest,
575 OCPP16StatusNotificationResponse
576 >(
577 chargingStation,
578 OCPP16RequestCommand.STATUS_NOTIFICATION,
579 {
580 connectorId: id,
581 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
314793aa 582 status: connectorStatus.status as OCPP16ChargePointStatus
1b4a545f
JB
583 },
584 {
585 triggerMessage: true
586 }
587 )
588 .catch(errorHandler)
589 }
590 }
591 break
5c24bae9
JB
592 }
593 }
594 )
ba9a56a6 595 this.validatePayload = this.validatePayload.bind(this)
58144adb
JB
596 }
597
9429aa42 598 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 599 chargingStation: ChargingStation,
e7aeea18
JB
600 messageId: string,
601 commandName: OCPP16IncomingRequestCommand,
66a7748d 602 commandPayload: ReqType
e7aeea18 603 ): Promise<void> {
66a7748d 604 let response: ResType
e7aeea18 605 if (
5398cecf 606 chargingStation.stationInfo?.ocppStrictCompliance === true &&
66a7748d 607 chargingStation.inPendingState() &&
e7aeea18
JB
608 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
609 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
610 ) {
611 throw new OCPPError(
612 ErrorType.SECURITY_ERROR,
e3018bc4 613 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 614 commandPayload,
4ed03b6e 615 undefined,
66a7748d 616 2
e7aeea18 617 )} while the charging station is in pending state on the central server`,
7369e417 618 commandName,
66a7748d
JB
619 commandPayload
620 )
caad9d6b 621 }
e7aeea18 622 if (
66a7748d 623 chargingStation.isRegistered() ||
5398cecf 624 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
66a7748d 625 chargingStation.inUnknownState())
e7aeea18 626 ) {
65554cc3 627 if (
66a7748d
JB
628 this.incomingRequestHandlers.has(commandName) &&
629 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
65554cc3 630 ) {
124f3553 631 try {
66a7748d 632 this.validatePayload(chargingStation, commandName, commandPayload)
c75a6675 633 // Call the method to build the response
66a7748d 634 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
bcf95df1
JB
635 const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
636 if (isAsyncFunction(incomingRequestHandler)) {
637 response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
638 } else {
639 response = incomingRequestHandler(chargingStation, commandPayload) as ResType
640 }
124f3553
JB
641 } catch (error) {
642 // Log
6c8f5d90 643 logger.error(
944d4529 644 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
66a7748d
JB
645 error
646 )
647 throw error
124f3553
JB
648 }
649 } else {
650 // Throw exception
e7aeea18
JB
651 throw new OCPPError(
652 ErrorType.NOT_IMPLEMENTED,
240fa4da 653 `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
e7aeea18 654 commandPayload,
4ed03b6e 655 undefined,
66a7748d 656 2
e7aeea18 657 )}`,
7369e417 658 commandName,
66a7748d
JB
659 commandPayload
660 )
c0560973
JB
661 }
662 } else {
e7aeea18
JB
663 throw new OCPPError(
664 ErrorType.SECURITY_ERROR,
e3018bc4 665 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 666 commandPayload,
4ed03b6e 667 undefined,
66a7748d 668 2
412cece8 669 )} while the charging station is not registered on the central server`,
7369e417 670 commandName,
66a7748d
JB
671 commandPayload
672 )
c0560973 673 }
c75a6675 674 // Send the built response
08f130a0
JB
675 await chargingStation.ocppRequestService.sendResponse(
676 chargingStation,
677 messageId,
678 response,
66a7748d
JB
679 commandName
680 )
868c6830 681 // Emit command name event to allow delayed handling
1b7eb386 682 this.emit(commandName, chargingStation, commandPayload, response)
c0560973
JB
683 }
684
66a7748d 685 private validatePayload (
9c5c4195
JB
686 chargingStation: ChargingStation,
687 commandName: OCPP16IncomingRequestCommand,
66a7748d 688 commandPayload: JsonType
9c5c4195 689 ): boolean {
d5490a13 690 if (this.payloadValidateFunctions.has(commandName)) {
24d15716 691 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
9c5c4195
JB
692 }
693 logger.warn(
24d15716 694 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
695 )
696 return false
9c5c4195
JB
697 }
698
c0560973 699 // Simulate charging station restart
66a7748d 700 private handleRequestReset (
08f130a0 701 chargingStation: ChargingStation,
66a7748d 702 commandPayload: ResetRequest
f03e1042 703 ): GenericResponse {
66a7748d 704 const { type } = commandPayload
d1ff8599
JB
705 chargingStation
706 .reset(`${type}Reset` as OCPP16StopTransactionReason)
66a7748d 707 .catch(Constants.EMPTY_FUNCTION)
e7aeea18 708 logger.info(
944d4529 709 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
66a7748d 710 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 711 chargingStation.stationInfo!.resetTime!
66a7748d
JB
712 )}`
713 )
714 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
c0560973
JB
715 }
716
66a7748d 717 private async handleRequestUnlockConnector (
08f130a0 718 chargingStation: ChargingStation,
66a7748d 719 commandPayload: UnlockConnectorRequest
e7aeea18 720 ): Promise<UnlockConnectorResponse> {
66a7748d
JB
721 const { connectorId } = commandPayload
722 if (!chargingStation.hasConnector(connectorId)) {
c60ed4b8 723 logger.error(
66a7748d
JB
724 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
725 )
726 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c60ed4b8 727 }
c0560973 728 if (connectorId === 0) {
66a7748d
JB
729 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
730 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c0560973 731 }
5e3cb728
JB
732 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
733 const stopResponse = await chargingStation.stopTransactionOnConnector(
734 connectorId,
66a7748d
JB
735 OCPP16StopTransactionReason.UNLOCK_COMMAND
736 )
c0560973 737 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d 738 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973 739 }
66a7748d 740 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
c0560973 741 }
4ecff7ce
JB
742 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
743 chargingStation,
ef6fa3fb 744 connectorId,
66a7748d
JB
745 OCPP16ChargePointStatus.Available
746 )
747 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973
JB
748 }
749
66a7748d 750 private handleRequestGetConfiguration (
08f130a0 751 chargingStation: ChargingStation,
66a7748d 752 commandPayload: GetConfigurationRequest
e7aeea18 753 ): GetConfigurationResponse {
66a7748d
JB
754 const { key } = commandPayload
755 const configurationKey: OCPPConfigurationKey[] = []
756 const unknownKey: string[] = []
300418e9 757 if (key == null) {
66a7748d 758 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563e40ce
JB
759 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
760 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
66a7748d 761 continue
c0560973
JB
762 }
763 configurationKey.push({
563e40ce
JB
764 key: configKey.key,
765 readonly: configKey.readonly,
766 value: configKey.value
66a7748d 767 })
c0560973 768 }
66a7748d 769 } else if (isNotEmptyArray(key)) {
300418e9 770 for (const k of key) {
66a7748d 771 const keyFound = getConfigurationKey(chargingStation, k, true)
a807045b 772 if (keyFound != null) {
563e40ce 773 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
66a7748d 774 continue
c0560973
JB
775 }
776 configurationKey.push({
777 key: keyFound.key,
778 readonly: keyFound.readonly,
66a7748d
JB
779 value: keyFound.value
780 })
c0560973 781 } else {
66a7748d 782 unknownKey.push(k)
c0560973
JB
783 }
784 }
785 }
786 return {
787 configurationKey,
66a7748d
JB
788 unknownKey
789 }
c0560973
JB
790 }
791
66a7748d 792 private handleRequestChangeConfiguration (
08f130a0 793 chargingStation: ChargingStation,
66a7748d 794 commandPayload: ChangeConfigurationRequest
e7aeea18 795 ): ChangeConfigurationResponse {
66a7748d
JB
796 const { key, value } = commandPayload
797 const keyToChange = getConfigurationKey(chargingStation, key, true)
e1d9a0f4 798 if (keyToChange?.readonly === true) {
66a7748d 799 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
bd5d98e0 800 } else if (keyToChange?.readonly === false) {
66a7748d 801 let valueChanged = false
0d1f33ba 802 if (keyToChange.value !== value) {
66a7748d
JB
803 setConfigurationKeyValue(chargingStation, key, value, true)
804 valueChanged = true
c0560973 805 }
66a7748d 806 let triggerHeartbeatRestart = false
e1d9a0f4
JB
807 if (
808 (keyToChange.key as OCPP16StandardParametersKey) ===
809 OCPP16StandardParametersKey.HeartBeatInterval &&
810 valueChanged
811 ) {
f2d5e3d9 812 setConfigurationKeyValue(
17ac262c 813 chargingStation,
e7aeea18 814 OCPP16StandardParametersKey.HeartbeatInterval,
66a7748d
JB
815 value
816 )
817 triggerHeartbeatRestart = true
c0560973 818 }
e1d9a0f4
JB
819 if (
820 (keyToChange.key as OCPP16StandardParametersKey) ===
821 OCPP16StandardParametersKey.HeartbeatInterval &&
822 valueChanged
823 ) {
f2d5e3d9 824 setConfigurationKeyValue(
17ac262c 825 chargingStation,
e7aeea18 826 OCPP16StandardParametersKey.HeartBeatInterval,
66a7748d
JB
827 value
828 )
829 triggerHeartbeatRestart = true
c0560973
JB
830 }
831 if (triggerHeartbeatRestart) {
66a7748d 832 chargingStation.restartHeartbeat()
c0560973 833 }
e1d9a0f4
JB
834 if (
835 (keyToChange.key as OCPP16StandardParametersKey) ===
836 OCPP16StandardParametersKey.WebSocketPingInterval &&
837 valueChanged
838 ) {
66a7748d 839 chargingStation.restartWebSocketPing()
c0560973 840 }
66a7748d
JB
841 if (keyToChange.reboot === true) {
842 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
c0560973 843 }
66a7748d 844 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
c0560973 845 }
66a7748d 846 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
c0560973
JB
847 }
848
66a7748d 849 private handleRequestSetChargingProfile (
08f130a0 850 chargingStation: ChargingStation,
66a7748d 851 commandPayload: SetChargingProfileRequest
e7aeea18 852 ): SetChargingProfileResponse {
370ae4ee 853 if (
66a7748d 854 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 855 chargingStation,
370ae4ee 856 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
857 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
858 )
370ae4ee 859 ) {
66a7748d 860 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
68cb8b91 861 }
66a7748d
JB
862 const { connectorId, csChargingProfiles } = commandPayload
863 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 864 logger.error(
66a7748d
JB
865 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
866 )
867 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 868 }
e7aeea18 869 if (
0d1f33ba 870 csChargingProfiles.chargingProfilePurpose ===
0ac97927 871 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
0d1f33ba 872 connectorId !== 0
e7aeea18 873 ) {
66a7748d 874 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 875 }
e7aeea18 876 if (
0d1f33ba 877 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
86f51b96
JB
878 connectorId === 0
879 ) {
880 logger.error(
66a7748d
JB
881 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
882 )
883 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 884 }
66a7748d 885 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
86f51b96
JB
886 if (
887 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
888 connectorId > 0 &&
889 connectorStatus?.transactionStarted === false
e7aeea18 890 ) {
db0af086 891 logger.error(
66a7748d
JB
892 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
893 )
894 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 895 }
86f51b96
JB
896 if (
897 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
898 connectorId > 0 &&
899 connectorStatus?.transactionStarted === true &&
5199f9fd 900 csChargingProfiles.transactionId !== connectorStatus.transactionId
86f51b96
JB
901 ) {
902 logger.error(
944d4529
JB
903 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
904 csChargingProfiles.transactionId
5199f9fd 905 } than the started transaction id ${connectorStatus.transactionId}`
66a7748d
JB
906 )
907 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 908 }
66a7748d 909 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
e7aeea18 910 logger.debug(
0d1f33ba 911 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
66a7748d
JB
912 csChargingProfiles
913 )
914 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
915 }
916
66a7748d 917 private handleRequestGetCompositeSchedule (
41189456 918 chargingStation: ChargingStation,
66a7748d 919 commandPayload: OCPP16GetCompositeScheduleRequest
41189456
JB
920 ): OCPP16GetCompositeScheduleResponse {
921 if (
66a7748d 922 !OCPP16ServiceUtils.checkFeatureProfile(
41189456
JB
923 chargingStation,
924 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
925 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
926 )
41189456 927 ) {
66a7748d 928 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 929 }
66a7748d
JB
930 const { connectorId, duration, chargingRateUnit } = commandPayload
931 if (!chargingStation.hasConnector(connectorId)) {
41189456 932 logger.error(
66a7748d
JB
933 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
934 )
935 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 936 }
b3d7d654
JB
937 if (connectorId === 0) {
938 logger.error(
66a7748d
JB
939 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
940 )
941 return OCPP16Constants.OCPP_RESPONSE_REJECTED
b3d7d654 942 }
66a7748d 943 if (chargingRateUnit != null) {
bbb55ee4 944 logger.warn(
66a7748d
JB
945 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
946 )
b3d7d654 947 }
f938317f 948 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
ad490d5f 949 if (
38ae4ce2
JB
950 isEmpty(connectorStatus?.chargingProfiles) &&
951 isEmpty(chargingStation.getConnectorStatus(0)?.chargingProfiles)
ad490d5f 952 ) {
66a7748d 953 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 954 }
66a7748d 955 const currentDate = new Date()
ef9e3b33 956 const compositeScheduleInterval: Interval = {
ad490d5f 957 start: currentDate,
66a7748d
JB
958 end: addSeconds(currentDate, duration)
959 }
6fc0c6f3 960 // Get charging profiles sorted by connector id then stack level
ef9e3b33 961 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
6fc0c6f3 962 chargingStation,
66a7748d
JB
963 connectorId
964 )
965 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
966 let compositeSchedule: OCPP16ChargingSchedule | undefined
ef9e3b33 967 for (const chargingProfile of chargingProfiles) {
2466918c 968 if (chargingProfile.chargingSchedule.startSchedule == null) {
ad490d5f
JB
969 logger.debug(
970 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
ef9e3b33 971 chargingProfile.chargingProfileId
66a7748d
JB
972 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
973 )
ad490d5f 974 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
f938317f 975 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
ad490d5f 976 }
2466918c 977 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
ef9e3b33
JB
978 logger.warn(
979 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
980 chargingProfile.chargingProfileId
66a7748d
JB
981 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
982 )
ef9e3b33 983 chargingProfile.chargingSchedule.startSchedule = convertToDate(
5199f9fd 984 chargingProfile.chargingSchedule.startSchedule
3423c8a5 985 )
ef9e3b33 986 }
2466918c 987 if (chargingProfile.chargingSchedule.duration == null) {
da332e70 988 logger.debug(
ef9e3b33
JB
989 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
990 chargingProfile.chargingProfileId
66a7748d
JB
991 } has no duration defined and will be set to the maximum time allowed`
992 )
da332e70 993 // OCPP specifies that if duration is not defined, it should be infinite
ef9e3b33 994 chargingProfile.chargingSchedule.duration = differenceInSeconds(
da332e70 995 maxTime,
3423c8a5
JB
996 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
997 chargingProfile.chargingSchedule.startSchedule!
66a7748d 998 )
da332e70 999 }
0eb666db
JB
1000 if (
1001 !prepareChargingProfileKind(
1002 connectorStatus,
ef9e3b33 1003 chargingProfile,
6dde6c5f 1004 compositeScheduleInterval.start,
66a7748d 1005 chargingStation.logPrefix()
0eb666db
JB
1006 )
1007 ) {
66a7748d 1008 continue
b3d7d654 1009 }
41189456 1010 if (
ad490d5f 1011 !canProceedChargingProfile(
ef9e3b33 1012 chargingProfile,
6dde6c5f 1013 compositeScheduleInterval.start,
66a7748d 1014 chargingStation.logPrefix()
b3d7d654 1015 )
41189456 1016 ) {
66a7748d 1017 continue
ad490d5f 1018 }
ef9e3b33
JB
1019 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
1020 previousCompositeSchedule,
1021 chargingProfile.chargingSchedule,
66a7748d
JB
1022 compositeScheduleInterval
1023 )
1024 previousCompositeSchedule = compositeSchedule
ef9e3b33 1025 }
66a7748d 1026 if (compositeSchedule != null) {
ef9e3b33
JB
1027 return {
1028 status: GenericStatus.Accepted,
0c1e4bc1 1029 scheduleStart: compositeSchedule.startSchedule,
ef9e3b33 1030 connectorId,
66a7748d
JB
1031 chargingSchedule: compositeSchedule
1032 }
ef9e3b33 1033 }
66a7748d 1034 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456
JB
1035 }
1036
66a7748d 1037 private handleRequestClearChargingProfile (
08f130a0 1038 chargingStation: ChargingStation,
66a7748d 1039 commandPayload: OCPP16ClearChargingProfileRequest
41f3983a 1040 ): OCPP16ClearChargingProfileResponse {
370ae4ee 1041 if (
66a7748d 1042 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1043 chargingStation,
370ae4ee 1044 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
1045 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
1046 )
370ae4ee 1047 ) {
66a7748d 1048 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
68cb8b91 1049 }
66a7748d 1050 const { connectorId } = commandPayload
ec6dd88b
JB
1051 if (connectorId != null) {
1052 if (!chargingStation.hasConnector(connectorId)) {
1053 logger.error(
1054 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
1055 )
1056 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1057 }
1058 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1059 if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
1060 connectorStatus.chargingProfiles = []
1061 logger.debug(
1062 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
1063 )
1064 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1065 }
1066 } else {
66a7748d 1067 let clearedCP = false
4334db72
JB
1068 if (chargingStation.hasEvses) {
1069 for (const evseStatus of chargingStation.evses.values()) {
f406808f 1070 for (const status of evseStatus.connectors.values()) {
ec6dd88b 1071 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
73d87be1
JB
1072 chargingStation,
1073 commandPayload,
66a7748d
JB
1074 status.chargingProfiles
1075 )
ec6dd88b
JB
1076 if (clearedConnectorCP && !clearedCP) {
1077 clearedCP = true
1078 }
4334db72
JB
1079 }
1080 }
1081 } else {
0d1f33ba 1082 for (const id of chargingStation.connectors.keys()) {
ec6dd88b 1083 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
73d87be1
JB
1084 chargingStation,
1085 commandPayload,
66a7748d
JB
1086 chargingStation.getConnectorStatus(id)?.chargingProfiles
1087 )
ec6dd88b
JB
1088 if (clearedConnectorCP && !clearedCP) {
1089 clearedCP = true
1090 }
c0560973
JB
1091 }
1092 }
1093 if (clearedCP) {
66a7748d 1094 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
1095 }
1096 }
66a7748d 1097 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973
JB
1098 }
1099
66a7748d 1100 private async handleRequestChangeAvailability (
08f130a0 1101 chargingStation: ChargingStation,
66a7748d 1102 commandPayload: OCPP16ChangeAvailabilityRequest
366f75f6 1103 ): Promise<OCPP16ChangeAvailabilityResponse> {
66a7748d
JB
1104 const { connectorId, type } = commandPayload
1105 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 1106 logger.error(
66a7748d
JB
1107 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
1108 )
1109 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973 1110 }
e7aeea18 1111 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 1112 type === OCPP16AvailabilityType.Operative
721646e9 1113 ? OCPP16ChargePointStatus.Available
66a7748d 1114 : OCPP16ChargePointStatus.Unavailable
c0560973 1115 if (connectorId === 0) {
f938317f 1116 let response: OCPP16ChangeAvailabilityResponse | undefined
ded57f02
JB
1117 if (chargingStation.hasEvses) {
1118 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
1119 response = await OCPP16ServiceUtils.changeAvailability(
1120 chargingStation,
225e32b0 1121 [...evseStatus.connectors.keys()],
366f75f6 1122 chargePointStatus,
66a7748d
JB
1123 type
1124 )
ded57f02 1125 }
225e32b0
JB
1126 } else {
1127 response = await OCPP16ServiceUtils.changeAvailability(
1128 chargingStation,
1129 [...chargingStation.connectors.keys()],
1130 chargePointStatus,
66a7748d
JB
1131 type
1132 )
c0560973 1133 }
66a7748d
JB
1134 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1135 return response!
e7aeea18
JB
1136 } else if (
1137 connectorId > 0 &&
66a7748d
JB
1138 (chargingStation.isChargingStationAvailable() ||
1139 (!chargingStation.isChargingStationAvailable() &&
0d1f33ba 1140 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 1141 ) {
5e3cb728 1142 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
66a7748d
JB
1143 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1144 chargingStation.getConnectorStatus(connectorId)!.availability = type
1145 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
c0560973 1146 }
66a7748d
JB
1147 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1148 chargingStation.getConnectorStatus(connectorId)!.availability = type
4ecff7ce
JB
1149 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1150 chargingStation,
ef6fa3fb 1151 connectorId,
66a7748d
JB
1152 chargePointStatus
1153 )
1154 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
c0560973 1155 }
66a7748d 1156 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973
JB
1157 }
1158
66a7748d 1159 private async handleRequestRemoteStartTransaction (
08f130a0 1160 chargingStation: ChargingStation,
66a7748d 1161 commandPayload: RemoteStartTransactionRequest
f03e1042 1162 ): Promise<GenericResponse> {
c0bbb3ea
JB
1163 if (commandPayload.connectorId == null) {
1164 do {
1165 commandPayload.connectorId = randomInt(1, chargingStation.getNumberOfConnectors())
1166 } while (
8946ba9d
JB
1167 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
1168 true &&
1169 OCPP16ServiceUtils.hasReservation(
1170 chargingStation,
1171 commandPayload.connectorId,
1172 commandPayload.idTag
1173 )
c0bbb3ea
JB
1174 )
1175 }
66a7748d
JB
1176 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1177 if (!chargingStation.hasConnector(transactionConnectorId)) {
a9671b9e 1178 return this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
1179 chargingStation,
1180 transactionConnectorId,
66a7748d
JB
1181 idTag
1182 )
649287f8
JB
1183 }
1184 if (
d193a949
JB
1185 !chargingStation.isChargingStationAvailable() ||
1186 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8 1187 ) {
a9671b9e 1188 return this.notifyRemoteStartTransactionRejected(
649287f8
JB
1189 chargingStation,
1190 transactionConnectorId,
66a7748d
JB
1191 idTag
1192 )
649287f8 1193 }
b10202d7 1194 // idTag authorization check required
d984c13f 1195 if (
66a7748d 1196 chargingStation.getAuthorizeRemoteTxRequests() &&
b10202d7 1197 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
66dd3447 1198 ) {
a9671b9e 1199 return this.notifyRemoteStartTransactionRejected(
08f130a0 1200 chargingStation,
e7aeea18 1201 transactionConnectorId,
66a7748d
JB
1202 idTag
1203 )
c0560973 1204 }
649287f8 1205 if (
b10202d7
JB
1206 chargingProfile != null &&
1207 !this.setRemoteStartTransactionChargingProfile(
1208 chargingStation,
1209 transactionConnectorId,
1210 chargingProfile
1211 )
649287f8 1212 ) {
a9671b9e 1213 return this.notifyRemoteStartTransactionRejected(
649287f8
JB
1214 chargingStation,
1215 transactionConnectorId,
66a7748d
JB
1216 idTag
1217 )
649287f8 1218 }
2665ed1e 1219 logger.debug(
48847bc0
JB
1220 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
1221 chargingStation.stationInfo?.chargingStationId
1222 }#${transactionConnectorId}}, idTag '${idTag}'`
2665ed1e 1223 )
54510a60 1224 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
a7fc8211
JB
1225 }
1226
a9671b9e 1227 private notifyRemoteStartTransactionRejected (
08f130a0 1228 chargingStation: ChargingStation,
e7aeea18 1229 connectorId: number,
66a7748d 1230 idTag: string
a9671b9e 1231 ): GenericResponse {
66a7748d 1232 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
2665ed1e 1233 logger.debug(
48847bc0
JB
1234 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1235 chargingStation.stationInfo?.chargingStationId
1236 }#${connectorId}, idTag '${idTag}', availability '${
1237 connectorStatus?.availability
1238 }', status '${connectorStatus?.status}'`
66a7748d
JB
1239 )
1240 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1241 }
1242
66a7748d 1243 private setRemoteStartTransactionChargingProfile (
08f130a0 1244 chargingStation: ChargingStation,
e7aeea18 1245 connectorId: number,
66a7748d 1246 chargingProfile: OCPP16ChargingProfile
e7aeea18 1247 ): boolean {
5199f9fd 1248 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1249 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1250 logger.debug(
48847bc0
JB
1251 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${
1252 chargingStation.stationInfo?.chargingStationId
1253 }#${connectorId}`,
66a7748d
JB
1254 chargingProfile
1255 )
1256 return true
a7fc8211 1257 }
2665ed1e 1258 logger.debug(
f406808f
JB
1259 `${chargingStation.logPrefix()} Not allowed to set ${
1260 chargingProfile.chargingProfilePurpose
66a7748d
JB
1261 } charging profile(s) at remote start transaction`
1262 )
1263 return false
a7fc8211
JB
1264 }
1265
2665ed1e 1266 private handleRequestRemoteStopTransaction (
08f130a0 1267 chargingStation: ChargingStation,
66a7748d 1268 commandPayload: RemoteStopTransactionRequest
2665ed1e 1269 ): GenericResponse {
66a7748d 1270 const { transactionId } = commandPayload
2665ed1e
JB
1271 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1272 logger.debug(
1273 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1274 )
1275 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
c0560973 1276 }
2665ed1e
JB
1277 logger.debug(
1278 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
66a7748d
JB
1279 )
1280 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1281 }
47e22477 1282
66a7748d 1283 private handleRequestUpdateFirmware (
b03df580 1284 chargingStation: ChargingStation,
66a7748d 1285 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1286 ): OCPP16UpdateFirmwareResponse {
1287 if (
66a7748d 1288 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1289 chargingStation,
1290 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1291 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1292 )
b03df580 1293 ) {
5d280aae 1294 logger.warn(
66a7748d
JB
1295 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1296 )
1297 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1298 }
95dab6cf
JB
1299 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1300 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1301 const { retrieveDate } = commandPayload
382b62c4
JB
1302 if (
1303 chargingStation.stationInfo?.firmwareStatus != null &&
1304 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1305 ) {
5d280aae 1306 logger.warn(
66a7748d
JB
1307 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1308 )
1309 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1310 }
66a7748d 1311 const now = Date.now()
5199f9fd 1312 if (retrieveDate.getTime() <= now) {
66a7748d 1313 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1314 } else {
5199f9fd
JB
1315 setTimeout(() => {
1316 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1317 }, retrieveDate.getTime() - now)
c9a4f9ea 1318 }
66a7748d 1319 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1320 }
1321
66a7748d 1322 private async updateFirmwareSimulation (
c9a4f9ea 1323 chargingStation: ChargingStation,
90293abb 1324 maxDelay = 30,
66a7748d 1325 minDelay = 15
c9a4f9ea 1326 ): Promise<void> {
66a7748d
JB
1327 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1328 return
1bf29f5b 1329 }
ded57f02
JB
1330 if (chargingStation.hasEvses) {
1331 for (const [evseId, evseStatus] of chargingStation.evses) {
1332 if (evseId > 0) {
1333 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1334 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1335 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1336 chargingStation,
1337 connectorId,
66a7748d
JB
1338 OCPP16ChargePointStatus.Unavailable
1339 )
ded57f02
JB
1340 }
1341 }
1342 }
1343 }
1344 } else {
1345 for (const connectorId of chargingStation.connectors.keys()) {
1346 if (
1347 connectorId > 0 &&
1348 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1349 ) {
1350 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1351 chargingStation,
1352 connectorId,
66a7748d
JB
1353 OCPP16ChargePointStatus.Unavailable
1354 )
ded57f02 1355 }
c9a4f9ea
JB
1356 }
1357 }
93f0c2c8 1358 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1359 OCPP16FirmwareStatusNotificationRequest,
1360 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1361 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1362 status: OCPP16FirmwareStatus.Downloading
1363 })
5199f9fd
JB
1364 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1365 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1366 if (
93f0c2c8
JB
1367 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1368 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1369 ) {
fcda9151 1370 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
5d280aae 1371 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1372 OCPP16FirmwareStatusNotificationRequest,
1373 OCPP16FirmwareStatusNotificationResponse
5d280aae 1374 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1375 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1376 })
93f0c2c8 1377 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1378 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1379 return
5d280aae 1380 }
fcda9151 1381 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
c9a4f9ea 1382 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1383 OCPP16FirmwareStatusNotificationRequest,
1384 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1385 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1386 status: OCPP16FirmwareStatus.Downloaded
1387 })
5199f9fd
JB
1388 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1389 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1390 let wasTransactionsStarted = false
1391 let transactionsStarted: boolean
62340a29 1392 do {
66a7748d 1393 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1394 if (runningTransactions > 0) {
66a7748d 1395 const waitTime = secondsToMilliseconds(15)
62340a29 1396 logger.debug(
944d4529 1397 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1398 waitTime
1399 )} before continuing firmware update simulation`
1400 )
1401 await sleep(waitTime)
1402 transactionsStarted = true
1403 wasTransactionsStarted = true
62340a29 1404 } else {
ded57f02
JB
1405 if (chargingStation.hasEvses) {
1406 for (const [evseId, evseStatus] of chargingStation.evses) {
1407 if (evseId > 0) {
1408 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1409 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1410 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1411 chargingStation,
1412 connectorId,
66a7748d
JB
1413 OCPP16ChargePointStatus.Unavailable
1414 )
ded57f02
JB
1415 }
1416 }
1417 }
1418 }
1419 } else {
1420 for (const connectorId of chargingStation.connectors.keys()) {
1421 if (
1422 connectorId > 0 &&
1423 chargingStation.getConnectorStatus(connectorId)?.status !==
1424 OCPP16ChargePointStatus.Unavailable
1425 ) {
1426 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1427 chargingStation,
1428 connectorId,
66a7748d
JB
1429 OCPP16ChargePointStatus.Unavailable
1430 )
ded57f02 1431 }
62340a29
JB
1432 }
1433 }
66a7748d 1434 transactionsStarted = false
62340a29 1435 }
66a7748d 1436 } while (transactionsStarted)
fcda9151 1437 !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
66a7748d
JB
1438 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1439 return
1bf29f5b 1440 }
c9a4f9ea 1441 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1442 OCPP16FirmwareStatusNotificationRequest,
1443 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1444 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1445 status: OCPP16FirmwareStatus.Installing
1446 })
5199f9fd
JB
1447 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1448 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1449 if (
1450 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1451 OCPP16FirmwareStatus.InstallationFailed
1452 ) {
fcda9151 1453 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
93f0c2c8 1454 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1455 OCPP16FirmwareStatusNotificationRequest,
1456 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1457 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1458 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1459 })
93f0c2c8 1460 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1461 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1462 return
93f0c2c8 1463 }
15748260 1464 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
fcda9151 1465 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
66a7748d 1466 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1467 }
b03df580
JB
1468 }
1469
66a7748d 1470 private async handleRequestGetDiagnostics (
08f130a0 1471 chargingStation: ChargingStation,
66a7748d 1472 commandPayload: GetDiagnosticsRequest
e7aeea18 1473 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1474 if (
66a7748d 1475 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1476 chargingStation,
370ae4ee 1477 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1478 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1479 )
68cb8b91 1480 ) {
90293abb 1481 logger.warn(
66a7748d
JB
1482 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1483 )
1484 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1485 }
66a7748d
JB
1486 const { location } = commandPayload
1487 const uri = new URL(location)
47e22477 1488 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1489 let ftpClient: Client | undefined
47e22477 1490 try {
0ddd2460
JB
1491 const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
1492 ConfigurationSection.log
1493 )
e56bbec5
JB
1494 const logFiles = readdirSync(
1495 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1496 resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!)))
1497 )
0ddd2460
JB
1498 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1499 .filter(file => file.endsWith(extname(logConfiguration.file!)))
1500 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1501 .map(file => join(dirname(logConfiguration.file!), file))
5199f9fd 1502 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1503 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1504 ftpClient = new Client()
47e22477 1505 const accessResponse = await ftpClient.access({
2c975e3a 1506 host: uri.hostname,
9bf0ef23
JB
1507 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1508 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1509 ...(isNotEmptyString(uri.password) && { password: uri.password })
1510 })
1511 let uploadResponse: FTPResponse | undefined
47e22477 1512 if (accessResponse.code === 220) {
a974c8e4 1513 ftpClient.trackProgress(info => {
e7aeea18 1514 logger.info(
56563a3c 1515 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1516 info.bytes / 1024
66a7748d
JB
1517 } bytes transferred from diagnostics archive ${info.name}`
1518 )
6a8329b4
JB
1519 chargingStation.ocppRequestService
1520 .requestHandler<
66a7748d
JB
1521 OCPP16DiagnosticsStatusNotificationRequest,
1522 OCPP16DiagnosticsStatusNotificationResponse
1523 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1524 status: OCPP16DiagnosticsStatus.Uploading
1525 })
ea32ea05 1526 .catch((error: unknown) => {
6a8329b4 1527 logger.error(
944d4529
JB
1528 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1529 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1530 }'`,
66a7748d
JB
1531 error
1532 )
1533 })
1534 })
e7aeea18 1535 uploadResponse = await ftpClient.uploadFrom(
d972af76 1536 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1537 `${uri.pathname}${diagnosticsArchive}`
1538 )
47e22477 1539 if (uploadResponse.code === 226) {
08f130a0 1540 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1541 OCPP16DiagnosticsStatusNotificationRequest,
1542 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1543 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1544 status: OCPP16DiagnosticsStatus.Uploaded
1545 })
5199f9fd 1546 ftpClient.close()
66a7748d 1547 return { fileName: diagnosticsArchive }
47e22477 1548 }
e7aeea18
JB
1549 throw new OCPPError(
1550 ErrorType.GENERIC_ERROR,
5199f9fd 1551 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1552 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1553 )
47e22477 1554 }
e7aeea18
JB
1555 throw new OCPPError(
1556 ErrorType.GENERIC_ERROR,
5199f9fd 1557 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1558 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1559 )
47e22477 1560 } catch (error) {
08f130a0 1561 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1562 OCPP16DiagnosticsStatusNotificationRequest,
1563 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1564 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1565 status: OCPP16DiagnosticsStatus.UploadFailed
1566 })
5199f9fd 1567 ftpClient?.close()
66a7748d 1568 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1569 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1570 chargingStation,
e7aeea18
JB
1571 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1572 error as Error,
66a7748d
JB
1573 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1574 )!
47e22477
JB
1575 }
1576 } else {
e7aeea18 1577 logger.error(
08f130a0 1578 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1579 uri.protocol
66a7748d
JB
1580 } to transfer the diagnostic logs archive`
1581 )
08f130a0 1582 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1583 OCPP16DiagnosticsStatusNotificationRequest,
1584 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1585 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1586 status: OCPP16DiagnosticsStatus.UploadFailed
1587 })
1588 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1589 }
1590 }
802cfa13 1591
66a7748d 1592 private handleRequestTriggerMessage (
08f130a0 1593 chargingStation: ChargingStation,
66a7748d 1594 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1595 ): OCPP16TriggerMessageResponse {
66a7748d 1596 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1597 if (
1598 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1599 chargingStation,
370ae4ee 1600 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1601 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1602 ) ||
0d1f33ba 1603 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1604 ) {
66a7748d 1605 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1606 }
c60ed4b8 1607 if (
4caa7e67 1608 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1609 chargingStation,
1610 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1611 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1612 connectorId!
c60ed4b8
JB
1613 )
1614 ) {
66a7748d 1615 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1616 }
f2f33b97
JB
1617 switch (requestedMessage) {
1618 case OCPP16MessageTrigger.BootNotification:
f2f33b97 1619 case OCPP16MessageTrigger.Heartbeat:
f2f33b97 1620 case OCPP16MessageTrigger.StatusNotification:
f2f33b97
JB
1621 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1622 default:
1623 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1624 }
1625 }
77b95a89 1626
66a7748d 1627 private handleRequestDataTransfer (
77b95a89 1628 chargingStation: ChargingStation,
66a7748d 1629 commandPayload: OCPP16DataTransferRequest
77b95a89 1630 ): OCPP16DataTransferResponse {
66a7748d 1631 const { vendorId } = commandPayload
77b95a89 1632 try {
0d1f33ba 1633 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1634 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1635 }
66a7748d 1636 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1637 } catch (error) {
66a7748d 1638 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1639 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1640 chargingStation,
1641 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1642 error as Error,
66a7748d
JB
1643 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1644 )!
77b95a89
JB
1645 }
1646 }
24578c31 1647
66a7748d 1648 private async handleRequestReserveNow (
24578c31 1649 chargingStation: ChargingStation,
66a7748d 1650 commandPayload: OCPP16ReserveNowRequest
24578c31 1651 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1652 if (
1653 !OCPP16ServiceUtils.checkFeatureProfile(
1654 chargingStation,
1655 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1656 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1657 )
1658 ) {
66a7748d 1659 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1660 }
95dab6cf
JB
1661 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1662 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
66a7748d
JB
1663 const { reservationId, idTag, connectorId } = commandPayload
1664 let response: OCPP16ReserveNowResponse
24578c31 1665 try {
d984c13f 1666 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1667 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1668 }
10e8c3e1 1669 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1670 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1671 }
66dd3447 1672 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1673 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1674 }
66a7748d 1675 await removeExpiredReservations(chargingStation)
f938317f 1676 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1677 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1678 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1679 break
178956d8
JB
1680 case OCPP16ChargePointStatus.Preparing:
1681 case OCPP16ChargePointStatus.Charging:
1682 case OCPP16ChargePointStatus.SuspendedEV:
1683 case OCPP16ChargePointStatus.SuspendedEVSE:
1684 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1685 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1686 break
178956d8 1687 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1688 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1689 break
178956d8 1690 case OCPP16ChargePointStatus.Reserved:
66dd3447 1691 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1692 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1693 break
24578c31
JB
1694 }
1695 // eslint-disable-next-line no-fallthrough
1696 default:
66dd3447 1697 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1698 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1699 break
d193a949
JB
1700 }
1701 await chargingStation.addReservation({
1702 id: commandPayload.reservationId,
66a7748d
JB
1703 ...commandPayload
1704 })
1705 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1706 break
24578c31 1707 }
66a7748d 1708 return response
24578c31 1709 } catch (error) {
66a7748d
JB
1710 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1711 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1712 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1713 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1714 chargingStation,
1715 OCPP16IncomingRequestCommand.RESERVE_NOW,
1716 error as Error,
66a7748d
JB
1717 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1718 )!
24578c31
JB
1719 }
1720 }
1721
66a7748d 1722 private async handleRequestCancelReservation (
24578c31 1723 chargingStation: ChargingStation,
66a7748d 1724 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1725 ): Promise<GenericResponse> {
66dd3447
JB
1726 if (
1727 !OCPP16ServiceUtils.checkFeatureProfile(
1728 chargingStation,
1729 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1730 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1731 )
1732 ) {
66a7748d 1733 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1734 }
24578c31 1735 try {
66a7748d
JB
1736 const { reservationId } = commandPayload
1737 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1738 if (reservation == null) {
90aceaf6 1739 logger.debug(
66a7748d
JB
1740 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1741 )
1742 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1743 }
ec9f36cc 1744 await chargingStation.removeReservation(
300418e9 1745 reservation,
66a7748d
JB
1746 ReservationTerminationReason.RESERVATION_CANCELED
1747 )
1748 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1749 } catch (error) {
66a7748d 1750 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1751 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1752 chargingStation,
1753 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1754 error as Error,
48847bc0
JB
1755 {
1756 errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1757 }
66a7748d 1758 )!
24578c31
JB
1759 }
1760 }
c0560973 1761}