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