fix: ensure WS connection retry flag is reset in all connection case
[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 }
66dd3447 984 const remoteStartTransactionLogMsg = `
a223d9be
JB
985 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
986 chargingStation.stationInfo?.chargingStationId
987 }#${transactionConnectorId} for idTag '${idTag}'`
649287f8
JB
988 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
989 chargingStation,
990 transactionConnectorId,
66a7748d
JB
991 OCPP16ChargePointStatus.Preparing
992 )
993 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
994 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!
d984c13f
JB
995 // Authorization check required
996 if (
66a7748d 997 chargingStation.getAuthorizeRemoteTxRequests() &&
66dd3447
JB
998 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
999 ) {
1000 // Authorization successful, start transaction
1001 if (
66a7748d 1002 (chargingProfile != null &&
8e3437b1
JB
1003 this.setRemoteStartTransactionChargingProfile(
1004 chargingStation,
1005 transactionConnectorId,
66a7748d
JB
1006 chargingProfile
1007 )) ||
1008 chargingProfile == null
66dd3447 1009 ) {
66a7748d 1010 connectorStatus.transactionRemoteStarted = true
e7aeea18 1011 if (
66dd3447
JB
1012 (
1013 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1014 OCPP16StartTransactionRequest,
1015 OCPP16StartTransactionResponse
d984c13f
JB
1016 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1017 connectorId: transactionConnectorId,
66a7748d 1018 idTag
d984c13f 1019 })
66dd3447 1020 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 1021 ) {
66a7748d
JB
1022 logger.debug(remoteStartTransactionLogMsg)
1023 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
e060fe58 1024 }
66a7748d 1025 return await this.notifyRemoteStartTransactionRejected(
08f130a0 1026 chargingStation,
e7aeea18 1027 transactionConnectorId,
66a7748d
JB
1028 idTag
1029 )
c0560973 1030 }
66a7748d 1031 return await this.notifyRemoteStartTransactionRejected(
08f130a0 1032 chargingStation,
e7aeea18 1033 transactionConnectorId,
66a7748d
JB
1034 idTag
1035 )
c0560973 1036 }
649287f8
JB
1037 // No authorization check required, start transaction
1038 if (
66a7748d 1039 (chargingProfile != null &&
8e3437b1
JB
1040 this.setRemoteStartTransactionChargingProfile(
1041 chargingStation,
1042 transactionConnectorId,
66a7748d
JB
1043 chargingProfile
1044 )) ||
1045 chargingProfile == null
649287f8 1046 ) {
66a7748d 1047 connectorStatus.transactionRemoteStarted = true
649287f8
JB
1048 if (
1049 (
1050 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1051 OCPP16StartTransactionRequest,
1052 OCPP16StartTransactionResponse
649287f8
JB
1053 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1054 connectorId: transactionConnectorId,
66a7748d 1055 idTag
649287f8
JB
1056 })
1057 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
1058 ) {
66a7748d
JB
1059 logger.debug(remoteStartTransactionLogMsg)
1060 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
649287f8 1061 }
66a7748d 1062 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1063 chargingStation,
1064 transactionConnectorId,
66a7748d
JB
1065 idTag
1066 )
649287f8 1067 }
66a7748d 1068 return await this.notifyRemoteStartTransactionRejected(
08f130a0
JB
1069 chargingStation,
1070 transactionConnectorId,
66a7748d
JB
1071 idTag
1072 )
a7fc8211
JB
1073 }
1074
66a7748d 1075 private async notifyRemoteStartTransactionRejected (
08f130a0 1076 chargingStation: ChargingStation,
e7aeea18 1077 connectorId: number,
66a7748d 1078 idTag: string
f03e1042 1079 ): Promise<GenericResponse> {
66a7748d 1080 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f406808f 1081 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
1082 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1083 chargingStation,
ef6fa3fb 1084 connectorId,
66a7748d
JB
1085 OCPP16ChargePointStatus.Available
1086 )
e060fe58 1087 }
e7aeea18 1088 logger.warn(
66a7748d
JB
1089 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1090 )
1091 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1092 }
1093
66a7748d 1094 private setRemoteStartTransactionChargingProfile (
08f130a0 1095 chargingStation: ChargingStation,
e7aeea18 1096 connectorId: number,
66a7748d 1097 chargingProfile: OCPP16ChargingProfile
e7aeea18 1098 ): boolean {
5199f9fd 1099 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1100 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1101 logger.debug(
944d4529 1102 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
66a7748d
JB
1103 chargingProfile
1104 )
1105 return true
a7fc8211 1106 }
f406808f
JB
1107 logger.warn(
1108 `${chargingStation.logPrefix()} Not allowed to set ${
1109 chargingProfile.chargingProfilePurpose
66a7748d
JB
1110 } charging profile(s) at remote start transaction`
1111 )
1112 return false
a7fc8211
JB
1113 }
1114
66a7748d 1115 private async handleRequestRemoteStopTransaction (
08f130a0 1116 chargingStation: ChargingStation,
66a7748d 1117 commandPayload: RemoteStopTransactionRequest
f03e1042 1118 ): Promise<GenericResponse> {
66a7748d 1119 const { transactionId } = commandPayload
ded57f02
JB
1120 if (chargingStation.hasEvses) {
1121 for (const [evseId, evseStatus] of chargingStation.evses) {
1122 if (evseId > 0) {
1123 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1124 if (connectorStatus.transactionId === transactionId) {
66a7748d 1125 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ded57f02
JB
1126 }
1127 }
1128 }
1129 }
1130 } else {
1131 for (const connectorId of chargingStation.connectors.keys()) {
1132 if (
1133 connectorId > 0 &&
1134 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1135 ) {
66a7748d 1136 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ef6fa3fb 1137 }
c0560973
JB
1138 }
1139 }
44b9b577 1140 logger.warn(
66a7748d
JB
1141 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1142 )
1143 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1144 }
47e22477 1145
66a7748d 1146 private handleRequestUpdateFirmware (
b03df580 1147 chargingStation: ChargingStation,
66a7748d 1148 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1149 ): OCPP16UpdateFirmwareResponse {
1150 if (
66a7748d 1151 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1152 chargingStation,
1153 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1154 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1155 )
b03df580 1156 ) {
5d280aae 1157 logger.warn(
66a7748d
JB
1158 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1159 )
1160 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1161 }
95dab6cf
JB
1162 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1163 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1164 const { retrieveDate } = commandPayload
5199f9fd 1165 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
5d280aae 1166 logger.warn(
66a7748d
JB
1167 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1168 )
1169 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1170 }
66a7748d 1171 const now = Date.now()
5199f9fd 1172 if (retrieveDate.getTime() <= now) {
66a7748d 1173 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1174 } else {
5199f9fd
JB
1175 setTimeout(() => {
1176 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1177 }, retrieveDate.getTime() - now)
c9a4f9ea 1178 }
66a7748d 1179 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1180 }
1181
66a7748d 1182 private async updateFirmwareSimulation (
c9a4f9ea 1183 chargingStation: ChargingStation,
90293abb 1184 maxDelay = 30,
66a7748d 1185 minDelay = 15
c9a4f9ea 1186 ): Promise<void> {
66a7748d
JB
1187 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1188 return
1bf29f5b 1189 }
ded57f02
JB
1190 if (chargingStation.hasEvses) {
1191 for (const [evseId, evseStatus] of chargingStation.evses) {
1192 if (evseId > 0) {
1193 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1194 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1195 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1196 chargingStation,
1197 connectorId,
66a7748d
JB
1198 OCPP16ChargePointStatus.Unavailable
1199 )
ded57f02
JB
1200 }
1201 }
1202 }
1203 }
1204 } else {
1205 for (const connectorId of chargingStation.connectors.keys()) {
1206 if (
1207 connectorId > 0 &&
1208 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1209 ) {
1210 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1211 chargingStation,
1212 connectorId,
66a7748d
JB
1213 OCPP16ChargePointStatus.Unavailable
1214 )
ded57f02 1215 }
c9a4f9ea
JB
1216 }
1217 }
93f0c2c8 1218 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1219 OCPP16FirmwareStatusNotificationRequest,
1220 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1221 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1222 status: OCPP16FirmwareStatus.Downloading
1223 })
5199f9fd
JB
1224 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1225 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1226 if (
93f0c2c8
JB
1227 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1228 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1229 ) {
66a7748d 1230 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
5d280aae 1231 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1232 OCPP16FirmwareStatusNotificationRequest,
1233 OCPP16FirmwareStatusNotificationResponse
5d280aae 1234 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1235 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1236 })
93f0c2c8 1237 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1238 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1239 return
5d280aae 1240 }
66a7748d 1241 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
c9a4f9ea 1242 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1246 status: OCPP16FirmwareStatus.Downloaded
1247 })
5199f9fd
JB
1248 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1249 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1250 let wasTransactionsStarted = false
1251 let transactionsStarted: boolean
62340a29 1252 do {
66a7748d 1253 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1254 if (runningTransactions > 0) {
66a7748d 1255 const waitTime = secondsToMilliseconds(15)
62340a29 1256 logger.debug(
944d4529 1257 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1258 waitTime
1259 )} before continuing firmware update simulation`
1260 )
1261 await sleep(waitTime)
1262 transactionsStarted = true
1263 wasTransactionsStarted = true
62340a29 1264 } else {
ded57f02
JB
1265 if (chargingStation.hasEvses) {
1266 for (const [evseId, evseStatus] of chargingStation.evses) {
1267 if (evseId > 0) {
1268 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1269 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1270 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1271 chargingStation,
1272 connectorId,
66a7748d
JB
1273 OCPP16ChargePointStatus.Unavailable
1274 )
ded57f02
JB
1275 }
1276 }
1277 }
1278 }
1279 } else {
1280 for (const connectorId of chargingStation.connectors.keys()) {
1281 if (
1282 connectorId > 0 &&
1283 chargingStation.getConnectorStatus(connectorId)?.status !==
1284 OCPP16ChargePointStatus.Unavailable
1285 ) {
1286 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1287 chargingStation,
1288 connectorId,
66a7748d
JB
1289 OCPP16ChargePointStatus.Unavailable
1290 )
ded57f02 1291 }
62340a29
JB
1292 }
1293 }
66a7748d 1294 transactionsStarted = false
62340a29 1295 }
66a7748d 1296 } while (transactionsStarted)
be4c6702 1297 !wasTransactionsStarted &&
66a7748d
JB
1298 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1299 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1300 return
1bf29f5b 1301 }
c9a4f9ea 1302 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1303 OCPP16FirmwareStatusNotificationRequest,
1304 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1305 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1306 status: OCPP16FirmwareStatus.Installing
1307 })
5199f9fd
JB
1308 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1309 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1310 if (
1311 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1312 OCPP16FirmwareStatus.InstallationFailed
1313 ) {
66a7748d 1314 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
93f0c2c8 1315 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1316 OCPP16FirmwareStatusNotificationRequest,
1317 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1318 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1319 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1320 })
93f0c2c8 1321 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1322 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1323 return
93f0c2c8 1324 }
15748260 1325 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
66a7748d
JB
1326 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1327 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1328 }
b03df580
JB
1329 }
1330
66a7748d 1331 private async handleRequestGetDiagnostics (
08f130a0 1332 chargingStation: ChargingStation,
66a7748d 1333 commandPayload: GetDiagnosticsRequest
e7aeea18 1334 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1335 if (
66a7748d 1336 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1337 chargingStation,
370ae4ee 1338 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1339 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1340 )
68cb8b91 1341 ) {
90293abb 1342 logger.warn(
66a7748d
JB
1343 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1344 )
1345 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1346 }
66a7748d
JB
1347 const { location } = commandPayload
1348 const uri = new URL(location)
47e22477 1349 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1350 let ftpClient: Client | undefined
47e22477 1351 try {
d972af76 1352 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
a974c8e4
JB
1353 .filter(file => file.endsWith('.log'))
1354 .map(file => join('./', file))
5199f9fd 1355 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1356 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1357 ftpClient = new Client()
47e22477
JB
1358 const accessResponse = await ftpClient.access({
1359 host: uri.host,
9bf0ef23
JB
1360 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1361 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1362 ...(isNotEmptyString(uri.password) && { password: uri.password })
1363 })
1364 let uploadResponse: FTPResponse | undefined
47e22477 1365 if (accessResponse.code === 220) {
a974c8e4 1366 ftpClient.trackProgress(info => {
e7aeea18 1367 logger.info(
56563a3c 1368 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1369 info.bytes / 1024
66a7748d
JB
1370 } bytes transferred from diagnostics archive ${info.name}`
1371 )
6a8329b4
JB
1372 chargingStation.ocppRequestService
1373 .requestHandler<
66a7748d
JB
1374 OCPP16DiagnosticsStatusNotificationRequest,
1375 OCPP16DiagnosticsStatusNotificationResponse
1376 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1377 status: OCPP16DiagnosticsStatus.Uploading
1378 })
a974c8e4 1379 .catch(error => {
6a8329b4 1380 logger.error(
944d4529
JB
1381 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1382 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1383 }'`,
66a7748d
JB
1384 error
1385 )
1386 })
1387 })
e7aeea18 1388 uploadResponse = await ftpClient.uploadFrom(
d972af76 1389 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1390 `${uri.pathname}${diagnosticsArchive}`
1391 )
47e22477 1392 if (uploadResponse.code === 226) {
08f130a0 1393 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1394 OCPP16DiagnosticsStatusNotificationRequest,
1395 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1396 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1397 status: OCPP16DiagnosticsStatus.Uploaded
1398 })
5199f9fd 1399 ftpClient.close()
66a7748d 1400 return { fileName: diagnosticsArchive }
47e22477 1401 }
e7aeea18
JB
1402 throw new OCPPError(
1403 ErrorType.GENERIC_ERROR,
5199f9fd 1404 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1405 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1406 )
47e22477 1407 }
e7aeea18
JB
1408 throw new OCPPError(
1409 ErrorType.GENERIC_ERROR,
5199f9fd 1410 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1411 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1412 )
47e22477 1413 } catch (error) {
08f130a0 1414 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1415 OCPP16DiagnosticsStatusNotificationRequest,
1416 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1417 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1418 status: OCPP16DiagnosticsStatus.UploadFailed
1419 })
5199f9fd 1420 ftpClient?.close()
66a7748d 1421 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1422 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1423 chargingStation,
e7aeea18
JB
1424 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1425 error as Error,
66a7748d
JB
1426 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1427 )!
47e22477
JB
1428 }
1429 } else {
e7aeea18 1430 logger.error(
08f130a0 1431 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1432 uri.protocol
66a7748d
JB
1433 } to transfer the diagnostic logs archive`
1434 )
08f130a0 1435 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1436 OCPP16DiagnosticsStatusNotificationRequest,
1437 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1438 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1439 status: OCPP16DiagnosticsStatus.UploadFailed
1440 })
1441 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1442 }
1443 }
802cfa13 1444
66a7748d 1445 private handleRequestTriggerMessage (
08f130a0 1446 chargingStation: ChargingStation,
66a7748d 1447 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1448 ): OCPP16TriggerMessageResponse {
66a7748d 1449 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1450 if (
1451 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1452 chargingStation,
370ae4ee 1453 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1454 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1455 ) ||
0d1f33ba 1456 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1457 ) {
66a7748d 1458 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1459 }
c60ed4b8 1460 if (
4caa7e67 1461 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1462 chargingStation,
1463 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1464 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1465 connectorId!
c60ed4b8
JB
1466 )
1467 ) {
66a7748d 1468 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1469 }
802cfa13 1470 try {
0d1f33ba 1471 switch (requestedMessage) {
c60ed4b8 1472 case OCPP16MessageTrigger.BootNotification:
802cfa13 1473 setTimeout(() => {
08f130a0 1474 chargingStation.ocppRequestService
f7f98c68 1475 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
66a7748d
JB
1476 chargingStation,
1477 OCPP16RequestCommand.BOOT_NOTIFICATION,
1478 chargingStation.bootNotificationRequest,
1479 { skipBufferingOnError: true, triggerMessage: true }
1480 )
a974c8e4 1481 .then(response => {
66a7748d 1482 chargingStation.bootNotificationResponse = response
ae711c83 1483 })
66a7748d
JB
1484 .catch(Constants.EMPTY_FUNCTION)
1485 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1486 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1487 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1488 setTimeout(() => {
08f130a0 1489 chargingStation.ocppRequestService
f7f98c68 1490 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
66a7748d
JB
1491 chargingStation,
1492 OCPP16RequestCommand.HEARTBEAT,
1493 undefined,
1494 {
1495 triggerMessage: true
1496 }
1497 )
1498 .catch(Constants.EMPTY_FUNCTION)
1499 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1500 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1501 case OCPP16MessageTrigger.StatusNotification:
dc661702 1502 setTimeout(() => {
be9f397b 1503 if (connectorId != null) {
08f130a0 1504 chargingStation.ocppRequestService
dc661702 1505 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
66a7748d
JB
1506 chargingStation,
1507 OCPP16RequestCommand.STATUS_NOTIFICATION,
1508 {
1509 connectorId,
1510 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
be9f397b 1511 status: chargingStation.getConnectorStatus(connectorId)?.status
66a7748d
JB
1512 },
1513 {
1514 triggerMessage: true
1515 }
1516 )
1517 .catch(Constants.EMPTY_FUNCTION)
f938317f
JB
1518 } else if (chargingStation.hasEvses) {
1519 for (const evseStatus of chargingStation.evses.values()) {
1520 for (const [id, connectorStatus] of evseStatus.connectors) {
66a7748d
JB
1521 chargingStation.ocppRequestService
1522 .requestHandler<
1523 OCPP16StatusNotificationRequest,
1524 OCPP16StatusNotificationResponse
1525 >(
1526 chargingStation,
1527 OCPP16RequestCommand.STATUS_NOTIFICATION,
1528 {
1529 connectorId: id,
1530 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
f938317f 1531 status: connectorStatus.status
66a7748d
JB
1532 },
1533 {
1534 triggerMessage: true
1535 }
1536 )
1537 .catch(Constants.EMPTY_FUNCTION)
ded57f02 1538 }
dc661702 1539 }
f938317f
JB
1540 } else {
1541 for (const [id, connectorStatus] of chargingStation.connectors) {
1542 chargingStation.ocppRequestService
1543 .requestHandler<
1544 OCPP16StatusNotificationRequest,
1545 OCPP16StatusNotificationResponse
1546 >(
1547 chargingStation,
1548 OCPP16RequestCommand.STATUS_NOTIFICATION,
1549 {
1550 connectorId: id,
1551 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1552 status: connectorStatus.status
1553 },
1554 {
1555 triggerMessage: true
1556 }
1557 )
1558 .catch(Constants.EMPTY_FUNCTION)
1559 }
dc661702 1560 }
66a7748d
JB
1561 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1562 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
802cfa13 1563 default:
66a7748d 1564 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1565 }
1566 } catch (error) {
66a7748d 1567 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1568 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1569 chargingStation,
e7aeea18
JB
1570 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1571 error as Error,
66a7748d
JB
1572 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1573 )!
802cfa13
JB
1574 }
1575 }
77b95a89 1576
66a7748d 1577 private handleRequestDataTransfer (
77b95a89 1578 chargingStation: ChargingStation,
66a7748d 1579 commandPayload: OCPP16DataTransferRequest
77b95a89 1580 ): OCPP16DataTransferResponse {
66a7748d 1581 const { vendorId } = commandPayload
77b95a89 1582 try {
0d1f33ba 1583 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1584 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1585 }
66a7748d 1586 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1587 } catch (error) {
66a7748d 1588 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1589 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1590 chargingStation,
1591 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1592 error as Error,
66a7748d
JB
1593 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1594 )!
77b95a89
JB
1595 }
1596 }
24578c31 1597
66a7748d 1598 private async handleRequestReserveNow (
24578c31 1599 chargingStation: ChargingStation,
66a7748d 1600 commandPayload: OCPP16ReserveNowRequest
24578c31 1601 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1602 if (
1603 !OCPP16ServiceUtils.checkFeatureProfile(
1604 chargingStation,
1605 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1606 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1607 )
1608 ) {
66a7748d 1609 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1610 }
95dab6cf
JB
1611 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1612 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
66a7748d
JB
1613 const { reservationId, idTag, connectorId } = commandPayload
1614 let response: OCPP16ReserveNowResponse
24578c31 1615 try {
d984c13f 1616 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1617 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1618 }
10e8c3e1 1619 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1620 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1621 }
66dd3447 1622 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1623 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1624 }
66a7748d 1625 await removeExpiredReservations(chargingStation)
f938317f 1626 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1627 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1628 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1629 break
178956d8
JB
1630 case OCPP16ChargePointStatus.Preparing:
1631 case OCPP16ChargePointStatus.Charging:
1632 case OCPP16ChargePointStatus.SuspendedEV:
1633 case OCPP16ChargePointStatus.SuspendedEVSE:
1634 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1635 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1636 break
178956d8 1637 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1638 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1639 break
178956d8 1640 case OCPP16ChargePointStatus.Reserved:
66dd3447 1641 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1642 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1643 break
24578c31
JB
1644 }
1645 // eslint-disable-next-line no-fallthrough
1646 default:
66dd3447 1647 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1648 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1649 break
d193a949
JB
1650 }
1651 await chargingStation.addReservation({
1652 id: commandPayload.reservationId,
66a7748d
JB
1653 ...commandPayload
1654 })
1655 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1656 break
24578c31 1657 }
66a7748d 1658 return response
24578c31 1659 } catch (error) {
66a7748d
JB
1660 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1661 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1662 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1663 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1664 chargingStation,
1665 OCPP16IncomingRequestCommand.RESERVE_NOW,
1666 error as Error,
66a7748d
JB
1667 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1668 )!
24578c31
JB
1669 }
1670 }
1671
66a7748d 1672 private async handleRequestCancelReservation (
24578c31 1673 chargingStation: ChargingStation,
66a7748d 1674 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1675 ): Promise<GenericResponse> {
66dd3447
JB
1676 if (
1677 !OCPP16ServiceUtils.checkFeatureProfile(
1678 chargingStation,
1679 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1680 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1681 )
1682 ) {
66a7748d 1683 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1684 }
24578c31 1685 try {
66a7748d
JB
1686 const { reservationId } = commandPayload
1687 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1688 if (reservation == null) {
90aceaf6 1689 logger.debug(
66a7748d
JB
1690 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1691 )
1692 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1693 }
ec9f36cc 1694 await chargingStation.removeReservation(
300418e9 1695 reservation,
66a7748d
JB
1696 ReservationTerminationReason.RESERVATION_CANCELED
1697 )
1698 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1699 } catch (error) {
66a7748d 1700 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1701 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1702 chargingStation,
1703 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1704 error as Error,
66a7748d
JB
1705 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1706 )!
24578c31
JB
1707 }
1708 }
c0560973 1709}