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