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