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