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