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