refactor: cleanup log messages formatting
[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 ])
54510a60
JB
407 this.on(
408 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
409 (chargingStation: ChargingStation, connectorId: number, idTag: string) => {
410 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
411 chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
412 chargingStation.ocppRequestService
413 .requestHandler<OCPP16StartTransactionRequest, OCPP16StartTransactionResponse>(
414 chargingStation,
415 OCPP16RequestCommand.START_TRANSACTION,
416 {
417 connectorId,
418 idTag
419 }
420 )
421 .then(response => {
422 if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
4e41ea24
JB
423 logger.debug(
424 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
425 )
54510a60 426 } else {
4e41ea24
JB
427 logger.debug(
428 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
429 )
54510a60
JB
430 }
431 })
432 .catch(error => {
433 logger.error(
434 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
435 error
436 )
437 })
438 }
439 )
ba9a56a6 440 this.validatePayload = this.validatePayload.bind(this)
58144adb
JB
441 }
442
9429aa42 443 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 444 chargingStation: ChargingStation,
e7aeea18
JB
445 messageId: string,
446 commandName: OCPP16IncomingRequestCommand,
66a7748d 447 commandPayload: ReqType
e7aeea18 448 ): Promise<void> {
66a7748d 449 let response: ResType
e7aeea18 450 if (
5398cecf 451 chargingStation.stationInfo?.ocppStrictCompliance === true &&
66a7748d 452 chargingStation.inPendingState() &&
e7aeea18
JB
453 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
454 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
455 ) {
456 throw new OCPPError(
457 ErrorType.SECURITY_ERROR,
e3018bc4 458 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 459 commandPayload,
4ed03b6e 460 undefined,
66a7748d 461 2
e7aeea18 462 )} while the charging station is in pending state on the central server`,
7369e417 463 commandName,
66a7748d
JB
464 commandPayload
465 )
caad9d6b 466 }
e7aeea18 467 if (
66a7748d 468 chargingStation.isRegistered() ||
5398cecf 469 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
66a7748d 470 chargingStation.inUnknownState())
e7aeea18 471 ) {
65554cc3 472 if (
66a7748d
JB
473 this.incomingRequestHandlers.has(commandName) &&
474 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
65554cc3 475 ) {
124f3553 476 try {
66a7748d 477 this.validatePayload(chargingStation, commandName, commandPayload)
c75a6675 478 // Call the method to build the response
66a7748d 479 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9429aa42 480 response = (await this.incomingRequestHandlers.get(commandName)!(
08f130a0 481 chargingStation,
66a7748d
JB
482 commandPayload
483 )) as ResType
124f3553
JB
484 } catch (error) {
485 // Log
6c8f5d90 486 logger.error(
944d4529 487 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
66a7748d
JB
488 error
489 )
490 throw error
124f3553
JB
491 }
492 } else {
493 // Throw exception
e7aeea18
JB
494 throw new OCPPError(
495 ErrorType.NOT_IMPLEMENTED,
e3018bc4 496 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18 497 commandPayload,
4ed03b6e 498 undefined,
66a7748d 499 2
e7aeea18 500 )}`,
7369e417 501 commandName,
66a7748d
JB
502 commandPayload
503 )
c0560973
JB
504 }
505 } else {
e7aeea18
JB
506 throw new OCPPError(
507 ErrorType.SECURITY_ERROR,
e3018bc4 508 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 509 commandPayload,
4ed03b6e 510 undefined,
66a7748d 511 2
e7aeea18 512 )} while the charging station is not registered on the central server.`,
7369e417 513 commandName,
66a7748d
JB
514 commandPayload
515 )
c0560973 516 }
c75a6675 517 // Send the built response
08f130a0
JB
518 await chargingStation.ocppRequestService.sendResponse(
519 chargingStation,
520 messageId,
521 response,
66a7748d
JB
522 commandName
523 )
c0560973
JB
524 }
525
66a7748d 526 private validatePayload (
9c5c4195
JB
527 chargingStation: ChargingStation,
528 commandName: OCPP16IncomingRequestCommand,
66a7748d 529 commandPayload: JsonType
9c5c4195 530 ): boolean {
d5490a13 531 if (this.payloadValidateFunctions.has(commandName)) {
24d15716 532 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
9c5c4195
JB
533 }
534 logger.warn(
24d15716 535 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
536 )
537 return false
9c5c4195
JB
538 }
539
c0560973 540 // Simulate charging station restart
66a7748d 541 private handleRequestReset (
08f130a0 542 chargingStation: ChargingStation,
66a7748d 543 commandPayload: ResetRequest
f03e1042 544 ): GenericResponse {
66a7748d 545 const { type } = commandPayload
d1ff8599
JB
546 chargingStation
547 .reset(`${type}Reset` as OCPP16StopTransactionReason)
66a7748d 548 .catch(Constants.EMPTY_FUNCTION)
e7aeea18 549 logger.info(
944d4529 550 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
66a7748d 551 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 552 chargingStation.stationInfo!.resetTime!
66a7748d
JB
553 )}`
554 )
555 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
c0560973
JB
556 }
557
66a7748d 558 private async handleRequestUnlockConnector (
08f130a0 559 chargingStation: ChargingStation,
66a7748d 560 commandPayload: UnlockConnectorRequest
e7aeea18 561 ): Promise<UnlockConnectorResponse> {
66a7748d
JB
562 const { connectorId } = commandPayload
563 if (!chargingStation.hasConnector(connectorId)) {
c60ed4b8 564 logger.error(
66a7748d
JB
565 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
566 )
567 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c60ed4b8 568 }
c0560973 569 if (connectorId === 0) {
66a7748d
JB
570 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
571 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c0560973 572 }
5e3cb728
JB
573 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
574 const stopResponse = await chargingStation.stopTransactionOnConnector(
575 connectorId,
66a7748d
JB
576 OCPP16StopTransactionReason.UNLOCK_COMMAND
577 )
c0560973 578 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d 579 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973 580 }
66a7748d 581 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
c0560973 582 }
4ecff7ce
JB
583 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
584 chargingStation,
ef6fa3fb 585 connectorId,
66a7748d
JB
586 OCPP16ChargePointStatus.Available
587 )
588 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973
JB
589 }
590
66a7748d 591 private handleRequestGetConfiguration (
08f130a0 592 chargingStation: ChargingStation,
66a7748d 593 commandPayload: GetConfigurationRequest
e7aeea18 594 ): GetConfigurationResponse {
66a7748d
JB
595 const { key } = commandPayload
596 const configurationKey: OCPPConfigurationKey[] = []
597 const unknownKey: string[] = []
300418e9 598 if (key == null) {
66a7748d 599 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563e40ce
JB
600 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
601 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
66a7748d 602 continue
c0560973
JB
603 }
604 configurationKey.push({
563e40ce
JB
605 key: configKey.key,
606 readonly: configKey.readonly,
607 value: configKey.value
66a7748d 608 })
c0560973 609 }
66a7748d 610 } else if (isNotEmptyArray(key)) {
300418e9 611 for (const k of key) {
66a7748d 612 const keyFound = getConfigurationKey(chargingStation, k, true)
a807045b 613 if (keyFound != null) {
563e40ce 614 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
66a7748d 615 continue
c0560973
JB
616 }
617 configurationKey.push({
618 key: keyFound.key,
619 readonly: keyFound.readonly,
66a7748d
JB
620 value: keyFound.value
621 })
c0560973 622 } else {
66a7748d 623 unknownKey.push(k)
c0560973
JB
624 }
625 }
626 }
627 return {
628 configurationKey,
66a7748d
JB
629 unknownKey
630 }
c0560973
JB
631 }
632
66a7748d 633 private handleRequestChangeConfiguration (
08f130a0 634 chargingStation: ChargingStation,
66a7748d 635 commandPayload: ChangeConfigurationRequest
e7aeea18 636 ): ChangeConfigurationResponse {
66a7748d
JB
637 const { key, value } = commandPayload
638 const keyToChange = getConfigurationKey(chargingStation, key, true)
e1d9a0f4 639 if (keyToChange?.readonly === true) {
66a7748d 640 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
bd5d98e0 641 } else if (keyToChange?.readonly === false) {
66a7748d 642 let valueChanged = false
0d1f33ba 643 if (keyToChange.value !== value) {
66a7748d
JB
644 setConfigurationKeyValue(chargingStation, key, value, true)
645 valueChanged = true
c0560973 646 }
66a7748d 647 let triggerHeartbeatRestart = false
e1d9a0f4
JB
648 if (
649 (keyToChange.key as OCPP16StandardParametersKey) ===
650 OCPP16StandardParametersKey.HeartBeatInterval &&
651 valueChanged
652 ) {
f2d5e3d9 653 setConfigurationKeyValue(
17ac262c 654 chargingStation,
e7aeea18 655 OCPP16StandardParametersKey.HeartbeatInterval,
66a7748d
JB
656 value
657 )
658 triggerHeartbeatRestart = true
c0560973 659 }
e1d9a0f4
JB
660 if (
661 (keyToChange.key as OCPP16StandardParametersKey) ===
662 OCPP16StandardParametersKey.HeartbeatInterval &&
663 valueChanged
664 ) {
f2d5e3d9 665 setConfigurationKeyValue(
17ac262c 666 chargingStation,
e7aeea18 667 OCPP16StandardParametersKey.HeartBeatInterval,
66a7748d
JB
668 value
669 )
670 triggerHeartbeatRestart = true
c0560973
JB
671 }
672 if (triggerHeartbeatRestart) {
66a7748d 673 chargingStation.restartHeartbeat()
c0560973 674 }
e1d9a0f4
JB
675 if (
676 (keyToChange.key as OCPP16StandardParametersKey) ===
677 OCPP16StandardParametersKey.WebSocketPingInterval &&
678 valueChanged
679 ) {
66a7748d 680 chargingStation.restartWebSocketPing()
c0560973 681 }
66a7748d
JB
682 if (keyToChange.reboot === true) {
683 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
c0560973 684 }
66a7748d 685 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
c0560973 686 }
66a7748d 687 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
c0560973
JB
688 }
689
66a7748d 690 private handleRequestSetChargingProfile (
08f130a0 691 chargingStation: ChargingStation,
66a7748d 692 commandPayload: SetChargingProfileRequest
e7aeea18 693 ): SetChargingProfileResponse {
370ae4ee 694 if (
66a7748d 695 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 696 chargingStation,
370ae4ee 697 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
698 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
699 )
370ae4ee 700 ) {
66a7748d 701 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
68cb8b91 702 }
66a7748d
JB
703 const { connectorId, csChargingProfiles } = commandPayload
704 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 705 logger.error(
66a7748d
JB
706 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
707 )
708 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 709 }
e7aeea18 710 if (
0d1f33ba 711 csChargingProfiles.chargingProfilePurpose ===
0ac97927 712 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
0d1f33ba 713 connectorId !== 0
e7aeea18 714 ) {
66a7748d 715 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 716 }
e7aeea18 717 if (
0d1f33ba 718 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
86f51b96
JB
719 connectorId === 0
720 ) {
721 logger.error(
66a7748d
JB
722 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
723 )
724 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 725 }
66a7748d 726 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
86f51b96
JB
727 if (
728 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
729 connectorId > 0 &&
730 connectorStatus?.transactionStarted === false
e7aeea18 731 ) {
db0af086 732 logger.error(
66a7748d
JB
733 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
734 )
735 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 736 }
86f51b96
JB
737 if (
738 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
739 connectorId > 0 &&
740 connectorStatus?.transactionStarted === true &&
5199f9fd 741 csChargingProfiles.transactionId !== connectorStatus.transactionId
86f51b96
JB
742 ) {
743 logger.error(
944d4529
JB
744 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
745 csChargingProfiles.transactionId
5199f9fd 746 } than the started transaction id ${connectorStatus.transactionId}`
66a7748d
JB
747 )
748 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 749 }
66a7748d 750 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
e7aeea18 751 logger.debug(
0d1f33ba 752 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
66a7748d
JB
753 csChargingProfiles
754 )
755 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
756 }
757
66a7748d 758 private handleRequestGetCompositeSchedule (
41189456 759 chargingStation: ChargingStation,
66a7748d 760 commandPayload: OCPP16GetCompositeScheduleRequest
41189456
JB
761 ): OCPP16GetCompositeScheduleResponse {
762 if (
66a7748d 763 !OCPP16ServiceUtils.checkFeatureProfile(
41189456
JB
764 chargingStation,
765 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
766 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
767 )
41189456 768 ) {
66a7748d 769 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 770 }
66a7748d
JB
771 const { connectorId, duration, chargingRateUnit } = commandPayload
772 if (!chargingStation.hasConnector(connectorId)) {
41189456 773 logger.error(
66a7748d
JB
774 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
775 )
776 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 777 }
b3d7d654
JB
778 if (connectorId === 0) {
779 logger.error(
66a7748d
JB
780 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
781 )
782 return OCPP16Constants.OCPP_RESPONSE_REJECTED
b3d7d654 783 }
66a7748d 784 if (chargingRateUnit != null) {
bbb55ee4 785 logger.warn(
66a7748d
JB
786 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
787 )
b3d7d654 788 }
f938317f 789 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
ad490d5f 790 if (
f938317f 791 isEmptyArray(connectorStatus?.chargingProfiles) &&
a4385edc 792 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
ad490d5f 793 ) {
66a7748d 794 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 795 }
66a7748d 796 const currentDate = new Date()
ef9e3b33 797 const compositeScheduleInterval: Interval = {
ad490d5f 798 start: currentDate,
66a7748d
JB
799 end: addSeconds(currentDate, duration)
800 }
6fc0c6f3 801 // Get charging profiles sorted by connector id then stack level
ef9e3b33 802 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
6fc0c6f3 803 chargingStation,
66a7748d
JB
804 connectorId
805 )
806 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
807 let compositeSchedule: OCPP16ChargingSchedule | undefined
ef9e3b33 808 for (const chargingProfile of chargingProfiles) {
2466918c 809 if (chargingProfile.chargingSchedule.startSchedule == null) {
ad490d5f
JB
810 logger.debug(
811 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
ef9e3b33 812 chargingProfile.chargingProfileId
66a7748d
JB
813 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
814 )
ad490d5f 815 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
f938317f 816 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
ad490d5f 817 }
2466918c 818 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
ef9e3b33
JB
819 logger.warn(
820 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
821 chargingProfile.chargingProfileId
66a7748d
JB
822 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
823 )
ef9e3b33 824 chargingProfile.chargingSchedule.startSchedule = convertToDate(
5199f9fd 825 chargingProfile.chargingSchedule.startSchedule
3423c8a5 826 )
ef9e3b33 827 }
2466918c 828 if (chargingProfile.chargingSchedule.duration == null) {
da332e70 829 logger.debug(
ef9e3b33
JB
830 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
831 chargingProfile.chargingProfileId
66a7748d
JB
832 } has no duration defined and will be set to the maximum time allowed`
833 )
da332e70 834 // OCPP specifies that if duration is not defined, it should be infinite
ef9e3b33 835 chargingProfile.chargingSchedule.duration = differenceInSeconds(
da332e70 836 maxTime,
3423c8a5
JB
837 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
838 chargingProfile.chargingSchedule.startSchedule!
66a7748d 839 )
da332e70 840 }
0eb666db
JB
841 if (
842 !prepareChargingProfileKind(
843 connectorStatus,
ef9e3b33 844 chargingProfile,
6dde6c5f 845 compositeScheduleInterval.start,
66a7748d 846 chargingStation.logPrefix()
0eb666db
JB
847 )
848 ) {
66a7748d 849 continue
b3d7d654 850 }
41189456 851 if (
ad490d5f 852 !canProceedChargingProfile(
ef9e3b33 853 chargingProfile,
6dde6c5f 854 compositeScheduleInterval.start,
66a7748d 855 chargingStation.logPrefix()
b3d7d654 856 )
41189456 857 ) {
66a7748d 858 continue
ad490d5f 859 }
ef9e3b33
JB
860 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
861 previousCompositeSchedule,
862 chargingProfile.chargingSchedule,
66a7748d
JB
863 compositeScheduleInterval
864 )
865 previousCompositeSchedule = compositeSchedule
ef9e3b33 866 }
66a7748d 867 if (compositeSchedule != null) {
ef9e3b33
JB
868 return {
869 status: GenericStatus.Accepted,
0c1e4bc1 870 scheduleStart: compositeSchedule.startSchedule,
ef9e3b33 871 connectorId,
66a7748d
JB
872 chargingSchedule: compositeSchedule
873 }
ef9e3b33 874 }
66a7748d 875 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456
JB
876 }
877
66a7748d 878 private handleRequestClearChargingProfile (
08f130a0 879 chargingStation: ChargingStation,
66a7748d 880 commandPayload: OCPP16ClearChargingProfileRequest
41f3983a 881 ): OCPP16ClearChargingProfileResponse {
370ae4ee 882 if (
66a7748d 883 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 884 chargingStation,
370ae4ee 885 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
886 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
887 )
370ae4ee 888 ) {
66a7748d 889 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
68cb8b91 890 }
66a7748d
JB
891 const { connectorId } = commandPayload
892 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
893 if (!chargingStation.hasConnector(connectorId!)) {
e7aeea18 894 logger.error(
66a7748d
JB
895 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
896 )
897 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973 898 }
66a7748d
JB
899 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
900 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
be9f397b 901 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
5dc7c990 902 connectorStatus.chargingProfiles = []
e7aeea18 903 logger.debug(
66a7748d
JB
904 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
905 )
906 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973 907 }
be9f397b 908 if (connectorId == null) {
66a7748d 909 let clearedCP = false
4334db72
JB
910 if (chargingStation.hasEvses) {
911 for (const evseStatus of chargingStation.evses.values()) {
f406808f 912 for (const status of evseStatus.connectors.values()) {
73d87be1
JB
913 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
914 chargingStation,
915 commandPayload,
66a7748d
JB
916 status.chargingProfiles
917 )
4334db72
JB
918 }
919 }
920 } else {
0d1f33ba 921 for (const id of chargingStation.connectors.keys()) {
73d87be1
JB
922 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
923 chargingStation,
924 commandPayload,
66a7748d
JB
925 chargingStation.getConnectorStatus(id)?.chargingProfiles
926 )
c0560973
JB
927 }
928 }
929 if (clearedCP) {
66a7748d 930 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
931 }
932 }
66a7748d 933 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973
JB
934 }
935
66a7748d 936 private async handleRequestChangeAvailability (
08f130a0 937 chargingStation: ChargingStation,
66a7748d 938 commandPayload: OCPP16ChangeAvailabilityRequest
366f75f6 939 ): Promise<OCPP16ChangeAvailabilityResponse> {
66a7748d
JB
940 const { connectorId, type } = commandPayload
941 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 942 logger.error(
66a7748d
JB
943 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
944 )
945 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973 946 }
e7aeea18 947 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 948 type === OCPP16AvailabilityType.Operative
721646e9 949 ? OCPP16ChargePointStatus.Available
66a7748d 950 : OCPP16ChargePointStatus.Unavailable
c0560973 951 if (connectorId === 0) {
f938317f 952 let response: OCPP16ChangeAvailabilityResponse | undefined
ded57f02
JB
953 if (chargingStation.hasEvses) {
954 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
955 response = await OCPP16ServiceUtils.changeAvailability(
956 chargingStation,
225e32b0 957 [...evseStatus.connectors.keys()],
366f75f6 958 chargePointStatus,
66a7748d
JB
959 type
960 )
ded57f02 961 }
225e32b0
JB
962 } else {
963 response = await OCPP16ServiceUtils.changeAvailability(
964 chargingStation,
965 [...chargingStation.connectors.keys()],
966 chargePointStatus,
66a7748d
JB
967 type
968 )
c0560973 969 }
66a7748d
JB
970 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
971 return response!
e7aeea18
JB
972 } else if (
973 connectorId > 0 &&
66a7748d
JB
974 (chargingStation.isChargingStationAvailable() ||
975 (!chargingStation.isChargingStationAvailable() &&
0d1f33ba 976 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 977 ) {
5e3cb728 978 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
66a7748d
JB
979 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
980 chargingStation.getConnectorStatus(connectorId)!.availability = type
981 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
c0560973 982 }
66a7748d
JB
983 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
984 chargingStation.getConnectorStatus(connectorId)!.availability = type
4ecff7ce
JB
985 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
986 chargingStation,
ef6fa3fb 987 connectorId,
66a7748d
JB
988 chargePointStatus
989 )
990 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
c0560973 991 }
66a7748d 992 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973
JB
993 }
994
66a7748d 995 private async handleRequestRemoteStartTransaction (
08f130a0 996 chargingStation: ChargingStation,
66a7748d 997 commandPayload: RemoteStartTransactionRequest
f03e1042 998 ): Promise<GenericResponse> {
66a7748d
JB
999 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1000 if (!chargingStation.hasConnector(transactionConnectorId)) {
1001 return await this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
1002 chargingStation,
1003 transactionConnectorId,
66a7748d
JB
1004 idTag
1005 )
649287f8
JB
1006 }
1007 if (
d193a949
JB
1008 !chargingStation.isChargingStationAvailable() ||
1009 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8 1010 ) {
66a7748d 1011 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1012 chargingStation,
1013 transactionConnectorId,
66a7748d
JB
1014 idTag
1015 )
649287f8 1016 }
b10202d7 1017 // idTag authorization check required
d984c13f 1018 if (
66a7748d 1019 chargingStation.getAuthorizeRemoteTxRequests() &&
b10202d7 1020 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
66dd3447 1021 ) {
66a7748d 1022 return await this.notifyRemoteStartTransactionRejected(
08f130a0 1023 chargingStation,
e7aeea18 1024 transactionConnectorId,
66a7748d
JB
1025 idTag
1026 )
c0560973 1027 }
b10202d7
JB
1028 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1029 chargingStation,
1030 transactionConnectorId,
1031 OCPP16ChargePointStatus.Preparing
1032 )
649287f8 1033 if (
b10202d7
JB
1034 chargingProfile != null &&
1035 !this.setRemoteStartTransactionChargingProfile(
1036 chargingStation,
1037 transactionConnectorId,
1038 chargingProfile
1039 )
649287f8 1040 ) {
66a7748d 1041 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1042 chargingStation,
1043 transactionConnectorId,
66a7748d
JB
1044 idTag
1045 )
649287f8 1046 }
54510a60
JB
1047 this.emit(
1048 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
08f130a0
JB
1049 chargingStation,
1050 transactionConnectorId,
66a7748d
JB
1051 idTag
1052 )
54510a60 1053 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
a7fc8211
JB
1054 }
1055
66a7748d 1056 private async notifyRemoteStartTransactionRejected (
08f130a0 1057 chargingStation: ChargingStation,
e7aeea18 1058 connectorId: number,
66a7748d 1059 idTag: string
f03e1042 1060 ): Promise<GenericResponse> {
66a7748d 1061 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f406808f 1062 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
1063 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1064 chargingStation,
ef6fa3fb 1065 connectorId,
66a7748d
JB
1066 OCPP16ChargePointStatus.Available
1067 )
e060fe58 1068 }
e7aeea18 1069 logger.warn(
54510a60 1070 `${chargingStation.logPrefix()} Remote start transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
66a7748d
JB
1071 )
1072 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1073 }
1074
66a7748d 1075 private setRemoteStartTransactionChargingProfile (
08f130a0 1076 chargingStation: ChargingStation,
e7aeea18 1077 connectorId: number,
66a7748d 1078 chargingProfile: OCPP16ChargingProfile
e7aeea18 1079 ): boolean {
5199f9fd 1080 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1081 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1082 logger.debug(
944d4529 1083 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
66a7748d
JB
1084 chargingProfile
1085 )
1086 return true
a7fc8211 1087 }
f406808f
JB
1088 logger.warn(
1089 `${chargingStation.logPrefix()} Not allowed to set ${
1090 chargingProfile.chargingProfilePurpose
66a7748d
JB
1091 } charging profile(s) at remote start transaction`
1092 )
1093 return false
a7fc8211
JB
1094 }
1095
66a7748d 1096 private async handleRequestRemoteStopTransaction (
08f130a0 1097 chargingStation: ChargingStation,
66a7748d 1098 commandPayload: RemoteStopTransactionRequest
f03e1042 1099 ): Promise<GenericResponse> {
66a7748d 1100 const { transactionId } = commandPayload
ded57f02
JB
1101 if (chargingStation.hasEvses) {
1102 for (const [evseId, evseStatus] of chargingStation.evses) {
1103 if (evseId > 0) {
1104 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1105 if (connectorStatus.transactionId === transactionId) {
66a7748d 1106 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ded57f02
JB
1107 }
1108 }
1109 }
1110 }
1111 } else {
1112 for (const connectorId of chargingStation.connectors.keys()) {
1113 if (
1114 connectorId > 0 &&
1115 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1116 ) {
66a7748d 1117 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ef6fa3fb 1118 }
c0560973
JB
1119 }
1120 }
44b9b577 1121 logger.warn(
66a7748d
JB
1122 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1123 )
1124 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1125 }
47e22477 1126
66a7748d 1127 private handleRequestUpdateFirmware (
b03df580 1128 chargingStation: ChargingStation,
66a7748d 1129 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1130 ): OCPP16UpdateFirmwareResponse {
1131 if (
66a7748d 1132 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1133 chargingStation,
1134 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1135 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1136 )
b03df580 1137 ) {
5d280aae 1138 logger.warn(
66a7748d
JB
1139 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1140 )
1141 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1142 }
95dab6cf
JB
1143 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1144 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1145 const { retrieveDate } = commandPayload
5199f9fd 1146 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
5d280aae 1147 logger.warn(
66a7748d
JB
1148 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1149 )
1150 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1151 }
66a7748d 1152 const now = Date.now()
5199f9fd 1153 if (retrieveDate.getTime() <= now) {
66a7748d 1154 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1155 } else {
5199f9fd
JB
1156 setTimeout(() => {
1157 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1158 }, retrieveDate.getTime() - now)
c9a4f9ea 1159 }
66a7748d 1160 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1161 }
1162
66a7748d 1163 private async updateFirmwareSimulation (
c9a4f9ea 1164 chargingStation: ChargingStation,
90293abb 1165 maxDelay = 30,
66a7748d 1166 minDelay = 15
c9a4f9ea 1167 ): Promise<void> {
66a7748d
JB
1168 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1169 return
1bf29f5b 1170 }
ded57f02
JB
1171 if (chargingStation.hasEvses) {
1172 for (const [evseId, evseStatus] of chargingStation.evses) {
1173 if (evseId > 0) {
1174 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1175 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1176 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1177 chargingStation,
1178 connectorId,
66a7748d
JB
1179 OCPP16ChargePointStatus.Unavailable
1180 )
ded57f02
JB
1181 }
1182 }
1183 }
1184 }
1185 } else {
1186 for (const connectorId of chargingStation.connectors.keys()) {
1187 if (
1188 connectorId > 0 &&
1189 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1190 ) {
1191 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1192 chargingStation,
1193 connectorId,
66a7748d
JB
1194 OCPP16ChargePointStatus.Unavailable
1195 )
ded57f02 1196 }
c9a4f9ea
JB
1197 }
1198 }
93f0c2c8 1199 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1200 OCPP16FirmwareStatusNotificationRequest,
1201 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1202 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1203 status: OCPP16FirmwareStatus.Downloading
1204 })
5199f9fd
JB
1205 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1206 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1207 if (
93f0c2c8
JB
1208 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1209 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1210 ) {
66a7748d 1211 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
5d280aae 1212 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1213 OCPP16FirmwareStatusNotificationRequest,
1214 OCPP16FirmwareStatusNotificationResponse
5d280aae 1215 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1216 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1217 })
93f0c2c8 1218 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1219 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1220 return
5d280aae 1221 }
66a7748d 1222 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
c9a4f9ea 1223 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1224 OCPP16FirmwareStatusNotificationRequest,
1225 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1226 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1227 status: OCPP16FirmwareStatus.Downloaded
1228 })
5199f9fd
JB
1229 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1230 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1231 let wasTransactionsStarted = false
1232 let transactionsStarted: boolean
62340a29 1233 do {
66a7748d 1234 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1235 if (runningTransactions > 0) {
66a7748d 1236 const waitTime = secondsToMilliseconds(15)
62340a29 1237 logger.debug(
944d4529 1238 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1239 waitTime
1240 )} before continuing firmware update simulation`
1241 )
1242 await sleep(waitTime)
1243 transactionsStarted = true
1244 wasTransactionsStarted = true
62340a29 1245 } else {
ded57f02
JB
1246 if (chargingStation.hasEvses) {
1247 for (const [evseId, evseStatus] of chargingStation.evses) {
1248 if (evseId > 0) {
1249 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1250 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1251 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1252 chargingStation,
1253 connectorId,
66a7748d
JB
1254 OCPP16ChargePointStatus.Unavailable
1255 )
ded57f02
JB
1256 }
1257 }
1258 }
1259 }
1260 } else {
1261 for (const connectorId of chargingStation.connectors.keys()) {
1262 if (
1263 connectorId > 0 &&
1264 chargingStation.getConnectorStatus(connectorId)?.status !==
1265 OCPP16ChargePointStatus.Unavailable
1266 ) {
1267 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1268 chargingStation,
1269 connectorId,
66a7748d
JB
1270 OCPP16ChargePointStatus.Unavailable
1271 )
ded57f02 1272 }
62340a29
JB
1273 }
1274 }
66a7748d 1275 transactionsStarted = false
62340a29 1276 }
66a7748d 1277 } while (transactionsStarted)
be4c6702 1278 !wasTransactionsStarted &&
66a7748d
JB
1279 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1280 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1281 return
1bf29f5b 1282 }
c9a4f9ea 1283 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1284 OCPP16FirmwareStatusNotificationRequest,
1285 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1286 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1287 status: OCPP16FirmwareStatus.Installing
1288 })
5199f9fd
JB
1289 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1290 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1291 if (
1292 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1293 OCPP16FirmwareStatus.InstallationFailed
1294 ) {
66a7748d 1295 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
93f0c2c8 1296 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1297 OCPP16FirmwareStatusNotificationRequest,
1298 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1299 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1300 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1301 })
93f0c2c8 1302 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1303 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1304 return
93f0c2c8 1305 }
15748260 1306 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
66a7748d
JB
1307 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1308 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1309 }
b03df580
JB
1310 }
1311
66a7748d 1312 private async handleRequestGetDiagnostics (
08f130a0 1313 chargingStation: ChargingStation,
66a7748d 1314 commandPayload: GetDiagnosticsRequest
e7aeea18 1315 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1316 if (
66a7748d 1317 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1318 chargingStation,
370ae4ee 1319 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1320 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1321 )
68cb8b91 1322 ) {
90293abb 1323 logger.warn(
66a7748d
JB
1324 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1325 )
1326 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1327 }
66a7748d
JB
1328 const { location } = commandPayload
1329 const uri = new URL(location)
47e22477 1330 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1331 let ftpClient: Client | undefined
47e22477 1332 try {
d972af76 1333 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
a974c8e4
JB
1334 .filter(file => file.endsWith('.log'))
1335 .map(file => join('./', file))
5199f9fd 1336 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1337 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1338 ftpClient = new Client()
47e22477
JB
1339 const accessResponse = await ftpClient.access({
1340 host: uri.host,
9bf0ef23
JB
1341 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1342 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1343 ...(isNotEmptyString(uri.password) && { password: uri.password })
1344 })
1345 let uploadResponse: FTPResponse | undefined
47e22477 1346 if (accessResponse.code === 220) {
a974c8e4 1347 ftpClient.trackProgress(info => {
e7aeea18 1348 logger.info(
56563a3c 1349 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1350 info.bytes / 1024
66a7748d
JB
1351 } bytes transferred from diagnostics archive ${info.name}`
1352 )
6a8329b4
JB
1353 chargingStation.ocppRequestService
1354 .requestHandler<
66a7748d
JB
1355 OCPP16DiagnosticsStatusNotificationRequest,
1356 OCPP16DiagnosticsStatusNotificationResponse
1357 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1358 status: OCPP16DiagnosticsStatus.Uploading
1359 })
a974c8e4 1360 .catch(error => {
6a8329b4 1361 logger.error(
944d4529
JB
1362 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1363 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1364 }'`,
66a7748d
JB
1365 error
1366 )
1367 })
1368 })
e7aeea18 1369 uploadResponse = await ftpClient.uploadFrom(
d972af76 1370 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1371 `${uri.pathname}${diagnosticsArchive}`
1372 )
47e22477 1373 if (uploadResponse.code === 226) {
08f130a0 1374 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1375 OCPP16DiagnosticsStatusNotificationRequest,
1376 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1377 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1378 status: OCPP16DiagnosticsStatus.Uploaded
1379 })
5199f9fd 1380 ftpClient.close()
66a7748d 1381 return { fileName: diagnosticsArchive }
47e22477 1382 }
e7aeea18
JB
1383 throw new OCPPError(
1384 ErrorType.GENERIC_ERROR,
5199f9fd 1385 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1386 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1387 )
47e22477 1388 }
e7aeea18
JB
1389 throw new OCPPError(
1390 ErrorType.GENERIC_ERROR,
5199f9fd 1391 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1392 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1393 )
47e22477 1394 } catch (error) {
08f130a0 1395 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1396 OCPP16DiagnosticsStatusNotificationRequest,
1397 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1398 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1399 status: OCPP16DiagnosticsStatus.UploadFailed
1400 })
5199f9fd 1401 ftpClient?.close()
66a7748d 1402 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1403 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1404 chargingStation,
e7aeea18
JB
1405 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1406 error as Error,
66a7748d
JB
1407 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1408 )!
47e22477
JB
1409 }
1410 } else {
e7aeea18 1411 logger.error(
08f130a0 1412 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1413 uri.protocol
66a7748d
JB
1414 } to transfer the diagnostic logs archive`
1415 )
08f130a0 1416 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1417 OCPP16DiagnosticsStatusNotificationRequest,
1418 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1419 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1420 status: OCPP16DiagnosticsStatus.UploadFailed
1421 })
1422 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1423 }
1424 }
802cfa13 1425
66a7748d 1426 private handleRequestTriggerMessage (
08f130a0 1427 chargingStation: ChargingStation,
66a7748d 1428 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1429 ): OCPP16TriggerMessageResponse {
66a7748d 1430 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1431 if (
1432 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1433 chargingStation,
370ae4ee 1434 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1435 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1436 ) ||
0d1f33ba 1437 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1438 ) {
66a7748d 1439 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1440 }
c60ed4b8 1441 if (
4caa7e67 1442 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1443 chargingStation,
1444 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1445 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1446 connectorId!
c60ed4b8
JB
1447 )
1448 ) {
66a7748d 1449 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1450 }
802cfa13 1451 try {
0d1f33ba 1452 switch (requestedMessage) {
c60ed4b8 1453 case OCPP16MessageTrigger.BootNotification:
802cfa13 1454 setTimeout(() => {
08f130a0 1455 chargingStation.ocppRequestService
f7f98c68 1456 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
66a7748d
JB
1457 chargingStation,
1458 OCPP16RequestCommand.BOOT_NOTIFICATION,
1459 chargingStation.bootNotificationRequest,
1460 { skipBufferingOnError: true, triggerMessage: true }
1461 )
a974c8e4 1462 .then(response => {
66a7748d 1463 chargingStation.bootNotificationResponse = response
ae711c83 1464 })
66a7748d
JB
1465 .catch(Constants.EMPTY_FUNCTION)
1466 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1467 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1468 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1469 setTimeout(() => {
08f130a0 1470 chargingStation.ocppRequestService
f7f98c68 1471 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
66a7748d
JB
1472 chargingStation,
1473 OCPP16RequestCommand.HEARTBEAT,
1474 undefined,
1475 {
1476 triggerMessage: true
1477 }
1478 )
1479 .catch(Constants.EMPTY_FUNCTION)
1480 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1481 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1482 case OCPP16MessageTrigger.StatusNotification:
dc661702 1483 setTimeout(() => {
be9f397b 1484 if (connectorId != null) {
08f130a0 1485 chargingStation.ocppRequestService
dc661702 1486 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
66a7748d
JB
1487 chargingStation,
1488 OCPP16RequestCommand.STATUS_NOTIFICATION,
1489 {
1490 connectorId,
1491 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
be9f397b 1492 status: chargingStation.getConnectorStatus(connectorId)?.status
66a7748d
JB
1493 },
1494 {
1495 triggerMessage: true
1496 }
1497 )
1498 .catch(Constants.EMPTY_FUNCTION)
f938317f
JB
1499 } else if (chargingStation.hasEvses) {
1500 for (const evseStatus of chargingStation.evses.values()) {
1501 for (const [id, connectorStatus] of evseStatus.connectors) {
66a7748d
JB
1502 chargingStation.ocppRequestService
1503 .requestHandler<
1504 OCPP16StatusNotificationRequest,
1505 OCPP16StatusNotificationResponse
1506 >(
1507 chargingStation,
1508 OCPP16RequestCommand.STATUS_NOTIFICATION,
1509 {
1510 connectorId: id,
1511 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
f938317f 1512 status: connectorStatus.status
66a7748d
JB
1513 },
1514 {
1515 triggerMessage: true
1516 }
1517 )
1518 .catch(Constants.EMPTY_FUNCTION)
ded57f02 1519 }
dc661702 1520 }
f938317f
JB
1521 } else {
1522 for (const [id, connectorStatus] of chargingStation.connectors) {
1523 chargingStation.ocppRequestService
1524 .requestHandler<
1525 OCPP16StatusNotificationRequest,
1526 OCPP16StatusNotificationResponse
1527 >(
1528 chargingStation,
1529 OCPP16RequestCommand.STATUS_NOTIFICATION,
1530 {
1531 connectorId: id,
1532 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1533 status: connectorStatus.status
1534 },
1535 {
1536 triggerMessage: true
1537 }
1538 )
1539 .catch(Constants.EMPTY_FUNCTION)
1540 }
dc661702 1541 }
66a7748d
JB
1542 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1543 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
802cfa13 1544 default:
66a7748d 1545 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1546 }
1547 } catch (error) {
66a7748d 1548 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1549 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1550 chargingStation,
e7aeea18
JB
1551 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1552 error as Error,
66a7748d
JB
1553 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1554 )!
802cfa13
JB
1555 }
1556 }
77b95a89 1557
66a7748d 1558 private handleRequestDataTransfer (
77b95a89 1559 chargingStation: ChargingStation,
66a7748d 1560 commandPayload: OCPP16DataTransferRequest
77b95a89 1561 ): OCPP16DataTransferResponse {
66a7748d 1562 const { vendorId } = commandPayload
77b95a89 1563 try {
0d1f33ba 1564 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1565 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1566 }
66a7748d 1567 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1568 } catch (error) {
66a7748d 1569 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1570 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1571 chargingStation,
1572 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1573 error as Error,
66a7748d
JB
1574 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1575 )!
77b95a89
JB
1576 }
1577 }
24578c31 1578
66a7748d 1579 private async handleRequestReserveNow (
24578c31 1580 chargingStation: ChargingStation,
66a7748d 1581 commandPayload: OCPP16ReserveNowRequest
24578c31 1582 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1583 if (
1584 !OCPP16ServiceUtils.checkFeatureProfile(
1585 chargingStation,
1586 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1587 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1588 )
1589 ) {
66a7748d 1590 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1591 }
95dab6cf
JB
1592 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1593 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
66a7748d
JB
1594 const { reservationId, idTag, connectorId } = commandPayload
1595 let response: OCPP16ReserveNowResponse
24578c31 1596 try {
d984c13f 1597 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1598 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1599 }
10e8c3e1 1600 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1601 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1602 }
66dd3447 1603 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1604 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1605 }
66a7748d 1606 await removeExpiredReservations(chargingStation)
f938317f 1607 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1608 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1609 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1610 break
178956d8
JB
1611 case OCPP16ChargePointStatus.Preparing:
1612 case OCPP16ChargePointStatus.Charging:
1613 case OCPP16ChargePointStatus.SuspendedEV:
1614 case OCPP16ChargePointStatus.SuspendedEVSE:
1615 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1616 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1617 break
178956d8 1618 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1619 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1620 break
178956d8 1621 case OCPP16ChargePointStatus.Reserved:
66dd3447 1622 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1623 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1624 break
24578c31
JB
1625 }
1626 // eslint-disable-next-line no-fallthrough
1627 default:
66dd3447 1628 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1629 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1630 break
d193a949
JB
1631 }
1632 await chargingStation.addReservation({
1633 id: commandPayload.reservationId,
66a7748d
JB
1634 ...commandPayload
1635 })
1636 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1637 break
24578c31 1638 }
66a7748d 1639 return response
24578c31 1640 } catch (error) {
66a7748d
JB
1641 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1642 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1643 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1644 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1645 chargingStation,
1646 OCPP16IncomingRequestCommand.RESERVE_NOW,
1647 error as Error,
66a7748d
JB
1648 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1649 )!
24578c31
JB
1650 }
1651 }
1652
66a7748d 1653 private async handleRequestCancelReservation (
24578c31 1654 chargingStation: ChargingStation,
66a7748d 1655 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1656 ): Promise<GenericResponse> {
66dd3447
JB
1657 if (
1658 !OCPP16ServiceUtils.checkFeatureProfile(
1659 chargingStation,
1660 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1661 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1662 )
1663 ) {
66a7748d 1664 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1665 }
24578c31 1666 try {
66a7748d
JB
1667 const { reservationId } = commandPayload
1668 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1669 if (reservation == null) {
90aceaf6 1670 logger.debug(
66a7748d
JB
1671 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1672 )
1673 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1674 }
ec9f36cc 1675 await chargingStation.removeReservation(
300418e9 1676 reservation,
66a7748d
JB
1677 ReservationTerminationReason.RESERVATION_CANCELED
1678 )
1679 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1680 } catch (error) {
66a7748d 1681 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1682 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1683 chargingStation,
1684 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1685 error as Error,
66a7748d
JB
1686 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1687 )!
24578c31
JB
1688 }
1689 }
c0560973 1690}