refactor: cleanup nullish values handling
[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 }
f938317f 694 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
ad490d5f 695 if (
f938317f 696 isEmptyArray(connectorStatus?.chargingProfiles) &&
a4385edc 697 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
ad490d5f 698 ) {
66a7748d 699 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 700 }
66a7748d 701 const currentDate = new Date()
ef9e3b33 702 const compositeScheduleInterval: Interval = {
ad490d5f 703 start: currentDate,
66a7748d
JB
704 end: addSeconds(currentDate, duration)
705 }
6fc0c6f3 706 // Get charging profiles sorted by connector id then stack level
ef9e3b33 707 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
6fc0c6f3 708 chargingStation,
66a7748d
JB
709 connectorId
710 )
711 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
712 let compositeSchedule: OCPP16ChargingSchedule | undefined
ef9e3b33 713 for (const chargingProfile of chargingProfiles) {
2466918c 714 if (chargingProfile.chargingSchedule.startSchedule == null) {
ad490d5f
JB
715 logger.debug(
716 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
ef9e3b33 717 chargingProfile.chargingProfileId
66a7748d
JB
718 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
719 )
ad490d5f 720 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
f938317f 721 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
ad490d5f 722 }
2466918c 723 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
ef9e3b33
JB
724 logger.warn(
725 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
726 chargingProfile.chargingProfileId
66a7748d
JB
727 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
728 )
729 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33 730 chargingProfile.chargingSchedule.startSchedule = convertToDate(
5199f9fd 731 chargingProfile.chargingSchedule.startSchedule
66a7748d 732 )!
ef9e3b33 733 }
2466918c 734 if (chargingProfile.chargingSchedule.duration == null) {
da332e70 735 logger.debug(
ef9e3b33
JB
736 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
737 chargingProfile.chargingProfileId
66a7748d
JB
738 } has no duration defined and will be set to the maximum time allowed`
739 )
da332e70 740 // OCPP specifies that if duration is not defined, it should be infinite
ef9e3b33 741 chargingProfile.chargingSchedule.duration = differenceInSeconds(
da332e70 742 maxTime,
be9f397b 743 chargingProfile.chargingSchedule.startSchedule
66a7748d 744 )
da332e70 745 }
0eb666db
JB
746 if (
747 !prepareChargingProfileKind(
748 connectorStatus,
ef9e3b33 749 chargingProfile,
6dde6c5f 750 compositeScheduleInterval.start,
66a7748d 751 chargingStation.logPrefix()
0eb666db
JB
752 )
753 ) {
66a7748d 754 continue
b3d7d654 755 }
41189456 756 if (
ad490d5f 757 !canProceedChargingProfile(
ef9e3b33 758 chargingProfile,
6dde6c5f 759 compositeScheduleInterval.start,
66a7748d 760 chargingStation.logPrefix()
b3d7d654 761 )
41189456 762 ) {
66a7748d 763 continue
ad490d5f 764 }
ef9e3b33
JB
765 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
766 previousCompositeSchedule,
767 chargingProfile.chargingSchedule,
66a7748d
JB
768 compositeScheduleInterval
769 )
770 previousCompositeSchedule = compositeSchedule
ef9e3b33 771 }
66a7748d 772 if (compositeSchedule != null) {
ef9e3b33
JB
773 return {
774 status: GenericStatus.Accepted,
66a7748d 775 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33
JB
776 scheduleStart: compositeSchedule.startSchedule!,
777 connectorId,
66a7748d
JB
778 chargingSchedule: compositeSchedule
779 }
ef9e3b33 780 }
66a7748d 781 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456
JB
782 }
783
66a7748d 784 private handleRequestClearChargingProfile (
08f130a0 785 chargingStation: ChargingStation,
66a7748d 786 commandPayload: OCPP16ClearChargingProfileRequest
41f3983a 787 ): OCPP16ClearChargingProfileResponse {
370ae4ee 788 if (
66a7748d 789 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 790 chargingStation,
370ae4ee 791 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
792 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
793 )
370ae4ee 794 ) {
66a7748d 795 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
68cb8b91 796 }
66a7748d
JB
797 const { connectorId } = commandPayload
798 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
799 if (!chargingStation.hasConnector(connectorId!)) {
e7aeea18 800 logger.error(
66a7748d
JB
801 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
802 )
803 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973 804 }
66a7748d
JB
805 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
806 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
be9f397b 807 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
66a7748d
JB
808 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
809 connectorStatus!.chargingProfiles = []
e7aeea18 810 logger.debug(
66a7748d
JB
811 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
812 )
813 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973 814 }
be9f397b 815 if (connectorId == null) {
66a7748d 816 let clearedCP = false
4334db72
JB
817 if (chargingStation.hasEvses) {
818 for (const evseStatus of chargingStation.evses.values()) {
f406808f 819 for (const status of evseStatus.connectors.values()) {
73d87be1
JB
820 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
821 chargingStation,
822 commandPayload,
66a7748d
JB
823 status.chargingProfiles
824 )
4334db72
JB
825 }
826 }
827 } else {
0d1f33ba 828 for (const id of chargingStation.connectors.keys()) {
73d87be1
JB
829 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
830 chargingStation,
831 commandPayload,
66a7748d
JB
832 chargingStation.getConnectorStatus(id)?.chargingProfiles
833 )
c0560973
JB
834 }
835 }
836 if (clearedCP) {
66a7748d 837 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
838 }
839 }
66a7748d 840 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973
JB
841 }
842
66a7748d 843 private async handleRequestChangeAvailability (
08f130a0 844 chargingStation: ChargingStation,
66a7748d 845 commandPayload: OCPP16ChangeAvailabilityRequest
366f75f6 846 ): Promise<OCPP16ChangeAvailabilityResponse> {
66a7748d
JB
847 const { connectorId, type } = commandPayload
848 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 849 logger.error(
66a7748d
JB
850 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
851 )
852 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973 853 }
e7aeea18 854 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 855 type === OCPP16AvailabilityType.Operative
721646e9 856 ? OCPP16ChargePointStatus.Available
66a7748d 857 : OCPP16ChargePointStatus.Unavailable
c0560973 858 if (connectorId === 0) {
f938317f 859 let response: OCPP16ChangeAvailabilityResponse | undefined
ded57f02
JB
860 if (chargingStation.hasEvses) {
861 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
862 response = await OCPP16ServiceUtils.changeAvailability(
863 chargingStation,
225e32b0 864 [...evseStatus.connectors.keys()],
366f75f6 865 chargePointStatus,
66a7748d
JB
866 type
867 )
ded57f02 868 }
225e32b0
JB
869 } else {
870 response = await OCPP16ServiceUtils.changeAvailability(
871 chargingStation,
872 [...chargingStation.connectors.keys()],
873 chargePointStatus,
66a7748d
JB
874 type
875 )
c0560973 876 }
66a7748d
JB
877 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
878 return response!
e7aeea18
JB
879 } else if (
880 connectorId > 0 &&
66a7748d
JB
881 (chargingStation.isChargingStationAvailable() ||
882 (!chargingStation.isChargingStationAvailable() &&
0d1f33ba 883 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 884 ) {
5e3cb728 885 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
66a7748d
JB
886 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
887 chargingStation.getConnectorStatus(connectorId)!.availability = type
888 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
c0560973 889 }
66a7748d
JB
890 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
891 chargingStation.getConnectorStatus(connectorId)!.availability = type
4ecff7ce
JB
892 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
893 chargingStation,
ef6fa3fb 894 connectorId,
66a7748d
JB
895 chargePointStatus
896 )
897 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
c0560973 898 }
66a7748d 899 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973
JB
900 }
901
66a7748d 902 private async handleRequestRemoteStartTransaction (
08f130a0 903 chargingStation: ChargingStation,
66a7748d 904 commandPayload: RemoteStartTransactionRequest
f03e1042 905 ): Promise<GenericResponse> {
66a7748d
JB
906 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
907 if (!chargingStation.hasConnector(transactionConnectorId)) {
908 return await this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
909 chargingStation,
910 transactionConnectorId,
66a7748d
JB
911 idTag
912 )
649287f8
JB
913 }
914 if (
d193a949
JB
915 !chargingStation.isChargingStationAvailable() ||
916 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8 917 ) {
66a7748d 918 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
919 chargingStation,
920 transactionConnectorId,
66a7748d
JB
921 idTag
922 )
649287f8 923 }
66dd3447 924 const remoteStartTransactionLogMsg = `
5199f9fd
JB
925 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${chargingStation.stationInfo
926 ?.chargingStationId}#${transactionConnectorId} for idTag '${idTag}'`
649287f8
JB
927 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
928 chargingStation,
929 transactionConnectorId,
66a7748d
JB
930 OCPP16ChargePointStatus.Preparing
931 )
932 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
933 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!
d984c13f
JB
934 // Authorization check required
935 if (
66a7748d 936 chargingStation.getAuthorizeRemoteTxRequests() &&
66dd3447
JB
937 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
938 ) {
939 // Authorization successful, start transaction
940 if (
66a7748d 941 (chargingProfile != null &&
8e3437b1
JB
942 this.setRemoteStartTransactionChargingProfile(
943 chargingStation,
944 transactionConnectorId,
66a7748d
JB
945 chargingProfile
946 )) ||
947 chargingProfile == null
66dd3447 948 ) {
66a7748d 949 connectorStatus.transactionRemoteStarted = true
e7aeea18 950 if (
66dd3447
JB
951 (
952 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
953 OCPP16StartTransactionRequest,
954 OCPP16StartTransactionResponse
d984c13f
JB
955 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
956 connectorId: transactionConnectorId,
66a7748d 957 idTag
d984c13f 958 })
66dd3447 959 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 960 ) {
66a7748d
JB
961 logger.debug(remoteStartTransactionLogMsg)
962 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
e060fe58 963 }
66a7748d 964 return await this.notifyRemoteStartTransactionRejected(
08f130a0 965 chargingStation,
e7aeea18 966 transactionConnectorId,
66a7748d
JB
967 idTag
968 )
c0560973 969 }
66a7748d 970 return await this.notifyRemoteStartTransactionRejected(
08f130a0 971 chargingStation,
e7aeea18 972 transactionConnectorId,
66a7748d
JB
973 idTag
974 )
c0560973 975 }
649287f8
JB
976 // No authorization check required, start transaction
977 if (
66a7748d 978 (chargingProfile != null &&
8e3437b1
JB
979 this.setRemoteStartTransactionChargingProfile(
980 chargingStation,
981 transactionConnectorId,
66a7748d
JB
982 chargingProfile
983 )) ||
984 chargingProfile == null
649287f8 985 ) {
66a7748d 986 connectorStatus.transactionRemoteStarted = true
649287f8
JB
987 if (
988 (
989 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
990 OCPP16StartTransactionRequest,
991 OCPP16StartTransactionResponse
649287f8
JB
992 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
993 connectorId: transactionConnectorId,
66a7748d 994 idTag
649287f8
JB
995 })
996 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
997 ) {
66a7748d
JB
998 logger.debug(remoteStartTransactionLogMsg)
999 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
649287f8 1000 }
66a7748d 1001 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1002 chargingStation,
1003 transactionConnectorId,
66a7748d
JB
1004 idTag
1005 )
649287f8 1006 }
66a7748d 1007 return await this.notifyRemoteStartTransactionRejected(
08f130a0
JB
1008 chargingStation,
1009 transactionConnectorId,
66a7748d
JB
1010 idTag
1011 )
a7fc8211
JB
1012 }
1013
66a7748d 1014 private async notifyRemoteStartTransactionRejected (
08f130a0 1015 chargingStation: ChargingStation,
e7aeea18 1016 connectorId: number,
66a7748d 1017 idTag: string
f03e1042 1018 ): Promise<GenericResponse> {
66a7748d 1019 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f406808f 1020 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
1021 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1022 chargingStation,
ef6fa3fb 1023 connectorId,
66a7748d
JB
1024 OCPP16ChargePointStatus.Available
1025 )
e060fe58 1026 }
e7aeea18 1027 logger.warn(
66a7748d
JB
1028 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1029 )
1030 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1031 }
1032
66a7748d 1033 private setRemoteStartTransactionChargingProfile (
08f130a0 1034 chargingStation: ChargingStation,
e7aeea18 1035 connectorId: number,
66a7748d 1036 chargingProfile: OCPP16ChargingProfile
e7aeea18 1037 ): boolean {
5199f9fd 1038 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1039 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1040 logger.debug(
944d4529 1041 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
66a7748d
JB
1042 chargingProfile
1043 )
1044 return true
a7fc8211 1045 }
f406808f
JB
1046 logger.warn(
1047 `${chargingStation.logPrefix()} Not allowed to set ${
1048 chargingProfile.chargingProfilePurpose
66a7748d
JB
1049 } charging profile(s) at remote start transaction`
1050 )
1051 return false
a7fc8211
JB
1052 }
1053
66a7748d 1054 private async handleRequestRemoteStopTransaction (
08f130a0 1055 chargingStation: ChargingStation,
66a7748d 1056 commandPayload: RemoteStopTransactionRequest
f03e1042 1057 ): Promise<GenericResponse> {
66a7748d 1058 const { transactionId } = commandPayload
ded57f02
JB
1059 if (chargingStation.hasEvses) {
1060 for (const [evseId, evseStatus] of chargingStation.evses) {
1061 if (evseId > 0) {
1062 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1063 if (connectorStatus.transactionId === transactionId) {
66a7748d 1064 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ded57f02
JB
1065 }
1066 }
1067 }
1068 }
1069 } else {
1070 for (const connectorId of chargingStation.connectors.keys()) {
1071 if (
1072 connectorId > 0 &&
1073 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1074 ) {
66a7748d 1075 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ef6fa3fb 1076 }
c0560973
JB
1077 }
1078 }
44b9b577 1079 logger.warn(
66a7748d
JB
1080 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1081 )
1082 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1083 }
47e22477 1084
66a7748d 1085 private handleRequestUpdateFirmware (
b03df580 1086 chargingStation: ChargingStation,
66a7748d 1087 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1088 ): OCPP16UpdateFirmwareResponse {
1089 if (
66a7748d 1090 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1091 chargingStation,
1092 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1093 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1094 )
b03df580 1095 ) {
5d280aae 1096 logger.warn(
66a7748d
JB
1097 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1098 )
1099 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1100 }
66a7748d 1101 let { retrieveDate } = commandPayload
5199f9fd 1102 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
5d280aae 1103 logger.warn(
66a7748d
JB
1104 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1105 )
1106 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1107 }
66a7748d
JB
1108 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1109 retrieveDate = convertToDate(retrieveDate)!
1110 const now = Date.now()
5199f9fd 1111 if (retrieveDate.getTime() <= now) {
66a7748d 1112 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1113 } else {
5199f9fd
JB
1114 setTimeout(() => {
1115 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1116 }, retrieveDate.getTime() - now)
c9a4f9ea 1117 }
66a7748d 1118 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1119 }
1120
66a7748d 1121 private async updateFirmwareSimulation (
c9a4f9ea 1122 chargingStation: ChargingStation,
90293abb 1123 maxDelay = 30,
66a7748d 1124 minDelay = 15
c9a4f9ea 1125 ): Promise<void> {
66a7748d
JB
1126 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1127 return
1bf29f5b 1128 }
ded57f02
JB
1129 if (chargingStation.hasEvses) {
1130 for (const [evseId, evseStatus] of chargingStation.evses) {
1131 if (evseId > 0) {
1132 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1133 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1134 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1135 chargingStation,
1136 connectorId,
66a7748d
JB
1137 OCPP16ChargePointStatus.Unavailable
1138 )
ded57f02
JB
1139 }
1140 }
1141 }
1142 }
1143 } else {
1144 for (const connectorId of chargingStation.connectors.keys()) {
1145 if (
1146 connectorId > 0 &&
1147 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1148 ) {
1149 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1150 chargingStation,
1151 connectorId,
66a7748d
JB
1152 OCPP16ChargePointStatus.Unavailable
1153 )
ded57f02 1154 }
c9a4f9ea
JB
1155 }
1156 }
93f0c2c8 1157 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1158 OCPP16FirmwareStatusNotificationRequest,
1159 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1160 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1161 status: OCPP16FirmwareStatus.Downloading
1162 })
5199f9fd
JB
1163 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1164 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1165 if (
93f0c2c8
JB
1166 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1167 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1168 ) {
66a7748d 1169 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
5d280aae 1170 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1171 OCPP16FirmwareStatusNotificationRequest,
1172 OCPP16FirmwareStatusNotificationResponse
5d280aae 1173 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1174 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1175 })
93f0c2c8 1176 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1177 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1178 return
5d280aae 1179 }
66a7748d 1180 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
c9a4f9ea 1181 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1182 OCPP16FirmwareStatusNotificationRequest,
1183 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1184 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1185 status: OCPP16FirmwareStatus.Downloaded
1186 })
5199f9fd
JB
1187 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1188 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1189 let wasTransactionsStarted = false
1190 let transactionsStarted: boolean
62340a29 1191 do {
66a7748d 1192 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1193 if (runningTransactions > 0) {
66a7748d 1194 const waitTime = secondsToMilliseconds(15)
62340a29 1195 logger.debug(
944d4529 1196 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1197 waitTime
1198 )} before continuing firmware update simulation`
1199 )
1200 await sleep(waitTime)
1201 transactionsStarted = true
1202 wasTransactionsStarted = true
62340a29 1203 } else {
ded57f02
JB
1204 if (chargingStation.hasEvses) {
1205 for (const [evseId, evseStatus] of chargingStation.evses) {
1206 if (evseId > 0) {
1207 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1208 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1209 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1210 chargingStation,
1211 connectorId,
66a7748d
JB
1212 OCPP16ChargePointStatus.Unavailable
1213 )
ded57f02
JB
1214 }
1215 }
1216 }
1217 }
1218 } else {
1219 for (const connectorId of chargingStation.connectors.keys()) {
1220 if (
1221 connectorId > 0 &&
1222 chargingStation.getConnectorStatus(connectorId)?.status !==
1223 OCPP16ChargePointStatus.Unavailable
1224 ) {
1225 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1226 chargingStation,
1227 connectorId,
66a7748d
JB
1228 OCPP16ChargePointStatus.Unavailable
1229 )
ded57f02 1230 }
62340a29
JB
1231 }
1232 }
66a7748d 1233 transactionsStarted = false
62340a29 1234 }
66a7748d 1235 } while (transactionsStarted)
be4c6702 1236 !wasTransactionsStarted &&
66a7748d
JB
1237 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1238 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1239 return
1bf29f5b 1240 }
c9a4f9ea 1241 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1242 OCPP16FirmwareStatusNotificationRequest,
1243 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1244 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1245 status: OCPP16FirmwareStatus.Installing
1246 })
5199f9fd
JB
1247 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1248 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1249 if (
1250 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1251 OCPP16FirmwareStatus.InstallationFailed
1252 ) {
66a7748d 1253 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
93f0c2c8 1254 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1255 OCPP16FirmwareStatusNotificationRequest,
1256 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1257 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1258 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1259 })
93f0c2c8 1260 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1261 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1262 return
93f0c2c8 1263 }
15748260 1264 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
66a7748d
JB
1265 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1266 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1267 }
b03df580
JB
1268 }
1269
66a7748d 1270 private async handleRequestGetDiagnostics (
08f130a0 1271 chargingStation: ChargingStation,
66a7748d 1272 commandPayload: GetDiagnosticsRequest
e7aeea18 1273 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1274 if (
66a7748d 1275 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1276 chargingStation,
370ae4ee 1277 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1278 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1279 )
68cb8b91 1280 ) {
90293abb 1281 logger.warn(
66a7748d
JB
1282 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1283 )
1284 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1285 }
66a7748d
JB
1286 const { location } = commandPayload
1287 const uri = new URL(location)
47e22477 1288 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1289 let ftpClient: Client | undefined
47e22477 1290 try {
d972af76 1291 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1292 .filter((file) => file.endsWith('.log'))
66a7748d 1293 .map((file) => join('./', file))
5199f9fd 1294 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1295 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1296 ftpClient = new Client()
47e22477
JB
1297 const accessResponse = await ftpClient.access({
1298 host: uri.host,
9bf0ef23
JB
1299 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1300 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1301 ...(isNotEmptyString(uri.password) && { password: uri.password })
1302 })
1303 let uploadResponse: FTPResponse | undefined
47e22477 1304 if (accessResponse.code === 220) {
72092cfc 1305 ftpClient.trackProgress((info) => {
e7aeea18 1306 logger.info(
56563a3c 1307 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1308 info.bytes / 1024
66a7748d
JB
1309 } bytes transferred from diagnostics archive ${info.name}`
1310 )
6a8329b4
JB
1311 chargingStation.ocppRequestService
1312 .requestHandler<
66a7748d
JB
1313 OCPP16DiagnosticsStatusNotificationRequest,
1314 OCPP16DiagnosticsStatusNotificationResponse
1315 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1316 status: OCPP16DiagnosticsStatus.Uploading
1317 })
72092cfc 1318 .catch((error) => {
6a8329b4 1319 logger.error(
944d4529
JB
1320 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1321 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1322 }'`,
66a7748d
JB
1323 error
1324 )
1325 })
1326 })
e7aeea18 1327 uploadResponse = await ftpClient.uploadFrom(
d972af76 1328 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1329 `${uri.pathname}${diagnosticsArchive}`
1330 )
47e22477 1331 if (uploadResponse.code === 226) {
08f130a0 1332 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1333 OCPP16DiagnosticsStatusNotificationRequest,
1334 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1335 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1336 status: OCPP16DiagnosticsStatus.Uploaded
1337 })
5199f9fd 1338 ftpClient.close()
66a7748d 1339 return { fileName: diagnosticsArchive }
47e22477 1340 }
e7aeea18
JB
1341 throw new OCPPError(
1342 ErrorType.GENERIC_ERROR,
5199f9fd 1343 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1344 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1345 )
47e22477 1346 }
e7aeea18
JB
1347 throw new OCPPError(
1348 ErrorType.GENERIC_ERROR,
5199f9fd 1349 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1350 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1351 )
47e22477 1352 } catch (error) {
08f130a0 1353 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1354 OCPP16DiagnosticsStatusNotificationRequest,
1355 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1356 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1357 status: OCPP16DiagnosticsStatus.UploadFailed
1358 })
5199f9fd 1359 ftpClient?.close()
66a7748d 1360 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1361 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1362 chargingStation,
e7aeea18
JB
1363 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1364 error as Error,
66a7748d
JB
1365 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1366 )!
47e22477
JB
1367 }
1368 } else {
e7aeea18 1369 logger.error(
08f130a0 1370 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1371 uri.protocol
66a7748d
JB
1372 } to transfer the diagnostic logs archive`
1373 )
08f130a0 1374 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1375 OCPP16DiagnosticsStatusNotificationRequest,
1376 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1377 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1378 status: OCPP16DiagnosticsStatus.UploadFailed
1379 })
1380 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1381 }
1382 }
802cfa13 1383
66a7748d 1384 private handleRequestTriggerMessage (
08f130a0 1385 chargingStation: ChargingStation,
66a7748d 1386 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1387 ): OCPP16TriggerMessageResponse {
66a7748d 1388 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1389 if (
1390 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1391 chargingStation,
370ae4ee 1392 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1393 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1394 ) ||
0d1f33ba 1395 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1396 ) {
66a7748d 1397 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1398 }
c60ed4b8 1399 if (
4caa7e67 1400 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1401 chargingStation,
1402 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1403 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1404 connectorId!
c60ed4b8
JB
1405 )
1406 ) {
66a7748d 1407 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1408 }
802cfa13 1409 try {
0d1f33ba 1410 switch (requestedMessage) {
c60ed4b8 1411 case OCPP16MessageTrigger.BootNotification:
802cfa13 1412 setTimeout(() => {
08f130a0 1413 chargingStation.ocppRequestService
f7f98c68 1414 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
66a7748d
JB
1415 chargingStation,
1416 OCPP16RequestCommand.BOOT_NOTIFICATION,
1417 chargingStation.bootNotificationRequest,
1418 { skipBufferingOnError: true, triggerMessage: true }
1419 )
72092cfc 1420 .then((response) => {
66a7748d 1421 chargingStation.bootNotificationResponse = response
ae711c83 1422 })
66a7748d
JB
1423 .catch(Constants.EMPTY_FUNCTION)
1424 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1425 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1426 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1427 setTimeout(() => {
08f130a0 1428 chargingStation.ocppRequestService
f7f98c68 1429 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
66a7748d
JB
1430 chargingStation,
1431 OCPP16RequestCommand.HEARTBEAT,
1432 undefined,
1433 {
1434 triggerMessage: true
1435 }
1436 )
1437 .catch(Constants.EMPTY_FUNCTION)
1438 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1439 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1440 case OCPP16MessageTrigger.StatusNotification:
dc661702 1441 setTimeout(() => {
be9f397b 1442 if (connectorId != null) {
08f130a0 1443 chargingStation.ocppRequestService
dc661702 1444 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
66a7748d
JB
1445 chargingStation,
1446 OCPP16RequestCommand.STATUS_NOTIFICATION,
1447 {
1448 connectorId,
1449 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
be9f397b 1450 status: chargingStation.getConnectorStatus(connectorId)?.status
66a7748d
JB
1451 },
1452 {
1453 triggerMessage: true
1454 }
1455 )
1456 .catch(Constants.EMPTY_FUNCTION)
f938317f
JB
1457 } else if (chargingStation.hasEvses) {
1458 for (const evseStatus of chargingStation.evses.values()) {
1459 for (const [id, connectorStatus] of evseStatus.connectors) {
66a7748d
JB
1460 chargingStation.ocppRequestService
1461 .requestHandler<
1462 OCPP16StatusNotificationRequest,
1463 OCPP16StatusNotificationResponse
1464 >(
1465 chargingStation,
1466 OCPP16RequestCommand.STATUS_NOTIFICATION,
1467 {
1468 connectorId: id,
1469 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
f938317f 1470 status: connectorStatus.status
66a7748d
JB
1471 },
1472 {
1473 triggerMessage: true
1474 }
1475 )
1476 .catch(Constants.EMPTY_FUNCTION)
ded57f02 1477 }
dc661702 1478 }
f938317f
JB
1479 } else {
1480 for (const [id, connectorStatus] of chargingStation.connectors) {
1481 chargingStation.ocppRequestService
1482 .requestHandler<
1483 OCPP16StatusNotificationRequest,
1484 OCPP16StatusNotificationResponse
1485 >(
1486 chargingStation,
1487 OCPP16RequestCommand.STATUS_NOTIFICATION,
1488 {
1489 connectorId: id,
1490 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1491 status: connectorStatus.status
1492 },
1493 {
1494 triggerMessage: true
1495 }
1496 )
1497 .catch(Constants.EMPTY_FUNCTION)
1498 }
dc661702 1499 }
66a7748d
JB
1500 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1501 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
802cfa13 1502 default:
66a7748d 1503 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1504 }
1505 } catch (error) {
66a7748d 1506 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1507 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1508 chargingStation,
e7aeea18
JB
1509 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1510 error as Error,
66a7748d
JB
1511 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1512 )!
802cfa13
JB
1513 }
1514 }
77b95a89 1515
66a7748d 1516 private handleRequestDataTransfer (
77b95a89 1517 chargingStation: ChargingStation,
66a7748d 1518 commandPayload: OCPP16DataTransferRequest
77b95a89 1519 ): OCPP16DataTransferResponse {
66a7748d 1520 const { vendorId } = commandPayload
77b95a89 1521 try {
0d1f33ba 1522 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1523 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1524 }
66a7748d 1525 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1526 } catch (error) {
66a7748d 1527 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1528 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1529 chargingStation,
1530 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1531 error as Error,
66a7748d
JB
1532 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1533 )!
77b95a89
JB
1534 }
1535 }
24578c31 1536
66a7748d 1537 private async handleRequestReserveNow (
24578c31 1538 chargingStation: ChargingStation,
66a7748d 1539 commandPayload: OCPP16ReserveNowRequest
24578c31 1540 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1541 if (
1542 !OCPP16ServiceUtils.checkFeatureProfile(
1543 chargingStation,
1544 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1545 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1546 )
1547 ) {
66a7748d 1548 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1549 }
66a7748d
JB
1550 const { reservationId, idTag, connectorId } = commandPayload
1551 let response: OCPP16ReserveNowResponse
24578c31 1552 try {
d984c13f 1553 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1554 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1555 }
10e8c3e1 1556 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1557 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1558 }
66dd3447 1559 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1560 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1561 }
66a7748d 1562 await removeExpiredReservations(chargingStation)
f938317f 1563 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1564 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1565 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1566 break
178956d8
JB
1567 case OCPP16ChargePointStatus.Preparing:
1568 case OCPP16ChargePointStatus.Charging:
1569 case OCPP16ChargePointStatus.SuspendedEV:
1570 case OCPP16ChargePointStatus.SuspendedEVSE:
1571 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1572 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1573 break
178956d8 1574 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1575 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1576 break
178956d8 1577 case OCPP16ChargePointStatus.Reserved:
66dd3447 1578 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1579 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1580 break
24578c31
JB
1581 }
1582 // eslint-disable-next-line no-fallthrough
1583 default:
66dd3447 1584 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1585 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1586 break
d193a949
JB
1587 }
1588 await chargingStation.addReservation({
1589 id: commandPayload.reservationId,
66a7748d
JB
1590 ...commandPayload
1591 })
1592 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1593 break
24578c31 1594 }
66a7748d 1595 return response
24578c31 1596 } catch (error) {
66a7748d
JB
1597 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1598 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1599 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1600 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1601 chargingStation,
1602 OCPP16IncomingRequestCommand.RESERVE_NOW,
1603 error as Error,
66a7748d
JB
1604 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1605 )!
24578c31
JB
1606 }
1607 }
1608
66a7748d 1609 private async handleRequestCancelReservation (
24578c31 1610 chargingStation: ChargingStation,
66a7748d 1611 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1612 ): Promise<GenericResponse> {
66dd3447
JB
1613 if (
1614 !OCPP16ServiceUtils.checkFeatureProfile(
1615 chargingStation,
1616 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1617 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1618 )
1619 ) {
66a7748d 1620 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1621 }
24578c31 1622 try {
66a7748d
JB
1623 const { reservationId } = commandPayload
1624 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1625 if (reservation == null) {
90aceaf6 1626 logger.debug(
66a7748d
JB
1627 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1628 )
1629 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1630 }
ec9f36cc 1631 await chargingStation.removeReservation(
300418e9 1632 reservation,
66a7748d
JB
1633 ReservationTerminationReason.RESERVATION_CANCELED
1634 )
1635 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1636 } catch (error) {
66a7748d 1637 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1638 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1639 chargingStation,
1640 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1641 error as Error,
66a7748d
JB
1642 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1643 )!
24578c31
JB
1644 }
1645 }
c0560973 1646}