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