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