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