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