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