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