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