build(deps-dev): apply updates
[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 = `
a223d9be
JB
925 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
926 chargingStation.stationInfo?.chargingStationId
927 }#${transactionConnectorId} for idTag '${idTag}'`
649287f8
JB
928 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
929 chargingStation,
930 transactionConnectorId,
66a7748d
JB
931 OCPP16ChargePointStatus.Preparing
932 )
933 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
934 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!
d984c13f
JB
935 // Authorization check required
936 if (
66a7748d 937 chargingStation.getAuthorizeRemoteTxRequests() &&
66dd3447
JB
938 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
939 ) {
940 // Authorization successful, start transaction
941 if (
66a7748d 942 (chargingProfile != null &&
8e3437b1
JB
943 this.setRemoteStartTransactionChargingProfile(
944 chargingStation,
945 transactionConnectorId,
66a7748d
JB
946 chargingProfile
947 )) ||
948 chargingProfile == null
66dd3447 949 ) {
66a7748d 950 connectorStatus.transactionRemoteStarted = true
e7aeea18 951 if (
66dd3447
JB
952 (
953 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
954 OCPP16StartTransactionRequest,
955 OCPP16StartTransactionResponse
d984c13f
JB
956 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
957 connectorId: transactionConnectorId,
66a7748d 958 idTag
d984c13f 959 })
66dd3447 960 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 961 ) {
66a7748d
JB
962 logger.debug(remoteStartTransactionLogMsg)
963 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
e060fe58 964 }
66a7748d 965 return await this.notifyRemoteStartTransactionRejected(
08f130a0 966 chargingStation,
e7aeea18 967 transactionConnectorId,
66a7748d
JB
968 idTag
969 )
c0560973 970 }
66a7748d 971 return await this.notifyRemoteStartTransactionRejected(
08f130a0 972 chargingStation,
e7aeea18 973 transactionConnectorId,
66a7748d
JB
974 idTag
975 )
c0560973 976 }
649287f8
JB
977 // No authorization check required, start transaction
978 if (
66a7748d 979 (chargingProfile != null &&
8e3437b1
JB
980 this.setRemoteStartTransactionChargingProfile(
981 chargingStation,
982 transactionConnectorId,
66a7748d
JB
983 chargingProfile
984 )) ||
985 chargingProfile == null
649287f8 986 ) {
66a7748d 987 connectorStatus.transactionRemoteStarted = true
649287f8
JB
988 if (
989 (
990 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
991 OCPP16StartTransactionRequest,
992 OCPP16StartTransactionResponse
649287f8
JB
993 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
994 connectorId: transactionConnectorId,
66a7748d 995 idTag
649287f8
JB
996 })
997 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
998 ) {
66a7748d
JB
999 logger.debug(remoteStartTransactionLogMsg)
1000 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
649287f8 1001 }
66a7748d 1002 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1003 chargingStation,
1004 transactionConnectorId,
66a7748d
JB
1005 idTag
1006 )
649287f8 1007 }
66a7748d 1008 return await this.notifyRemoteStartTransactionRejected(
08f130a0
JB
1009 chargingStation,
1010 transactionConnectorId,
66a7748d
JB
1011 idTag
1012 )
a7fc8211
JB
1013 }
1014
66a7748d 1015 private async notifyRemoteStartTransactionRejected (
08f130a0 1016 chargingStation: ChargingStation,
e7aeea18 1017 connectorId: number,
66a7748d 1018 idTag: string
f03e1042 1019 ): Promise<GenericResponse> {
66a7748d 1020 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f406808f 1021 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
1022 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1023 chargingStation,
ef6fa3fb 1024 connectorId,
66a7748d
JB
1025 OCPP16ChargePointStatus.Available
1026 )
e060fe58 1027 }
e7aeea18 1028 logger.warn(
66a7748d
JB
1029 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1030 )
1031 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1032 }
1033
66a7748d 1034 private setRemoteStartTransactionChargingProfile (
08f130a0 1035 chargingStation: ChargingStation,
e7aeea18 1036 connectorId: number,
66a7748d 1037 chargingProfile: OCPP16ChargingProfile
e7aeea18 1038 ): boolean {
5199f9fd 1039 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1040 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1041 logger.debug(
944d4529 1042 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
66a7748d
JB
1043 chargingProfile
1044 )
1045 return true
a7fc8211 1046 }
f406808f
JB
1047 logger.warn(
1048 `${chargingStation.logPrefix()} Not allowed to set ${
1049 chargingProfile.chargingProfilePurpose
66a7748d
JB
1050 } charging profile(s) at remote start transaction`
1051 )
1052 return false
a7fc8211
JB
1053 }
1054
66a7748d 1055 private async handleRequestRemoteStopTransaction (
08f130a0 1056 chargingStation: ChargingStation,
66a7748d 1057 commandPayload: RemoteStopTransactionRequest
f03e1042 1058 ): Promise<GenericResponse> {
66a7748d 1059 const { transactionId } = commandPayload
ded57f02
JB
1060 if (chargingStation.hasEvses) {
1061 for (const [evseId, evseStatus] of chargingStation.evses) {
1062 if (evseId > 0) {
1063 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1064 if (connectorStatus.transactionId === transactionId) {
66a7748d 1065 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ded57f02
JB
1066 }
1067 }
1068 }
1069 }
1070 } else {
1071 for (const connectorId of chargingStation.connectors.keys()) {
1072 if (
1073 connectorId > 0 &&
1074 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1075 ) {
66a7748d 1076 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ef6fa3fb 1077 }
c0560973
JB
1078 }
1079 }
44b9b577 1080 logger.warn(
66a7748d
JB
1081 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1082 )
1083 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1084 }
47e22477 1085
66a7748d 1086 private handleRequestUpdateFirmware (
b03df580 1087 chargingStation: ChargingStation,
66a7748d 1088 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1089 ): OCPP16UpdateFirmwareResponse {
1090 if (
66a7748d 1091 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1092 chargingStation,
1093 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1094 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1095 )
b03df580 1096 ) {
5d280aae 1097 logger.warn(
66a7748d
JB
1098 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1099 )
1100 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1101 }
66a7748d 1102 let { retrieveDate } = commandPayload
5199f9fd 1103 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
5d280aae 1104 logger.warn(
66a7748d
JB
1105 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1106 )
1107 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1108 }
66a7748d
JB
1109 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1110 retrieveDate = convertToDate(retrieveDate)!
1111 const now = Date.now()
5199f9fd 1112 if (retrieveDate.getTime() <= now) {
66a7748d 1113 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1114 } else {
5199f9fd
JB
1115 setTimeout(() => {
1116 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1117 }, retrieveDate.getTime() - now)
c9a4f9ea 1118 }
66a7748d 1119 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1120 }
1121
66a7748d 1122 private async updateFirmwareSimulation (
c9a4f9ea 1123 chargingStation: ChargingStation,
90293abb 1124 maxDelay = 30,
66a7748d 1125 minDelay = 15
c9a4f9ea 1126 ): Promise<void> {
66a7748d
JB
1127 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1128 return
1bf29f5b 1129 }
ded57f02
JB
1130 if (chargingStation.hasEvses) {
1131 for (const [evseId, evseStatus] of chargingStation.evses) {
1132 if (evseId > 0) {
1133 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1134 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1135 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1136 chargingStation,
1137 connectorId,
66a7748d
JB
1138 OCPP16ChargePointStatus.Unavailable
1139 )
ded57f02
JB
1140 }
1141 }
1142 }
1143 }
1144 } else {
1145 for (const connectorId of chargingStation.connectors.keys()) {
1146 if (
1147 connectorId > 0 &&
1148 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1149 ) {
1150 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1151 chargingStation,
1152 connectorId,
66a7748d
JB
1153 OCPP16ChargePointStatus.Unavailable
1154 )
ded57f02 1155 }
c9a4f9ea
JB
1156 }
1157 }
93f0c2c8 1158 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1159 OCPP16FirmwareStatusNotificationRequest,
1160 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1161 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1162 status: OCPP16FirmwareStatus.Downloading
1163 })
5199f9fd
JB
1164 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1165 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1166 if (
93f0c2c8
JB
1167 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1168 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1169 ) {
66a7748d 1170 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
5d280aae 1171 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1172 OCPP16FirmwareStatusNotificationRequest,
1173 OCPP16FirmwareStatusNotificationResponse
5d280aae 1174 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1175 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1176 })
93f0c2c8 1177 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1178 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1179 return
5d280aae 1180 }
66a7748d 1181 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
c9a4f9ea 1182 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1183 OCPP16FirmwareStatusNotificationRequest,
1184 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1185 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1186 status: OCPP16FirmwareStatus.Downloaded
1187 })
5199f9fd
JB
1188 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1189 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1190 let wasTransactionsStarted = false
1191 let transactionsStarted: boolean
62340a29 1192 do {
66a7748d 1193 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1194 if (runningTransactions > 0) {
66a7748d 1195 const waitTime = secondsToMilliseconds(15)
62340a29 1196 logger.debug(
944d4529 1197 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1198 waitTime
1199 )} before continuing firmware update simulation`
1200 )
1201 await sleep(waitTime)
1202 transactionsStarted = true
1203 wasTransactionsStarted = true
62340a29 1204 } else {
ded57f02
JB
1205 if (chargingStation.hasEvses) {
1206 for (const [evseId, evseStatus] of chargingStation.evses) {
1207 if (evseId > 0) {
1208 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1209 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1210 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1211 chargingStation,
1212 connectorId,
66a7748d
JB
1213 OCPP16ChargePointStatus.Unavailable
1214 )
ded57f02
JB
1215 }
1216 }
1217 }
1218 }
1219 } else {
1220 for (const connectorId of chargingStation.connectors.keys()) {
1221 if (
1222 connectorId > 0 &&
1223 chargingStation.getConnectorStatus(connectorId)?.status !==
1224 OCPP16ChargePointStatus.Unavailable
1225 ) {
1226 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1227 chargingStation,
1228 connectorId,
66a7748d
JB
1229 OCPP16ChargePointStatus.Unavailable
1230 )
ded57f02 1231 }
62340a29
JB
1232 }
1233 }
66a7748d 1234 transactionsStarted = false
62340a29 1235 }
66a7748d 1236 } while (transactionsStarted)
be4c6702 1237 !wasTransactionsStarted &&
66a7748d
JB
1238 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1239 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1240 return
1bf29f5b 1241 }
c9a4f9ea 1242 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1246 status: OCPP16FirmwareStatus.Installing
1247 })
5199f9fd
JB
1248 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1249 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1250 if (
1251 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1252 OCPP16FirmwareStatus.InstallationFailed
1253 ) {
66a7748d 1254 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
93f0c2c8 1255 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1256 OCPP16FirmwareStatusNotificationRequest,
1257 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1258 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1259 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1260 })
93f0c2c8 1261 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1262 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1263 return
93f0c2c8 1264 }
15748260 1265 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
66a7748d
JB
1266 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1267 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1268 }
b03df580
JB
1269 }
1270
66a7748d 1271 private async handleRequestGetDiagnostics (
08f130a0 1272 chargingStation: ChargingStation,
66a7748d 1273 commandPayload: GetDiagnosticsRequest
e7aeea18 1274 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1275 if (
66a7748d 1276 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1277 chargingStation,
370ae4ee 1278 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1279 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1280 )
68cb8b91 1281 ) {
90293abb 1282 logger.warn(
66a7748d
JB
1283 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1284 )
1285 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1286 }
66a7748d
JB
1287 const { location } = commandPayload
1288 const uri = new URL(location)
47e22477 1289 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1290 let ftpClient: Client | undefined
47e22477 1291 try {
d972af76 1292 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1293 .filter((file) => file.endsWith('.log'))
66a7748d 1294 .map((file) => join('./', file))
5199f9fd 1295 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1296 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1297 ftpClient = new Client()
47e22477
JB
1298 const accessResponse = await ftpClient.access({
1299 host: uri.host,
9bf0ef23
JB
1300 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1301 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1302 ...(isNotEmptyString(uri.password) && { password: uri.password })
1303 })
1304 let uploadResponse: FTPResponse | undefined
47e22477 1305 if (accessResponse.code === 220) {
72092cfc 1306 ftpClient.trackProgress((info) => {
e7aeea18 1307 logger.info(
56563a3c 1308 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1309 info.bytes / 1024
66a7748d
JB
1310 } bytes transferred from diagnostics archive ${info.name}`
1311 )
6a8329b4
JB
1312 chargingStation.ocppRequestService
1313 .requestHandler<
66a7748d
JB
1314 OCPP16DiagnosticsStatusNotificationRequest,
1315 OCPP16DiagnosticsStatusNotificationResponse
1316 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1317 status: OCPP16DiagnosticsStatus.Uploading
1318 })
72092cfc 1319 .catch((error) => {
6a8329b4 1320 logger.error(
944d4529
JB
1321 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1322 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1323 }'`,
66a7748d
JB
1324 error
1325 )
1326 })
1327 })
e7aeea18 1328 uploadResponse = await ftpClient.uploadFrom(
d972af76 1329 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1330 `${uri.pathname}${diagnosticsArchive}`
1331 )
47e22477 1332 if (uploadResponse.code === 226) {
08f130a0 1333 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1334 OCPP16DiagnosticsStatusNotificationRequest,
1335 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1336 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1337 status: OCPP16DiagnosticsStatus.Uploaded
1338 })
5199f9fd 1339 ftpClient.close()
66a7748d 1340 return { fileName: diagnosticsArchive }
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 }
e7aeea18
JB
1348 throw new OCPPError(
1349 ErrorType.GENERIC_ERROR,
5199f9fd 1350 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1351 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1352 )
47e22477 1353 } catch (error) {
08f130a0 1354 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1355 OCPP16DiagnosticsStatusNotificationRequest,
1356 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1357 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1358 status: OCPP16DiagnosticsStatus.UploadFailed
1359 })
5199f9fd 1360 ftpClient?.close()
66a7748d 1361 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1362 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1363 chargingStation,
e7aeea18
JB
1364 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1365 error as Error,
66a7748d
JB
1366 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1367 )!
47e22477
JB
1368 }
1369 } else {
e7aeea18 1370 logger.error(
08f130a0 1371 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1372 uri.protocol
66a7748d
JB
1373 } to transfer the diagnostic logs archive`
1374 )
08f130a0 1375 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1376 OCPP16DiagnosticsStatusNotificationRequest,
1377 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1378 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1379 status: OCPP16DiagnosticsStatus.UploadFailed
1380 })
1381 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1382 }
1383 }
802cfa13 1384
66a7748d 1385 private handleRequestTriggerMessage (
08f130a0 1386 chargingStation: ChargingStation,
66a7748d 1387 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1388 ): OCPP16TriggerMessageResponse {
66a7748d 1389 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1390 if (
1391 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1392 chargingStation,
370ae4ee 1393 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1394 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1395 ) ||
0d1f33ba 1396 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1397 ) {
66a7748d 1398 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1399 }
c60ed4b8 1400 if (
4caa7e67 1401 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1402 chargingStation,
1403 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1404 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1405 connectorId!
c60ed4b8
JB
1406 )
1407 ) {
66a7748d 1408 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1409 }
802cfa13 1410 try {
0d1f33ba 1411 switch (requestedMessage) {
c60ed4b8 1412 case OCPP16MessageTrigger.BootNotification:
802cfa13 1413 setTimeout(() => {
08f130a0 1414 chargingStation.ocppRequestService
f7f98c68 1415 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
66a7748d
JB
1416 chargingStation,
1417 OCPP16RequestCommand.BOOT_NOTIFICATION,
1418 chargingStation.bootNotificationRequest,
1419 { skipBufferingOnError: true, triggerMessage: true }
1420 )
72092cfc 1421 .then((response) => {
66a7748d 1422 chargingStation.bootNotificationResponse = response
ae711c83 1423 })
66a7748d
JB
1424 .catch(Constants.EMPTY_FUNCTION)
1425 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1426 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1427 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1428 setTimeout(() => {
08f130a0 1429 chargingStation.ocppRequestService
f7f98c68 1430 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
66a7748d
JB
1431 chargingStation,
1432 OCPP16RequestCommand.HEARTBEAT,
1433 undefined,
1434 {
1435 triggerMessage: true
1436 }
1437 )
1438 .catch(Constants.EMPTY_FUNCTION)
1439 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1440 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
c60ed4b8 1441 case OCPP16MessageTrigger.StatusNotification:
dc661702 1442 setTimeout(() => {
be9f397b 1443 if (connectorId != null) {
08f130a0 1444 chargingStation.ocppRequestService
dc661702 1445 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
66a7748d
JB
1446 chargingStation,
1447 OCPP16RequestCommand.STATUS_NOTIFICATION,
1448 {
1449 connectorId,
1450 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
be9f397b 1451 status: chargingStation.getConnectorStatus(connectorId)?.status
66a7748d
JB
1452 },
1453 {
1454 triggerMessage: true
1455 }
1456 )
1457 .catch(Constants.EMPTY_FUNCTION)
f938317f
JB
1458 } else if (chargingStation.hasEvses) {
1459 for (const evseStatus of chargingStation.evses.values()) {
1460 for (const [id, connectorStatus] of evseStatus.connectors) {
66a7748d
JB
1461 chargingStation.ocppRequestService
1462 .requestHandler<
1463 OCPP16StatusNotificationRequest,
1464 OCPP16StatusNotificationResponse
1465 >(
1466 chargingStation,
1467 OCPP16RequestCommand.STATUS_NOTIFICATION,
1468 {
1469 connectorId: id,
1470 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
f938317f 1471 status: connectorStatus.status
66a7748d
JB
1472 },
1473 {
1474 triggerMessage: true
1475 }
1476 )
1477 .catch(Constants.EMPTY_FUNCTION)
ded57f02 1478 }
dc661702 1479 }
f938317f
JB
1480 } else {
1481 for (const [id, connectorStatus] of chargingStation.connectors) {
1482 chargingStation.ocppRequestService
1483 .requestHandler<
1484 OCPP16StatusNotificationRequest,
1485 OCPP16StatusNotificationResponse
1486 >(
1487 chargingStation,
1488 OCPP16RequestCommand.STATUS_NOTIFICATION,
1489 {
1490 connectorId: id,
1491 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1492 status: connectorStatus.status
1493 },
1494 {
1495 triggerMessage: true
1496 }
1497 )
1498 .catch(Constants.EMPTY_FUNCTION)
1499 }
dc661702 1500 }
66a7748d
JB
1501 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY)
1502 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
802cfa13 1503 default:
66a7748d 1504 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1505 }
1506 } catch (error) {
66a7748d 1507 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1508 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1509 chargingStation,
e7aeea18
JB
1510 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1511 error as Error,
66a7748d
JB
1512 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1513 )!
802cfa13
JB
1514 }
1515 }
77b95a89 1516
66a7748d 1517 private handleRequestDataTransfer (
77b95a89 1518 chargingStation: ChargingStation,
66a7748d 1519 commandPayload: OCPP16DataTransferRequest
77b95a89 1520 ): OCPP16DataTransferResponse {
66a7748d 1521 const { vendorId } = commandPayload
77b95a89 1522 try {
0d1f33ba 1523 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1524 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1525 }
66a7748d 1526 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1527 } catch (error) {
66a7748d 1528 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1529 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1530 chargingStation,
1531 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1532 error as Error,
66a7748d
JB
1533 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1534 )!
77b95a89
JB
1535 }
1536 }
24578c31 1537
66a7748d 1538 private async handleRequestReserveNow (
24578c31 1539 chargingStation: ChargingStation,
66a7748d 1540 commandPayload: OCPP16ReserveNowRequest
24578c31 1541 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1542 if (
1543 !OCPP16ServiceUtils.checkFeatureProfile(
1544 chargingStation,
1545 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1546 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1547 )
1548 ) {
66a7748d 1549 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1550 }
66a7748d
JB
1551 const { reservationId, idTag, connectorId } = commandPayload
1552 let response: OCPP16ReserveNowResponse
24578c31 1553 try {
d984c13f 1554 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1555 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1556 }
10e8c3e1 1557 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1558 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1559 }
66dd3447 1560 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1561 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1562 }
66a7748d 1563 await removeExpiredReservations(chargingStation)
f938317f 1564 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1565 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1566 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1567 break
178956d8
JB
1568 case OCPP16ChargePointStatus.Preparing:
1569 case OCPP16ChargePointStatus.Charging:
1570 case OCPP16ChargePointStatus.SuspendedEV:
1571 case OCPP16ChargePointStatus.SuspendedEVSE:
1572 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1573 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1574 break
178956d8 1575 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1576 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1577 break
178956d8 1578 case OCPP16ChargePointStatus.Reserved:
66dd3447 1579 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1580 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1581 break
24578c31
JB
1582 }
1583 // eslint-disable-next-line no-fallthrough
1584 default:
66dd3447 1585 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1586 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1587 break
d193a949
JB
1588 }
1589 await chargingStation.addReservation({
1590 id: commandPayload.reservationId,
66a7748d
JB
1591 ...commandPayload
1592 })
1593 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1594 break
24578c31 1595 }
66a7748d 1596 return response
24578c31 1597 } catch (error) {
66a7748d
JB
1598 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1599 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1600 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1601 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1602 chargingStation,
1603 OCPP16IncomingRequestCommand.RESERVE_NOW,
1604 error as Error,
66a7748d
JB
1605 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1606 )!
24578c31
JB
1607 }
1608 }
1609
66a7748d 1610 private async handleRequestCancelReservation (
24578c31 1611 chargingStation: ChargingStation,
66a7748d 1612 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1613 ): Promise<GenericResponse> {
66dd3447
JB
1614 if (
1615 !OCPP16ServiceUtils.checkFeatureProfile(
1616 chargingStation,
1617 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1618 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1619 )
1620 ) {
66a7748d 1621 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1622 }
24578c31 1623 try {
66a7748d
JB
1624 const { reservationId } = commandPayload
1625 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1626 if (reservation == null) {
90aceaf6 1627 logger.debug(
66a7748d
JB
1628 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1629 )
1630 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1631 }
ec9f36cc 1632 await chargingStation.removeReservation(
300418e9 1633 reservation,
66a7748d
JB
1634 ReservationTerminationReason.RESERVATION_CANCELED
1635 )
1636 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1637 } catch (error) {
66a7748d 1638 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1639 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1640 chargingStation,
1641 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1642 error as Error,
66a7748d
JB
1643 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1644 )!
24578c31
JB
1645 }
1646 }
c0560973 1647}