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