fix: avoid endless loop 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,
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 1164 if (commandPayload.connectorId == null) {
d0b71736
JB
1165 for (
1166 let connectorId = 1;
1167 connectorId <= chargingStation.getNumberOfConnectors();
1168 connectorId++
1169 ) {
1170 if (
1171 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false &&
1172 !OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, commandPayload.idTag)
1173 ) {
1174 commandPayload.connectorId = connectorId
1175 break
1176 }
1177 }
1178 if (commandPayload.connectorId == null) {
1179 logger.debug(
1180 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1181 chargingStation.stationInfo?.chargingStationId
1182 }, idTag '${commandPayload.idTag}': no available connector found`
8946ba9d 1183 )
d0b71736
JB
1184 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1185 }
c0bbb3ea 1186 }
66a7748d
JB
1187 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1188 if (!chargingStation.hasConnector(transactionConnectorId)) {
a9671b9e 1189 return this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
1190 chargingStation,
1191 transactionConnectorId,
66a7748d
JB
1192 idTag
1193 )
649287f8
JB
1194 }
1195 if (
d193a949
JB
1196 !chargingStation.isChargingStationAvailable() ||
1197 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8 1198 ) {
a9671b9e 1199 return this.notifyRemoteStartTransactionRejected(
649287f8
JB
1200 chargingStation,
1201 transactionConnectorId,
66a7748d
JB
1202 idTag
1203 )
649287f8 1204 }
b10202d7 1205 // idTag authorization check required
d984c13f 1206 if (
66a7748d 1207 chargingStation.getAuthorizeRemoteTxRequests() &&
b10202d7 1208 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
66dd3447 1209 ) {
a9671b9e 1210 return this.notifyRemoteStartTransactionRejected(
08f130a0 1211 chargingStation,
e7aeea18 1212 transactionConnectorId,
66a7748d
JB
1213 idTag
1214 )
c0560973 1215 }
649287f8 1216 if (
b10202d7
JB
1217 chargingProfile != null &&
1218 !this.setRemoteStartTransactionChargingProfile(
1219 chargingStation,
1220 transactionConnectorId,
1221 chargingProfile
1222 )
649287f8 1223 ) {
a9671b9e 1224 return this.notifyRemoteStartTransactionRejected(
649287f8
JB
1225 chargingStation,
1226 transactionConnectorId,
66a7748d
JB
1227 idTag
1228 )
649287f8 1229 }
2665ed1e 1230 logger.debug(
48847bc0
JB
1231 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
1232 chargingStation.stationInfo?.chargingStationId
1233 }#${transactionConnectorId}}, idTag '${idTag}'`
2665ed1e 1234 )
54510a60 1235 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
a7fc8211
JB
1236 }
1237
a9671b9e 1238 private notifyRemoteStartTransactionRejected (
08f130a0 1239 chargingStation: ChargingStation,
e7aeea18 1240 connectorId: number,
66a7748d 1241 idTag: string
a9671b9e 1242 ): GenericResponse {
66a7748d 1243 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
2665ed1e 1244 logger.debug(
48847bc0
JB
1245 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1246 chargingStation.stationInfo?.chargingStationId
1247 }#${connectorId}, idTag '${idTag}', availability '${
1248 connectorStatus?.availability
1249 }', status '${connectorStatus?.status}'`
66a7748d
JB
1250 )
1251 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1252 }
1253
66a7748d 1254 private setRemoteStartTransactionChargingProfile (
08f130a0 1255 chargingStation: ChargingStation,
e7aeea18 1256 connectorId: number,
66a7748d 1257 chargingProfile: OCPP16ChargingProfile
e7aeea18 1258 ): boolean {
5199f9fd 1259 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1260 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1261 logger.debug(
48847bc0
JB
1262 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${
1263 chargingStation.stationInfo?.chargingStationId
1264 }#${connectorId}`,
66a7748d
JB
1265 chargingProfile
1266 )
1267 return true
a7fc8211 1268 }
2665ed1e 1269 logger.debug(
f406808f
JB
1270 `${chargingStation.logPrefix()} Not allowed to set ${
1271 chargingProfile.chargingProfilePurpose
66a7748d
JB
1272 } charging profile(s) at remote start transaction`
1273 )
1274 return false
a7fc8211
JB
1275 }
1276
2665ed1e 1277 private handleRequestRemoteStopTransaction (
08f130a0 1278 chargingStation: ChargingStation,
66a7748d 1279 commandPayload: RemoteStopTransactionRequest
2665ed1e 1280 ): GenericResponse {
66a7748d 1281 const { transactionId } = commandPayload
2665ed1e
JB
1282 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1283 logger.debug(
1284 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1285 )
1286 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
c0560973 1287 }
2665ed1e
JB
1288 logger.debug(
1289 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
66a7748d
JB
1290 )
1291 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1292 }
47e22477 1293
66a7748d 1294 private handleRequestUpdateFirmware (
b03df580 1295 chargingStation: ChargingStation,
66a7748d 1296 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1297 ): OCPP16UpdateFirmwareResponse {
1298 if (
66a7748d 1299 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1300 chargingStation,
1301 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1302 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1303 )
b03df580 1304 ) {
5d280aae 1305 logger.warn(
66a7748d
JB
1306 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1307 )
1308 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1309 }
95dab6cf
JB
1310 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1311 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1312 const { retrieveDate } = commandPayload
382b62c4
JB
1313 if (
1314 chargingStation.stationInfo?.firmwareStatus != null &&
1315 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1316 ) {
5d280aae 1317 logger.warn(
66a7748d
JB
1318 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1319 )
1320 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1321 }
66a7748d 1322 const now = Date.now()
5199f9fd 1323 if (retrieveDate.getTime() <= now) {
66a7748d 1324 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1325 } else {
5199f9fd
JB
1326 setTimeout(() => {
1327 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1328 }, retrieveDate.getTime() - now)
c9a4f9ea 1329 }
66a7748d 1330 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1331 }
1332
66a7748d 1333 private async updateFirmwareSimulation (
c9a4f9ea 1334 chargingStation: ChargingStation,
90293abb 1335 maxDelay = 30,
66a7748d 1336 minDelay = 15
c9a4f9ea 1337 ): Promise<void> {
66a7748d
JB
1338 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1339 return
1bf29f5b 1340 }
ded57f02
JB
1341 if (chargingStation.hasEvses) {
1342 for (const [evseId, evseStatus] of chargingStation.evses) {
1343 if (evseId > 0) {
1344 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1345 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1346 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1347 chargingStation,
1348 connectorId,
66a7748d
JB
1349 OCPP16ChargePointStatus.Unavailable
1350 )
ded57f02
JB
1351 }
1352 }
1353 }
1354 }
1355 } else {
1356 for (const connectorId of chargingStation.connectors.keys()) {
1357 if (
1358 connectorId > 0 &&
1359 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1360 ) {
1361 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1362 chargingStation,
1363 connectorId,
66a7748d
JB
1364 OCPP16ChargePointStatus.Unavailable
1365 )
ded57f02 1366 }
c9a4f9ea
JB
1367 }
1368 }
93f0c2c8 1369 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1370 OCPP16FirmwareStatusNotificationRequest,
1371 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1372 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1373 status: OCPP16FirmwareStatus.Downloading
1374 })
5199f9fd
JB
1375 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1376 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1377 if (
93f0c2c8
JB
1378 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1379 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1380 ) {
fcda9151 1381 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
5d280aae 1382 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1383 OCPP16FirmwareStatusNotificationRequest,
1384 OCPP16FirmwareStatusNotificationResponse
5d280aae 1385 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1386 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1387 })
93f0c2c8 1388 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1389 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1390 return
5d280aae 1391 }
fcda9151 1392 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
c9a4f9ea 1393 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1394 OCPP16FirmwareStatusNotificationRequest,
1395 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1396 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1397 status: OCPP16FirmwareStatus.Downloaded
1398 })
5199f9fd
JB
1399 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1400 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1401 let wasTransactionsStarted = false
1402 let transactionsStarted: boolean
62340a29 1403 do {
66a7748d 1404 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1405 if (runningTransactions > 0) {
66a7748d 1406 const waitTime = secondsToMilliseconds(15)
62340a29 1407 logger.debug(
944d4529 1408 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1409 waitTime
1410 )} before continuing firmware update simulation`
1411 )
1412 await sleep(waitTime)
1413 transactionsStarted = true
1414 wasTransactionsStarted = true
62340a29 1415 } else {
ded57f02
JB
1416 if (chargingStation.hasEvses) {
1417 for (const [evseId, evseStatus] of chargingStation.evses) {
1418 if (evseId > 0) {
1419 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1420 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1421 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1422 chargingStation,
1423 connectorId,
66a7748d
JB
1424 OCPP16ChargePointStatus.Unavailable
1425 )
ded57f02
JB
1426 }
1427 }
1428 }
1429 }
1430 } else {
1431 for (const connectorId of chargingStation.connectors.keys()) {
1432 if (
1433 connectorId > 0 &&
1434 chargingStation.getConnectorStatus(connectorId)?.status !==
1435 OCPP16ChargePointStatus.Unavailable
1436 ) {
1437 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1438 chargingStation,
1439 connectorId,
66a7748d
JB
1440 OCPP16ChargePointStatus.Unavailable
1441 )
ded57f02 1442 }
62340a29
JB
1443 }
1444 }
66a7748d 1445 transactionsStarted = false
62340a29 1446 }
66a7748d 1447 } while (transactionsStarted)
fcda9151 1448 !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
66a7748d
JB
1449 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1450 return
1bf29f5b 1451 }
c9a4f9ea 1452 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1453 OCPP16FirmwareStatusNotificationRequest,
1454 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1455 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1456 status: OCPP16FirmwareStatus.Installing
1457 })
5199f9fd
JB
1458 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1459 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1460 if (
1461 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1462 OCPP16FirmwareStatus.InstallationFailed
1463 ) {
fcda9151 1464 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
93f0c2c8 1465 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1466 OCPP16FirmwareStatusNotificationRequest,
1467 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1468 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1469 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1470 })
93f0c2c8 1471 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1472 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1473 return
93f0c2c8 1474 }
15748260 1475 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
fcda9151 1476 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
66a7748d 1477 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1478 }
b03df580
JB
1479 }
1480
66a7748d 1481 private async handleRequestGetDiagnostics (
08f130a0 1482 chargingStation: ChargingStation,
66a7748d 1483 commandPayload: GetDiagnosticsRequest
e7aeea18 1484 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1485 if (
66a7748d 1486 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1487 chargingStation,
370ae4ee 1488 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1489 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1490 )
68cb8b91 1491 ) {
90293abb 1492 logger.warn(
66a7748d
JB
1493 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1494 )
1495 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1496 }
66a7748d
JB
1497 const { location } = commandPayload
1498 const uri = new URL(location)
47e22477 1499 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1500 let ftpClient: Client | undefined
47e22477 1501 try {
0ddd2460
JB
1502 const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
1503 ConfigurationSection.log
1504 )
e56bbec5
JB
1505 const logFiles = readdirSync(
1506 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1507 resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!)))
1508 )
0ddd2460
JB
1509 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1510 .filter(file => file.endsWith(extname(logConfiguration.file!)))
1511 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1512 .map(file => join(dirname(logConfiguration.file!), file))
5199f9fd 1513 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1514 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1515 ftpClient = new Client()
47e22477 1516 const accessResponse = await ftpClient.access({
2c975e3a 1517 host: uri.hostname,
9bf0ef23
JB
1518 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1519 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1520 ...(isNotEmptyString(uri.password) && { password: uri.password })
1521 })
1522 let uploadResponse: FTPResponse | undefined
47e22477 1523 if (accessResponse.code === 220) {
a974c8e4 1524 ftpClient.trackProgress(info => {
e7aeea18 1525 logger.info(
56563a3c 1526 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1527 info.bytes / 1024
66a7748d
JB
1528 } bytes transferred from diagnostics archive ${info.name}`
1529 )
6a8329b4
JB
1530 chargingStation.ocppRequestService
1531 .requestHandler<
66a7748d
JB
1532 OCPP16DiagnosticsStatusNotificationRequest,
1533 OCPP16DiagnosticsStatusNotificationResponse
1534 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1535 status: OCPP16DiagnosticsStatus.Uploading
1536 })
ea32ea05 1537 .catch((error: unknown) => {
6a8329b4 1538 logger.error(
944d4529
JB
1539 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1540 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1541 }'`,
66a7748d
JB
1542 error
1543 )
1544 })
1545 })
e7aeea18 1546 uploadResponse = await ftpClient.uploadFrom(
d972af76 1547 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1548 `${uri.pathname}${diagnosticsArchive}`
1549 )
47e22477 1550 if (uploadResponse.code === 226) {
08f130a0 1551 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1552 OCPP16DiagnosticsStatusNotificationRequest,
1553 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1554 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1555 status: OCPP16DiagnosticsStatus.Uploaded
1556 })
5199f9fd 1557 ftpClient.close()
66a7748d 1558 return { fileName: diagnosticsArchive }
47e22477 1559 }
e7aeea18
JB
1560 throw new OCPPError(
1561 ErrorType.GENERIC_ERROR,
5199f9fd 1562 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1563 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1564 )
47e22477 1565 }
e7aeea18
JB
1566 throw new OCPPError(
1567 ErrorType.GENERIC_ERROR,
5199f9fd 1568 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1569 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1570 )
47e22477 1571 } catch (error) {
08f130a0 1572 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1573 OCPP16DiagnosticsStatusNotificationRequest,
1574 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1575 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1576 status: OCPP16DiagnosticsStatus.UploadFailed
1577 })
5199f9fd 1578 ftpClient?.close()
66a7748d 1579 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1580 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1581 chargingStation,
e7aeea18
JB
1582 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1583 error as Error,
66a7748d
JB
1584 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1585 )!
47e22477
JB
1586 }
1587 } else {
e7aeea18 1588 logger.error(
08f130a0 1589 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1590 uri.protocol
66a7748d
JB
1591 } to transfer the diagnostic logs archive`
1592 )
08f130a0 1593 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1594 OCPP16DiagnosticsStatusNotificationRequest,
1595 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1596 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1597 status: OCPP16DiagnosticsStatus.UploadFailed
1598 })
1599 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1600 }
1601 }
802cfa13 1602
66a7748d 1603 private handleRequestTriggerMessage (
08f130a0 1604 chargingStation: ChargingStation,
66a7748d 1605 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1606 ): OCPP16TriggerMessageResponse {
66a7748d 1607 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1608 if (
1609 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1610 chargingStation,
370ae4ee 1611 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1612 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1613 ) ||
0d1f33ba 1614 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1615 ) {
66a7748d 1616 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1617 }
c60ed4b8 1618 if (
4caa7e67 1619 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1620 chargingStation,
1621 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1622 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1623 connectorId!
c60ed4b8
JB
1624 )
1625 ) {
66a7748d 1626 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1627 }
f2f33b97
JB
1628 switch (requestedMessage) {
1629 case OCPP16MessageTrigger.BootNotification:
f2f33b97 1630 case OCPP16MessageTrigger.Heartbeat:
f2f33b97 1631 case OCPP16MessageTrigger.StatusNotification:
f2f33b97
JB
1632 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1633 default:
1634 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1635 }
1636 }
77b95a89 1637
66a7748d 1638 private handleRequestDataTransfer (
77b95a89 1639 chargingStation: ChargingStation,
66a7748d 1640 commandPayload: OCPP16DataTransferRequest
77b95a89 1641 ): OCPP16DataTransferResponse {
66a7748d 1642 const { vendorId } = commandPayload
77b95a89 1643 try {
0d1f33ba 1644 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1645 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1646 }
66a7748d 1647 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1648 } catch (error) {
66a7748d 1649 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1650 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1651 chargingStation,
1652 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1653 error as Error,
66a7748d
JB
1654 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1655 )!
77b95a89
JB
1656 }
1657 }
24578c31 1658
66a7748d 1659 private async handleRequestReserveNow (
24578c31 1660 chargingStation: ChargingStation,
66a7748d 1661 commandPayload: OCPP16ReserveNowRequest
24578c31 1662 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1663 if (
1664 !OCPP16ServiceUtils.checkFeatureProfile(
1665 chargingStation,
1666 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1667 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1668 )
1669 ) {
66a7748d 1670 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1671 }
95dab6cf
JB
1672 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1673 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
66a7748d 1674 const { reservationId, idTag, connectorId } = commandPayload
239194e9
JB
1675 if (!chargingStation.hasConnector(connectorId)) {
1676 logger.error(
1677 `${chargingStation.logPrefix()} Trying to reserve a non existing connector id ${connectorId}`
1678 )
1679 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1680 }
626d3ce5
JB
1681 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1682 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1683 }
1684 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1685 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1686 }
1687 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1688 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1689 }
1690 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1691 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
1692 resetAuthorizeConnectorStatus(connectorStatus)
66a7748d 1693 let response: OCPP16ReserveNowResponse
24578c31 1694 try {
66a7748d 1695 await removeExpiredReservations(chargingStation)
239194e9 1696 switch (connectorStatus.status) {
178956d8 1697 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1698 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1699 break
178956d8
JB
1700 case OCPP16ChargePointStatus.Preparing:
1701 case OCPP16ChargePointStatus.Charging:
1702 case OCPP16ChargePointStatus.SuspendedEV:
1703 case OCPP16ChargePointStatus.SuspendedEVSE:
1704 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1705 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1706 break
178956d8 1707 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1708 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1709 break
178956d8 1710 case OCPP16ChargePointStatus.Reserved:
66dd3447 1711 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1712 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1713 break
24578c31
JB
1714 }
1715 // eslint-disable-next-line no-fallthrough
1716 default:
66dd3447 1717 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1718 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1719 break
d193a949
JB
1720 }
1721 await chargingStation.addReservation({
1722 id: commandPayload.reservationId,
66a7748d
JB
1723 ...commandPayload
1724 })
1725 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1726 break
24578c31 1727 }
66a7748d 1728 return response
24578c31 1729 } catch (error) {
66a7748d
JB
1730 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1731 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1732 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1733 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1734 chargingStation,
1735 OCPP16IncomingRequestCommand.RESERVE_NOW,
1736 error as Error,
66a7748d
JB
1737 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1738 )!
24578c31
JB
1739 }
1740 }
1741
66a7748d 1742 private async handleRequestCancelReservation (
24578c31 1743 chargingStation: ChargingStation,
66a7748d 1744 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1745 ): Promise<GenericResponse> {
66dd3447
JB
1746 if (
1747 !OCPP16ServiceUtils.checkFeatureProfile(
1748 chargingStation,
1749 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1750 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1751 )
1752 ) {
66a7748d 1753 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1754 }
24578c31 1755 try {
66a7748d
JB
1756 const { reservationId } = commandPayload
1757 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1758 if (reservation == null) {
90aceaf6 1759 logger.debug(
66a7748d
JB
1760 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1761 )
1762 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1763 }
ec9f36cc 1764 await chargingStation.removeReservation(
300418e9 1765 reservation,
66a7748d
JB
1766 ReservationTerminationReason.RESERVATION_CANCELED
1767 )
1768 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1769 } catch (error) {
66a7748d 1770 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1771 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1772 chargingStation,
1773 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1774 error as Error,
48847bc0
JB
1775 {
1776 errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1777 }
66a7748d 1778 )!
24578c31
JB
1779 }
1780 }
c0560973 1781}