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