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