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