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