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