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