Factor out some common code between OCPP 1.6 and 2.0.1 stack
[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
8114d10e
JB
3import fs from 'fs';
4import path from 'path';
5import { URL, fileURLToPath } from 'url';
6
6c1761d4 7import type { JSONSchemaType } from 'ajv';
27782dbc 8import { Client, type FTPResponse } from 'basic-ftp';
8114d10e
JB
9import tar from 'tar';
10
11import OCPPError from '../../../exception/OCPPError';
6c1761d4 12import type { JsonObject, JsonType } from '../../../types/JsonType';
8114d10e
JB
13import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
14import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
15import {
16 ChargingProfilePurposeType,
27782dbc 17 type OCPP16ChargingProfile,
8114d10e
JB
18} from '../../../types/ocpp/1.6/ChargingProfile';
19import {
20 OCPP16StandardParametersKey,
21 OCPP16SupportedFeatureProfiles,
22} from '../../../types/ocpp/1.6/Configuration';
23import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
e7aeea18 24import {
27782dbc
JB
25 type ChangeAvailabilityRequest,
26 type ChangeConfigurationRequest,
27 type ClearChargingProfileRequest,
27782dbc
JB
28 type GetConfigurationRequest,
29 type GetDiagnosticsRequest,
e7aeea18 30 OCPP16AvailabilityType,
27782dbc
JB
31 type OCPP16BootNotificationRequest,
32 type OCPP16ClearCacheRequest,
33 type OCPP16DataTransferRequest,
77b95a89 34 OCPP16DataTransferVendorId,
c9a4f9ea
JB
35 type OCPP16DiagnosticsStatusNotificationRequest,
36 OCPP16FirmwareStatus,
37 type OCPP16FirmwareStatusNotificationRequest,
27782dbc 38 type OCPP16HeartbeatRequest,
e7aeea18 39 OCPP16IncomingRequestCommand,
c60ed4b8 40 OCPP16MessageTrigger,
94a464f9 41 OCPP16RequestCommand,
27782dbc
JB
42 type OCPP16StatusNotificationRequest,
43 type OCPP16TriggerMessageRequest,
44 type OCPP16UpdateFirmwareRequest,
45 type RemoteStartTransactionRequest,
46 type RemoteStopTransactionRequest,
47 type ResetRequest,
48 type SetChargingProfileRequest,
49 type UnlockConnectorRequest,
e7aeea18 50} from '../../../types/ocpp/1.6/Requests';
77b95a89 51import {
27782dbc
JB
52 type ChangeAvailabilityResponse,
53 type ChangeConfigurationResponse,
54 type ClearChargingProfileResponse,
27782dbc
JB
55 type GetConfigurationResponse,
56 type GetDiagnosticsResponse,
57 type OCPP16BootNotificationResponse,
58 type OCPP16DataTransferResponse,
77b95a89 59 OCPP16DataTransferStatus,
c9a4f9ea
JB
60 type OCPP16DiagnosticsStatusNotificationResponse,
61 type OCPP16FirmwareStatusNotificationResponse,
27782dbc
JB
62 type OCPP16HeartbeatResponse,
63 type OCPP16StatusNotificationResponse,
64 type OCPP16TriggerMessageResponse,
65 type OCPP16UpdateFirmwareResponse,
66 type SetChargingProfileResponse,
67 type UnlockConnectorResponse,
e7aeea18 68} from '../../../types/ocpp/1.6/Responses';
e7aeea18
JB
69import {
70 OCPP16AuthorizationStatus,
27782dbc
JB
71 type OCPP16AuthorizeRequest,
72 type OCPP16AuthorizeResponse,
73 type OCPP16StartTransactionRequest,
74 type OCPP16StartTransactionResponse,
e7aeea18
JB
75 OCPP16StopTransactionReason,
76} from '../../../types/ocpp/1.6/Transaction';
6c1761d4 77import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
8114d10e 78import { ErrorType } from '../../../types/ocpp/ErrorType';
d270cc87 79import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
6c1761d4
JB
80import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
81import type { DefaultResponse } from '../../../types/ocpp/Responses';
8114d10e
JB
82import Constants from '../../../utils/Constants';
83import logger from '../../../utils/Logger';
84import Utils from '../../../utils/Utils';
73b9adec 85import type ChargingStation from '../../ChargingStation';
17ac262c 86import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
9d7484a4 87import { ChargingStationUtils } from '../../ChargingStationUtils';
bf53cadf 88import OCPPConstants from '../OCPPConstants';
c0560973 89import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
8114d10e 90import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
c0560973 91
2a115f87 92const moduleName = 'OCPP16IncomingRequestService';
909dcf2d 93
c0560973 94export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
b3fc3ff5 95 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
58144adb
JB
96 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
97
08f130a0 98 public constructor() {
909dcf2d 99 if (new.target?.name === moduleName) {
06127450 100 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 101 }
d270cc87 102 super(OCPPVersion.VERSION_16);
58144adb
JB
103 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
104 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
105 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
106 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
e7aeea18
JB
107 [
108 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
109 this.handleRequestGetConfiguration.bind(this),
110 ],
111 [
112 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
113 this.handleRequestChangeConfiguration.bind(this),
114 ],
115 [
116 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
117 this.handleRequestSetChargingProfile.bind(this),
118 ],
119 [
120 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
121 this.handleRequestClearChargingProfile.bind(this),
122 ],
123 [
124 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
125 this.handleRequestChangeAvailability.bind(this),
126 ],
127 [
128 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
129 this.handleRequestRemoteStartTransaction.bind(this),
130 ],
131 [
132 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
133 this.handleRequestRemoteStopTransaction.bind(this),
134 ],
734d790d 135 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
e7aeea18 136 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
77b95a89 137 [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
c9a4f9ea 138 [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
58144adb 139 ]);
b52c969d
JB
140 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
141 [
142 OCPP16IncomingRequestCommand.RESET,
143 JSON.parse(
144 fs.readFileSync(
145 path.resolve(
146 path.dirname(fileURLToPath(import.meta.url)),
147 '../../../assets/json-schemas/ocpp/1.6/Reset.json'
148 ),
149 'utf8'
150 )
151 ) as JSONSchemaType<ResetRequest>,
152 ],
153 [
154 OCPP16IncomingRequestCommand.CLEAR_CACHE,
155 JSON.parse(
156 fs.readFileSync(
157 path.resolve(
158 path.dirname(fileURLToPath(import.meta.url)),
159 '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
160 ),
161 'utf8'
162 )
163 ) as JSONSchemaType<OCPP16ClearCacheRequest>,
164 ],
165 [
166 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
167 JSON.parse(
168 fs.readFileSync(
169 path.resolve(
170 path.dirname(fileURLToPath(import.meta.url)),
171 '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
172 ),
173 'utf8'
174 )
175 ) as JSONSchemaType<UnlockConnectorRequest>,
176 ],
177 [
178 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
179 JSON.parse(
180 fs.readFileSync(
181 path.resolve(
182 path.dirname(fileURLToPath(import.meta.url)),
183 '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
184 ),
185 'utf8'
186 )
187 ) as JSONSchemaType<GetConfigurationRequest>,
188 ],
189 [
190 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
191 JSON.parse(
192 fs.readFileSync(
193 path.resolve(
194 path.dirname(fileURLToPath(import.meta.url)),
195 '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
196 ),
197 'utf8'
198 )
199 ) as JSONSchemaType<ChangeConfigurationRequest>,
200 ],
201 [
202 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
203 JSON.parse(
204 fs.readFileSync(
205 path.resolve(
206 path.dirname(fileURLToPath(import.meta.url)),
207 '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
208 ),
209 'utf8'
210 )
211 ) as JSONSchemaType<GetDiagnosticsRequest>,
212 ],
213 [
214 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
215 JSON.parse(
216 fs.readFileSync(
217 path.resolve(
218 path.dirname(fileURLToPath(import.meta.url)),
219 '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
220 ),
221 'utf8'
222 )
223 ) as JSONSchemaType<SetChargingProfileRequest>,
224 ],
225 [
226 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
227 JSON.parse(
228 fs.readFileSync(
229 path.resolve(
230 path.dirname(fileURLToPath(import.meta.url)),
231 '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
232 ),
233 'utf8'
234 )
235 ) as JSONSchemaType<ClearChargingProfileRequest>,
236 ],
237 [
238 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
239 JSON.parse(
240 fs.readFileSync(
241 path.resolve(
242 path.dirname(fileURLToPath(import.meta.url)),
243 '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
244 ),
245 'utf8'
246 )
247 ) as JSONSchemaType<ChangeAvailabilityRequest>,
248 ],
249 [
250 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
251 JSON.parse(
252 fs.readFileSync(
253 path.resolve(
254 path.dirname(fileURLToPath(import.meta.url)),
255 '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
256 ),
257 'utf8'
258 )
259 ) as JSONSchemaType<RemoteStartTransactionRequest>,
260 ],
261 [
262 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
263 JSON.parse(
264 fs.readFileSync(
265 path.resolve(
266 path.dirname(fileURLToPath(import.meta.url)),
267 '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
268 ),
269 'utf8'
270 )
271 ) as JSONSchemaType<RemoteStopTransactionRequest>,
272 ],
273 [
274 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
275 JSON.parse(
276 fs.readFileSync(
277 path.resolve(
278 path.dirname(fileURLToPath(import.meta.url)),
279 '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
280 ),
281 'utf8'
282 )
283 ) as JSONSchemaType<OCPP16TriggerMessageRequest>,
284 ],
77b95a89
JB
285 [
286 OCPP16IncomingRequestCommand.DATA_TRANSFER,
287 JSON.parse(
288 fs.readFileSync(
289 path.resolve(
290 path.dirname(fileURLToPath(import.meta.url)),
291 '../../../assets/json-schemas/ocpp/1.6/DataTransfer.json'
292 ),
293 'utf8'
294 )
295 ) as JSONSchemaType<OCPP16DataTransferRequest>,
296 ],
bfbda738
JB
297 [
298 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
299 JSON.parse(
300 fs.readFileSync(
301 path.resolve(
302 path.dirname(fileURLToPath(import.meta.url)),
303 '../../../assets/json-schemas/ocpp/1.6/UpdateFirmware.json'
304 ),
305 'utf8'
306 )
307 ) as JSONSchemaType<OCPP16UpdateFirmwareRequest>,
308 ],
b52c969d 309 ]);
9952c548 310 this.validatePayload.bind(this);
58144adb
JB
311 }
312
f7f98c68 313 public async incomingRequestHandler(
08f130a0 314 chargingStation: ChargingStation,
e7aeea18
JB
315 messageId: string,
316 commandName: OCPP16IncomingRequestCommand,
5cc4b63b 317 commandPayload: JsonType
e7aeea18 318 ): Promise<void> {
5cc4b63b 319 let response: JsonType;
e7aeea18 320 if (
3a13fc92
JB
321 chargingStation.getOcppStrictCompliance() === true &&
322 chargingStation.isInPendingState() === true &&
e7aeea18
JB
323 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
324 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
325 ) {
326 throw new OCPPError(
327 ErrorType.SECURITY_ERROR,
e3018bc4 328 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
329 commandPayload,
330 null,
331 2
332 )} while the charging station is in pending state on the central server`,
7369e417
JB
333 commandName,
334 commandPayload
e7aeea18 335 );
caad9d6b 336 }
e7aeea18 337 if (
ed6cfcff
JB
338 chargingStation.isRegistered() === true ||
339 (chargingStation.getOcppStrictCompliance() === false &&
340 chargingStation.isInUnknownState() === true)
e7aeea18 341 ) {
65554cc3 342 if (
ed6cfcff
JB
343 this.incomingRequestHandlers.has(commandName) === true &&
344 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
65554cc3 345 ) {
124f3553 346 try {
9c5c4195 347 this.validatePayload(chargingStation, commandName, commandPayload);
c75a6675 348 // Call the method to build the response
08f130a0
JB
349 response = await this.incomingRequestHandlers.get(commandName)(
350 chargingStation,
351 commandPayload
352 );
124f3553
JB
353 } catch (error) {
354 // Log
6c8f5d90
JB
355 logger.error(
356 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
357 error
358 );
124f3553
JB
359 throw error;
360 }
361 } else {
362 // Throw exception
e7aeea18
JB
363 throw new OCPPError(
364 ErrorType.NOT_IMPLEMENTED,
e3018bc4 365 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18
JB
366 commandPayload,
367 null,
368 2
369 )}`,
7369e417
JB
370 commandName,
371 commandPayload
e7aeea18 372 );
c0560973
JB
373 }
374 } else {
e7aeea18
JB
375 throw new OCPPError(
376 ErrorType.SECURITY_ERROR,
e3018bc4 377 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
378 commandPayload,
379 null,
380 2
381 )} while the charging station is not registered on the central server.`,
7369e417
JB
382 commandName,
383 commandPayload
e7aeea18 384 );
c0560973 385 }
c75a6675 386 // Send the built response
08f130a0
JB
387 await chargingStation.ocppRequestService.sendResponse(
388 chargingStation,
389 messageId,
390 response,
391 commandName
392 );
c0560973
JB
393 }
394
9c5c4195
JB
395 private validatePayload(
396 chargingStation: ChargingStation,
397 commandName: OCPP16IncomingRequestCommand,
398 commandPayload: JsonType
399 ): boolean {
45988780 400 if (this.jsonSchemas.has(commandName) === true) {
9c5c4195
JB
401 return this.validateIncomingRequestPayload(
402 chargingStation,
403 commandName,
404 this.jsonSchemas.get(commandName),
405 commandPayload
406 );
407 }
408 logger.warn(
b3fc3ff5 409 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
9c5c4195
JB
410 );
411 return false;
412 }
413
c0560973 414 // Simulate charging station restart
08f130a0
JB
415 private handleRequestReset(
416 chargingStation: ChargingStation,
417 commandPayload: ResetRequest
418 ): DefaultResponse {
64818750
JB
419 this.asyncResource
420 .runInAsyncScope(
421 chargingStation.reset.bind(chargingStation) as (
422 this: ChargingStation,
423 ...args: any[]
424 ) => Promise<void>,
425 chargingStation,
426 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
427 )
428 .catch(() => {
429 /* This is intentional */
430 });
e7aeea18 431 logger.info(
08f130a0 432 `${chargingStation.logPrefix()} ${
e7aeea18
JB
433 commandPayload.type
434 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
08f130a0 435 chargingStation.stationInfo.resetTime
e7aeea18
JB
436 )}`
437 );
bf53cadf 438 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
c0560973
JB
439 }
440
e7aeea18 441 private async handleRequestUnlockConnector(
08f130a0 442 chargingStation: ChargingStation,
e7aeea18
JB
443 commandPayload: UnlockConnectorRequest
444 ): Promise<UnlockConnectorResponse> {
c0560973 445 const connectorId = commandPayload.connectorId;
c60ed4b8
JB
446 if (chargingStation.connectors.has(connectorId) === false) {
447 logger.error(
448 `${chargingStation.logPrefix()} Trying to unlock a non existing connector Id ${connectorId.toString()}`
449 );
bf53cadf 450 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 451 }
c0560973 452 if (connectorId === 0) {
e7aeea18 453 logger.error(
c60ed4b8 454 chargingStation.logPrefix() + ' Trying to unlock connector Id ' + connectorId.toString()
e7aeea18 455 );
bf53cadf 456 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c0560973 457 }
5e3cb728
JB
458 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
459 const stopResponse = await chargingStation.stopTransactionOnConnector(
460 connectorId,
461 OCPP16StopTransactionReason.UNLOCK_COMMAND
462 );
c0560973 463 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 464 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973 465 }
bf53cadf 466 return OCPPConstants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 467 }
08f130a0 468 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
469 OCPP16StatusNotificationRequest,
470 OCPP16StatusNotificationResponse
08f130a0 471 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
472 connectorId,
473 status: OCPP16ChargePointStatus.AVAILABLE,
474 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
475 });
08f130a0 476 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
bf53cadf 477 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
478 }
479
e7aeea18 480 private handleRequestGetConfiguration(
08f130a0 481 chargingStation: ChargingStation,
e7aeea18
JB
482 commandPayload: GetConfigurationRequest
483 ): GetConfigurationResponse {
c0560973
JB
484 const configurationKey: OCPPConfigurationKey[] = [];
485 const unknownKey: string[] = [];
a723e7e9 486 if (Utils.isEmptyArray(commandPayload.key) === true) {
08f130a0 487 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
a723e7e9 488 if (Utils.isUndefined(configuration.visible) === true) {
7f7b65ca 489 configuration.visible = true;
c0560973 490 }
da8629bb 491 if (configuration.visible === false) {
c0560973
JB
492 continue;
493 }
494 configurationKey.push({
7f7b65ca
JB
495 key: configuration.key,
496 readonly: configuration.readonly,
497 value: configuration.value,
c0560973
JB
498 });
499 }
500 } else {
501 for (const key of commandPayload.key) {
17ac262c
JB
502 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
503 chargingStation,
504 key
505 );
c0560973 506 if (keyFound) {
a723e7e9 507 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
508 keyFound.visible = true;
509 }
a723e7e9 510 if (keyFound.visible === false) {
c0560973
JB
511 continue;
512 }
513 configurationKey.push({
514 key: keyFound.key,
515 readonly: keyFound.readonly,
516 value: keyFound.value,
517 });
518 } else {
519 unknownKey.push(key);
520 }
521 }
522 }
523 return {
524 configurationKey,
525 unknownKey,
526 };
527 }
528
e7aeea18 529 private handleRequestChangeConfiguration(
08f130a0 530 chargingStation: ChargingStation,
e7aeea18
JB
531 commandPayload: ChangeConfigurationRequest
532 ): ChangeConfigurationResponse {
17ac262c
JB
533 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
534 chargingStation,
535 commandPayload.key,
536 true
537 );
c0560973 538 if (!keyToChange) {
bf53cadf 539 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973 540 } else if (keyToChange && keyToChange.readonly) {
bf53cadf 541 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
c0560973 542 } else if (keyToChange && !keyToChange.readonly) {
c0560973 543 let valueChanged = false;
a95873d8 544 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
545 ChargingStationConfigurationUtils.setConfigurationKeyValue(
546 chargingStation,
547 commandPayload.key,
548 commandPayload.value,
549 true
550 );
c0560973
JB
551 valueChanged = true;
552 }
553 let triggerHeartbeatRestart = false;
554 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
555 ChargingStationConfigurationUtils.setConfigurationKeyValue(
556 chargingStation,
e7aeea18
JB
557 OCPP16StandardParametersKey.HeartbeatInterval,
558 commandPayload.value
559 );
c0560973
JB
560 triggerHeartbeatRestart = true;
561 }
562 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
563 ChargingStationConfigurationUtils.setConfigurationKeyValue(
564 chargingStation,
e7aeea18
JB
565 OCPP16StandardParametersKey.HeartBeatInterval,
566 commandPayload.value
567 );
c0560973
JB
568 triggerHeartbeatRestart = true;
569 }
570 if (triggerHeartbeatRestart) {
08f130a0 571 chargingStation.restartHeartbeat();
c0560973
JB
572 }
573 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 574 chargingStation.restartWebSocketPing();
c0560973
JB
575 }
576 if (keyToChange.reboot) {
bf53cadf 577 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 578 }
bf53cadf 579 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973
JB
580 }
581 }
582
e7aeea18 583 private handleRequestSetChargingProfile(
08f130a0 584 chargingStation: ChargingStation,
e7aeea18
JB
585 commandPayload: SetChargingProfileRequest
586 ): SetChargingProfileResponse {
370ae4ee 587 if (
1789ba2c 588 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 589 chargingStation,
370ae4ee
JB
590 OCPP16SupportedFeatureProfiles.SmartCharging,
591 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
1789ba2c 592 ) === false
370ae4ee 593 ) {
bf53cadf 594 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 595 }
1789ba2c 596 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 597 logger.error(
08f130a0 598 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
599 commandPayload.connectorId
600 }`
601 );
bf53cadf 602 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 603 }
e7aeea18
JB
604 if (
605 commandPayload.csChargingProfiles.chargingProfilePurpose ===
606 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
607 commandPayload.connectorId !== 0
608 ) {
bf53cadf 609 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 610 }
e7aeea18
JB
611 if (
612 commandPayload.csChargingProfiles.chargingProfilePurpose ===
613 ChargingProfilePurposeType.TX_PROFILE &&
614 (commandPayload.connectorId === 0 ||
5e3cb728
JB
615 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
616 false)
e7aeea18 617 ) {
db0af086
JB
618 logger.error(
619 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${
620 commandPayload.connectorId
621 } without a started transaction`
622 );
bf53cadf 623 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 624 }
ed3d2808 625 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 626 chargingStation,
e7aeea18
JB
627 commandPayload.connectorId,
628 commandPayload.csChargingProfiles
629 );
630 logger.debug(
08f130a0 631 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
632 commandPayload.connectorId
633 }, dump their stack: %j`,
08f130a0 634 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 635 );
bf53cadf 636 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
637 }
638
e7aeea18 639 private handleRequestClearChargingProfile(
08f130a0 640 chargingStation: ChargingStation,
e7aeea18
JB
641 commandPayload: ClearChargingProfileRequest
642 ): ClearChargingProfileResponse {
370ae4ee 643 if (
a36bad10 644 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 645 chargingStation,
370ae4ee
JB
646 OCPP16SupportedFeatureProfiles.SmartCharging,
647 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
a36bad10 648 ) === false
370ae4ee 649 ) {
bf53cadf 650 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 651 }
1789ba2c 652 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 653 logger.error(
08f130a0 654 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
655 commandPayload.connectorId
656 }`
657 );
bf53cadf 658 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 659 }
1789ba2c 660 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16
JB
661 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
662 connectorStatus.chargingProfiles = [];
e7aeea18 663 logger.debug(
08f130a0 664 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
665 commandPayload.connectorId
666 }, dump their stack: %j`,
658e2d16 667 connectorStatus.chargingProfiles
e7aeea18 668 );
bf53cadf 669 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
670 }
671 if (!commandPayload.connectorId) {
672 let clearedCP = false;
08f130a0
JB
673 for (const connectorId of chargingStation.connectors.keys()) {
674 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
675 chargingStation
e7aeea18
JB
676 .getConnectorStatus(connectorId)
677 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
678 let clearCurrentCP = false;
679 if (chargingProfile.chargingProfileId === commandPayload.id) {
680 clearCurrentCP = true;
681 }
682 if (
683 !commandPayload.chargingProfilePurpose &&
684 chargingProfile.stackLevel === commandPayload.stackLevel
685 ) {
686 clearCurrentCP = true;
687 }
688 if (
689 !chargingProfile.stackLevel &&
690 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
691 ) {
692 clearCurrentCP = true;
693 }
694 if (
695 chargingProfile.stackLevel === commandPayload.stackLevel &&
696 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
697 ) {
698 clearCurrentCP = true;
699 }
700 if (clearCurrentCP) {
ccb1d6e9 701 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 702 logger.debug(
08f130a0 703 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
704 commandPayload.connectorId
705 }, dump their stack: %j`,
658e2d16 706 connectorStatus.chargingProfiles
e7aeea18
JB
707 );
708 clearedCP = true;
709 }
710 });
c0560973
JB
711 }
712 }
713 if (clearedCP) {
bf53cadf 714 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
715 }
716 }
bf53cadf 717 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
718 }
719
e7aeea18 720 private async handleRequestChangeAvailability(
08f130a0 721 chargingStation: ChargingStation,
e7aeea18
JB
722 commandPayload: ChangeAvailabilityRequest
723 ): Promise<ChangeAvailabilityResponse> {
c0560973 724 const connectorId: number = commandPayload.connectorId;
c60ed4b8 725 if (chargingStation.connectors.has(connectorId) === false) {
e7aeea18 726 logger.error(
08f130a0 727 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 728 );
bf53cadf 729 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 730 }
e7aeea18
JB
731 const chargePointStatus: OCPP16ChargePointStatus =
732 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
733 ? OCPP16ChargePointStatus.AVAILABLE
734 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 735 if (connectorId === 0) {
bf53cadf 736 let response: ChangeAvailabilityResponse = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 737 for (const id of chargingStation.connectors.keys()) {
5e3cb728 738 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
bf53cadf 739 response = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 740 }
08f130a0 741 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
bf53cadf 742 if (response === OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 743 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
744 OCPP16StatusNotificationRequest,
745 OCPP16StatusNotificationResponse
08f130a0 746 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
747 connectorId: id,
748 status: chargePointStatus,
749 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
750 });
08f130a0 751 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
752 }
753 }
754 return response;
e7aeea18
JB
755 } else if (
756 connectorId > 0 &&
56eb297e
JB
757 (chargingStation.isChargingStationAvailable() === true ||
758 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
759 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
760 ) {
5e3cb728 761 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 762 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
bf53cadf 763 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 764 }
08f130a0
JB
765 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
766 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
767 OCPP16StatusNotificationRequest,
768 OCPP16StatusNotificationResponse
08f130a0 769 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
770 connectorId,
771 status: chargePointStatus,
772 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
773 });
08f130a0 774 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
bf53cadf 775 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 776 }
bf53cadf 777 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
778 }
779
e7aeea18 780 private async handleRequestRemoteStartTransaction(
08f130a0 781 chargingStation: ChargingStation,
e7aeea18
JB
782 commandPayload: RemoteStartTransactionRequest
783 ): Promise<DefaultResponse> {
658e2d16 784 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 785 if (chargingStation.connectors.has(transactionConnectorId) === true) {
91a4f151
JB
786 const remoteStartTransactionLogMsg =
787 chargingStation.logPrefix() +
788 ' Transaction remotely STARTED on ' +
789 chargingStation.stationInfo.chargingStationId +
790 '#' +
791 transactionConnectorId.toString() +
792 " for idTag '" +
793 commandPayload.idTag +
794 "'";
08f130a0 795 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
796 OCPP16StatusNotificationRequest,
797 OCPP16StatusNotificationResponse
08f130a0 798 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
799 connectorId: transactionConnectorId,
800 status: OCPP16ChargePointStatus.PREPARING,
801 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
802 });
1789ba2c 803 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
658e2d16 804 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
1789ba2c 805 if (chargingStation.isChargingStationAvailable() === true) {
e060fe58 806 // Check if authorized
1789ba2c 807 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 808 let authorized = false;
e7aeea18 809 if (
1789ba2c
JB
810 chargingStation.getLocalAuthListEnabled() === true &&
811 chargingStation.hasAuthorizedTags() === true &&
9d7484a4
JB
812 chargingStation.authorizedTagsCache
813 .getAuthorizedTags(
814 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
815 )
1789ba2c 816 .find((idTag) => idTag === commandPayload.idTag)
e7aeea18 817 ) {
658e2d16
JB
818 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
819 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 820 authorized = true;
1789ba2c 821 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 822 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 823 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 824 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
825 OCPP16AuthorizeRequest,
826 OCPP16AuthorizeResponse
08f130a0 827 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
828 idTag: commandPayload.idTag,
829 });
a7fc8211
JB
830 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
831 authorized = true;
a7fc8211 832 }
71068fb9 833 } else {
e7aeea18 834 logger.warn(
08f130a0 835 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 836 );
a7fc8211 837 }
1789ba2c 838 if (authorized === true) {
a7fc8211 839 // Authorization successful, start transaction
e7aeea18
JB
840 if (
841 this.setRemoteStartTransactionChargingProfile(
08f130a0 842 chargingStation,
e7aeea18
JB
843 transactionConnectorId,
844 commandPayload.chargingProfile
1789ba2c 845 ) === true
e7aeea18 846 ) {
658e2d16 847 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
848 if (
849 (
08f130a0 850 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
851 OCPP16StartTransactionRequest,
852 OCPP16StartTransactionResponse
08f130a0 853 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
854 connectorId: transactionConnectorId,
855 idTag: commandPayload.idTag,
856 })
e7aeea18
JB
857 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
858 ) {
91a4f151 859 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 860 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 861 }
e7aeea18 862 return this.notifyRemoteStartTransactionRejected(
08f130a0 863 chargingStation,
e7aeea18
JB
864 transactionConnectorId,
865 commandPayload.idTag
866 );
e060fe58 867 }
e7aeea18 868 return this.notifyRemoteStartTransactionRejected(
08f130a0 869 chargingStation,
e7aeea18
JB
870 transactionConnectorId,
871 commandPayload.idTag
872 );
a7fc8211 873 }
e7aeea18 874 return this.notifyRemoteStartTransactionRejected(
08f130a0 875 chargingStation,
e7aeea18
JB
876 transactionConnectorId,
877 commandPayload.idTag
878 );
36f6a92e 879 }
a7fc8211 880 // No authorization check required, start transaction
e7aeea18
JB
881 if (
882 this.setRemoteStartTransactionChargingProfile(
08f130a0 883 chargingStation,
e7aeea18
JB
884 transactionConnectorId,
885 commandPayload.chargingProfile
1789ba2c 886 ) === true
e7aeea18 887 ) {
658e2d16 888 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
889 if (
890 (
08f130a0 891 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
892 OCPP16StartTransactionRequest,
893 OCPP16StartTransactionResponse
08f130a0 894 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
895 connectorId: transactionConnectorId,
896 idTag: commandPayload.idTag,
897 })
e7aeea18
JB
898 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
899 ) {
91a4f151 900 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 901 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 902 }
e7aeea18 903 return this.notifyRemoteStartTransactionRejected(
08f130a0 904 chargingStation,
e7aeea18
JB
905 transactionConnectorId,
906 commandPayload.idTag
907 );
e060fe58 908 }
e7aeea18 909 return this.notifyRemoteStartTransactionRejected(
08f130a0 910 chargingStation,
e7aeea18
JB
911 transactionConnectorId,
912 commandPayload.idTag
913 );
c0560973 914 }
e7aeea18 915 return this.notifyRemoteStartTransactionRejected(
08f130a0 916 chargingStation,
e7aeea18
JB
917 transactionConnectorId,
918 commandPayload.idTag
919 );
c0560973 920 }
08f130a0
JB
921 return this.notifyRemoteStartTransactionRejected(
922 chargingStation,
923 transactionConnectorId,
924 commandPayload.idTag
925 );
a7fc8211
JB
926 }
927
e7aeea18 928 private async notifyRemoteStartTransactionRejected(
08f130a0 929 chargingStation: ChargingStation,
e7aeea18
JB
930 connectorId: number,
931 idTag: string
932 ): Promise<DefaultResponse> {
933 if (
08f130a0 934 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 935 ) {
08f130a0 936 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
937 OCPP16StatusNotificationRequest,
938 OCPP16StatusNotificationResponse
08f130a0 939 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
940 connectorId,
941 status: OCPP16ChargePointStatus.AVAILABLE,
942 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
943 });
08f130a0 944 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 945 }
e7aeea18 946 logger.warn(
08f130a0 947 chargingStation.logPrefix() +
e7aeea18
JB
948 ' Remote starting transaction REJECTED on connector Id ' +
949 connectorId.toString() +
91a4f151 950 ", idTag '" +
e7aeea18 951 idTag +
91a4f151 952 "', availability '" +
08f130a0 953 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
954 "', status '" +
955 chargingStation.getConnectorStatus(connectorId).status +
956 "'"
e7aeea18 957 );
bf53cadf 958 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
959 }
960
e7aeea18 961 private setRemoteStartTransactionChargingProfile(
08f130a0 962 chargingStation: ChargingStation,
e7aeea18
JB
963 connectorId: number,
964 cp: OCPP16ChargingProfile
965 ): boolean {
a7fc8211 966 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 967 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 968 logger.debug(
08f130a0
JB
969 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
970 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 971 );
a7fc8211
JB
972 return true;
973 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 974 logger.warn(
08f130a0 975 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
976 cp.chargingProfilePurpose
977 } charging profile(s) at remote start transaction`
978 );
a7fc8211 979 return false;
e060fe58
JB
980 } else if (!cp) {
981 return true;
a7fc8211
JB
982 }
983 }
984
e7aeea18 985 private async handleRequestRemoteStopTransaction(
08f130a0 986 chargingStation: ChargingStation,
e7aeea18
JB
987 commandPayload: RemoteStopTransactionRequest
988 ): Promise<DefaultResponse> {
c0560973 989 const transactionId = commandPayload.transactionId;
08f130a0 990 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
991 if (
992 connectorId > 0 &&
08f130a0 993 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 994 ) {
08f130a0 995 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
996 OCPP16StatusNotificationRequest,
997 OCPP16StatusNotificationResponse
08f130a0 998 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
999 connectorId,
1000 status: OCPP16ChargePointStatus.FINISHING,
1001 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1002 });
08f130a0 1003 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
1004 const stopResponse = await chargingStation.stopTransactionOnConnector(
1005 connectorId,
a65319ba 1006 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
1007 );
1008 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 1009 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 1010 }
bf53cadf 1011 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
1012 }
1013 }
44b9b577 1014 logger.warn(
08f130a0 1015 chargingStation.logPrefix() +
e7aeea18
JB
1016 ' Trying to remote stop a non existing transaction ' +
1017 transactionId.toString()
1018 );
bf53cadf 1019 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973 1020 }
47e22477 1021
b03df580
JB
1022 private handleRequestUpdateFirmware(
1023 chargingStation: ChargingStation,
1024 commandPayload: OCPP16UpdateFirmwareRequest
1025 ): OCPP16UpdateFirmwareResponse {
1026 if (
1027 OCPP16ServiceUtils.checkFeatureProfile(
1028 chargingStation,
1029 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1030 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1031 ) === false
1032 ) {
bf53cadf 1033 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580 1034 }
c9a4f9ea
JB
1035 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
1036 if (retrieveDate.getTime() <= Date.now()) {
1037 this.asyncResource
1038 .runInAsyncScope(
1039 this.updateFirmware.bind(this) as (
1040 this: OCPP16IncomingRequestService,
1041 ...args: any[]
1042 ) => Promise<void>,
1043 this,
1044 chargingStation
1045 )
1046 .catch(() => {
1047 /* This is intentional */
1048 });
1049 } else {
1050 setTimeout(() => {
1051 this.updateFirmware(chargingStation).catch(() => {
1052 /* Intentional */
1053 });
1054 }, retrieveDate.getTime() - Date.now());
1055 }
1056 return OCPPConstants.OCPP_RESPONSE_EMPTY;
1057 }
1058
1059 private async updateFirmware(
1060 chargingStation: ChargingStation,
1061 minDelay = 15,
1062 maxDelay = 30
1063 ): Promise<void> {
1064 chargingStation.stopAutomaticTransactionGenerator();
1065 for (const connectorId of chargingStation.connectors.keys()) {
1066 if (
1067 connectorId > 0 &&
1068 chargingStation.getConnectorStatus(connectorId).transactionStarted === false
1069 ) {
1070 await chargingStation.ocppRequestService.requestHandler<
1071 OCPP16StatusNotificationRequest,
1072 OCPP16StatusNotificationResponse
1073 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
1074 connectorId,
1075 status: OCPP16ChargePointStatus.UNAVAILABLE,
1076 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1077 });
3637ca2c
JB
1078 chargingStation.getConnectorStatus(connectorId).status =
1079 OCPP16ChargePointStatus.UNAVAILABLE;
c9a4f9ea
JB
1080 }
1081 }
1082 await chargingStation.ocppRequestService.requestHandler<
1083 OCPP16FirmwareStatusNotificationRequest,
1084 OCPP16FirmwareStatusNotificationResponse
1085 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1086 status: OCPP16FirmwareStatus.Downloading,
1087 });
1088 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1089 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1090 await chargingStation.ocppRequestService.requestHandler<
1091 OCPP16FirmwareStatusNotificationRequest,
1092 OCPP16FirmwareStatusNotificationResponse
1093 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1094 status: OCPP16FirmwareStatus.Downloaded,
1095 });
1096 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1097 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1098 await chargingStation.ocppRequestService.requestHandler<
1099 OCPP16FirmwareStatusNotificationRequest,
1100 OCPP16FirmwareStatusNotificationResponse
1101 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1102 status: OCPP16FirmwareStatus.Installing,
1103 });
1104 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1105 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
f335c1d5 1106 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
b03df580
JB
1107 }
1108
e7aeea18 1109 private async handleRequestGetDiagnostics(
08f130a0 1110 chargingStation: ChargingStation,
e7aeea18
JB
1111 commandPayload: GetDiagnosticsRequest
1112 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1113 if (
1789ba2c 1114 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1115 chargingStation,
370ae4ee
JB
1116 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1117 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1118 ) === false
68cb8b91 1119 ) {
bf53cadf 1120 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1121 }
a3868ec4 1122 const uri = new URL(commandPayload.location);
47e22477
JB
1123 if (uri.protocol.startsWith('ftp:')) {
1124 let ftpClient: Client;
1125 try {
e7aeea18 1126 const logFiles = fs
0d8140bd 1127 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1128 .filter((file) => file.endsWith('.log'))
1129 .map((file) => path.join('./', file));
08f130a0 1130 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1131 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1132 ftpClient = new Client();
1133 const accessResponse = await ftpClient.access({
1134 host: uri.host,
e8191622
JB
1135 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1136 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1137 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1138 });
1139 let uploadResponse: FTPResponse;
1140 if (accessResponse.code === 220) {
1141 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1142 ftpClient.trackProgress(async (info) => {
e7aeea18 1143 logger.info(
08f130a0 1144 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1145 info.bytes / 1024
1146 } bytes transferred from diagnostics archive ${info.name}`
1147 );
08f130a0 1148 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1149 OCPP16DiagnosticsStatusNotificationRequest,
1150 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1151 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1152 status: OCPP16DiagnosticsStatus.Uploading,
1153 });
47e22477 1154 });
e7aeea18 1155 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1156 path.join(
1157 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1158 diagnosticsArchive
1159 ),
e7aeea18
JB
1160 uri.pathname + diagnosticsArchive
1161 );
47e22477 1162 if (uploadResponse.code === 226) {
08f130a0 1163 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1164 OCPP16DiagnosticsStatusNotificationRequest,
1165 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1166 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1167 status: OCPP16DiagnosticsStatus.Uploaded,
1168 });
47e22477
JB
1169 if (ftpClient) {
1170 ftpClient.close();
1171 }
1172 return { fileName: diagnosticsArchive };
1173 }
e7aeea18
JB
1174 throw new OCPPError(
1175 ErrorType.GENERIC_ERROR,
1176 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1177 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1178 }`,
1179 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1180 );
47e22477 1181 }
e7aeea18
JB
1182 throw new OCPPError(
1183 ErrorType.GENERIC_ERROR,
1184 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1185 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1186 }`,
1187 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1188 );
47e22477 1189 } catch (error) {
08f130a0 1190 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1191 OCPP16DiagnosticsStatusNotificationRequest,
1192 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1193 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1194 status: OCPP16DiagnosticsStatus.UploadFailed,
1195 });
47e22477
JB
1196 if (ftpClient) {
1197 ftpClient.close();
1198 }
e7aeea18 1199 return this.handleIncomingRequestError(
08f130a0 1200 chargingStation,
e7aeea18
JB
1201 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1202 error as Error,
bf53cadf 1203 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1204 );
47e22477
JB
1205 }
1206 } else {
e7aeea18 1207 logger.error(
08f130a0 1208 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1209 uri.protocol
1210 } to transfer the diagnostic logs archive`
1211 );
08f130a0 1212 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1213 OCPP16DiagnosticsStatusNotificationRequest,
1214 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1215 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1216 status: OCPP16DiagnosticsStatus.UploadFailed,
1217 });
bf53cadf 1218 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1219 }
1220 }
802cfa13 1221
e7aeea18 1222 private handleRequestTriggerMessage(
08f130a0 1223 chargingStation: ChargingStation,
e7aeea18
JB
1224 commandPayload: OCPP16TriggerMessageRequest
1225 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1226 if (
1227 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1228 chargingStation,
370ae4ee
JB
1229 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1230 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1231 ) ||
1232 !OCPP16ServiceUtils.isMessageTriggerSupported(
1233 chargingStation,
1234 commandPayload.requestedMessage
370ae4ee
JB
1235 )
1236 ) {
bf53cadf 1237 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1238 }
c60ed4b8 1239 if (
4caa7e67 1240 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1241 chargingStation,
1242 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1243 commandPayload.connectorId
1244 )
1245 ) {
bf53cadf 1246 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1247 }
802cfa13
JB
1248 try {
1249 switch (commandPayload.requestedMessage) {
c60ed4b8 1250 case OCPP16MessageTrigger.BootNotification:
802cfa13 1251 setTimeout(() => {
08f130a0 1252 chargingStation.ocppRequestService
f7f98c68 1253 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1254 chargingStation,
6a8b180d 1255 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1256 chargingStation.bootNotificationRequest,
6a8b180d 1257 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1258 )
8bfbc743
JB
1259 .then((response) => {
1260 chargingStation.bootNotificationResponse = response;
ae711c83 1261 })
e7aeea18
JB
1262 .catch(() => {
1263 /* This is intentional */
1264 });
802cfa13 1265 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1266 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1267 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1268 setTimeout(() => {
08f130a0 1269 chargingStation.ocppRequestService
f7f98c68 1270 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1271 chargingStation,
ef6fa3fb
JB
1272 OCPP16RequestCommand.HEARTBEAT,
1273 null,
1274 {
1275 triggerMessage: true,
1276 }
1277 )
e7aeea18
JB
1278 .catch(() => {
1279 /* This is intentional */
1280 });
802cfa13 1281 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1282 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1283 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1284 setTimeout(() => {
1285 if (commandPayload?.connectorId) {
08f130a0 1286 chargingStation.ocppRequestService
dc661702 1287 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1288 chargingStation,
dc661702
JB
1289 OCPP16RequestCommand.STATUS_NOTIFICATION,
1290 {
1291 connectorId: commandPayload.connectorId,
1292 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1293 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1294 },
1295 {
1296 triggerMessage: true,
1297 }
1298 )
1299 .catch(() => {
1300 /* This is intentional */
1301 });
1302 } else {
08f130a0
JB
1303 for (const connectorId of chargingStation.connectors.keys()) {
1304 chargingStation.ocppRequestService
dc661702
JB
1305 .requestHandler<
1306 OCPP16StatusNotificationRequest,
1307 OCPP16StatusNotificationResponse
1308 >(
08f130a0 1309 chargingStation,
dc661702
JB
1310 OCPP16RequestCommand.STATUS_NOTIFICATION,
1311 {
1312 connectorId,
1313 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1314 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1315 },
1316 {
1317 triggerMessage: true,
1318 }
1319 )
1320 .catch(() => {
1321 /* This is intentional */
1322 });
1323 }
1324 }
1325 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1326 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1327 default:
bf53cadf 1328 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1329 }
1330 } catch (error) {
e7aeea18 1331 return this.handleIncomingRequestError(
08f130a0 1332 chargingStation,
e7aeea18
JB
1333 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1334 error as Error,
bf53cadf 1335 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1336 );
802cfa13
JB
1337 }
1338 }
77b95a89
JB
1339
1340 private handleRequestDataTransfer(
1341 chargingStation: ChargingStation,
1342 commandPayload: OCPP16DataTransferRequest
1343 ): OCPP16DataTransferResponse {
1344 try {
1345 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1346 return {
1347 status: OCPP16DataTransferStatus.ACCEPTED,
1348 };
1349 }
1350 return {
1351 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1352 };
1353 } catch (error) {
1354 return this.handleIncomingRequestError(
1355 chargingStation,
1356 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1357 error as Error,
bf53cadf 1358 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1359 );
1360 }
1361 }
c0560973 1362}