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