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