Add initial support for OCPP 1.6 firmware update simulation
[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
a36bad10
JB
441 private handleRequestClearCache(chargingStation: ChargingStation): DefaultResponse {
442 chargingStation.authorizedTagsCache.deleteAuthorizedTags(
443 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
444 );
bf53cadf 445 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
c0560973
JB
446 }
447
e7aeea18 448 private async handleRequestUnlockConnector(
08f130a0 449 chargingStation: ChargingStation,
e7aeea18
JB
450 commandPayload: UnlockConnectorRequest
451 ): Promise<UnlockConnectorResponse> {
c0560973 452 const connectorId = commandPayload.connectorId;
c60ed4b8
JB
453 if (chargingStation.connectors.has(connectorId) === false) {
454 logger.error(
455 `${chargingStation.logPrefix()} Trying to unlock a non existing connector Id ${connectorId.toString()}`
456 );
bf53cadf 457 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 458 }
c0560973 459 if (connectorId === 0) {
e7aeea18 460 logger.error(
c60ed4b8 461 chargingStation.logPrefix() + ' Trying to unlock connector Id ' + connectorId.toString()
e7aeea18 462 );
bf53cadf 463 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c0560973 464 }
5e3cb728
JB
465 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
466 const stopResponse = await chargingStation.stopTransactionOnConnector(
467 connectorId,
468 OCPP16StopTransactionReason.UNLOCK_COMMAND
469 );
c0560973 470 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 471 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973 472 }
bf53cadf 473 return OCPPConstants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 474 }
08f130a0 475 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
476 OCPP16StatusNotificationRequest,
477 OCPP16StatusNotificationResponse
08f130a0 478 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
479 connectorId,
480 status: OCPP16ChargePointStatus.AVAILABLE,
481 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
482 });
08f130a0 483 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
bf53cadf 484 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
485 }
486
e7aeea18 487 private handleRequestGetConfiguration(
08f130a0 488 chargingStation: ChargingStation,
e7aeea18
JB
489 commandPayload: GetConfigurationRequest
490 ): GetConfigurationResponse {
c0560973
JB
491 const configurationKey: OCPPConfigurationKey[] = [];
492 const unknownKey: string[] = [];
a723e7e9 493 if (Utils.isEmptyArray(commandPayload.key) === true) {
08f130a0 494 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
a723e7e9 495 if (Utils.isUndefined(configuration.visible) === true) {
7f7b65ca 496 configuration.visible = true;
c0560973 497 }
da8629bb 498 if (configuration.visible === false) {
c0560973
JB
499 continue;
500 }
501 configurationKey.push({
7f7b65ca
JB
502 key: configuration.key,
503 readonly: configuration.readonly,
504 value: configuration.value,
c0560973
JB
505 });
506 }
507 } else {
508 for (const key of commandPayload.key) {
17ac262c
JB
509 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
510 chargingStation,
511 key
512 );
c0560973 513 if (keyFound) {
a723e7e9 514 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
515 keyFound.visible = true;
516 }
a723e7e9 517 if (keyFound.visible === false) {
c0560973
JB
518 continue;
519 }
520 configurationKey.push({
521 key: keyFound.key,
522 readonly: keyFound.readonly,
523 value: keyFound.value,
524 });
525 } else {
526 unknownKey.push(key);
527 }
528 }
529 }
530 return {
531 configurationKey,
532 unknownKey,
533 };
534 }
535
e7aeea18 536 private handleRequestChangeConfiguration(
08f130a0 537 chargingStation: ChargingStation,
e7aeea18
JB
538 commandPayload: ChangeConfigurationRequest
539 ): ChangeConfigurationResponse {
17ac262c
JB
540 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
541 chargingStation,
542 commandPayload.key,
543 true
544 );
c0560973 545 if (!keyToChange) {
bf53cadf 546 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973 547 } else if (keyToChange && keyToChange.readonly) {
bf53cadf 548 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
c0560973 549 } else if (keyToChange && !keyToChange.readonly) {
c0560973 550 let valueChanged = false;
a95873d8 551 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
552 ChargingStationConfigurationUtils.setConfigurationKeyValue(
553 chargingStation,
554 commandPayload.key,
555 commandPayload.value,
556 true
557 );
c0560973
JB
558 valueChanged = true;
559 }
560 let triggerHeartbeatRestart = false;
561 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
562 ChargingStationConfigurationUtils.setConfigurationKeyValue(
563 chargingStation,
e7aeea18
JB
564 OCPP16StandardParametersKey.HeartbeatInterval,
565 commandPayload.value
566 );
c0560973
JB
567 triggerHeartbeatRestart = true;
568 }
569 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
570 ChargingStationConfigurationUtils.setConfigurationKeyValue(
571 chargingStation,
e7aeea18
JB
572 OCPP16StandardParametersKey.HeartBeatInterval,
573 commandPayload.value
574 );
c0560973
JB
575 triggerHeartbeatRestart = true;
576 }
577 if (triggerHeartbeatRestart) {
08f130a0 578 chargingStation.restartHeartbeat();
c0560973
JB
579 }
580 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 581 chargingStation.restartWebSocketPing();
c0560973
JB
582 }
583 if (keyToChange.reboot) {
bf53cadf 584 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 585 }
bf53cadf 586 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973
JB
587 }
588 }
589
e7aeea18 590 private handleRequestSetChargingProfile(
08f130a0 591 chargingStation: ChargingStation,
e7aeea18
JB
592 commandPayload: SetChargingProfileRequest
593 ): SetChargingProfileResponse {
370ae4ee 594 if (
1789ba2c 595 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 596 chargingStation,
370ae4ee
JB
597 OCPP16SupportedFeatureProfiles.SmartCharging,
598 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
1789ba2c 599 ) === false
370ae4ee 600 ) {
bf53cadf 601 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 602 }
1789ba2c 603 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 604 logger.error(
08f130a0 605 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
606 commandPayload.connectorId
607 }`
608 );
bf53cadf 609 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 610 }
e7aeea18
JB
611 if (
612 commandPayload.csChargingProfiles.chargingProfilePurpose ===
613 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
614 commandPayload.connectorId !== 0
615 ) {
bf53cadf 616 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 617 }
e7aeea18
JB
618 if (
619 commandPayload.csChargingProfiles.chargingProfilePurpose ===
620 ChargingProfilePurposeType.TX_PROFILE &&
621 (commandPayload.connectorId === 0 ||
5e3cb728
JB
622 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
623 false)
e7aeea18 624 ) {
db0af086
JB
625 logger.error(
626 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${
627 commandPayload.connectorId
628 } without a started transaction`
629 );
bf53cadf 630 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 631 }
ed3d2808 632 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 633 chargingStation,
e7aeea18
JB
634 commandPayload.connectorId,
635 commandPayload.csChargingProfiles
636 );
637 logger.debug(
08f130a0 638 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
639 commandPayload.connectorId
640 }, dump their stack: %j`,
08f130a0 641 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 642 );
bf53cadf 643 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
644 }
645
e7aeea18 646 private handleRequestClearChargingProfile(
08f130a0 647 chargingStation: ChargingStation,
e7aeea18
JB
648 commandPayload: ClearChargingProfileRequest
649 ): ClearChargingProfileResponse {
370ae4ee 650 if (
a36bad10 651 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 652 chargingStation,
370ae4ee
JB
653 OCPP16SupportedFeatureProfiles.SmartCharging,
654 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
a36bad10 655 ) === false
370ae4ee 656 ) {
bf53cadf 657 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 658 }
1789ba2c 659 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 660 logger.error(
08f130a0 661 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
662 commandPayload.connectorId
663 }`
664 );
bf53cadf 665 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 666 }
1789ba2c 667 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16
JB
668 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
669 connectorStatus.chargingProfiles = [];
e7aeea18 670 logger.debug(
08f130a0 671 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
672 commandPayload.connectorId
673 }, dump their stack: %j`,
658e2d16 674 connectorStatus.chargingProfiles
e7aeea18 675 );
bf53cadf 676 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
677 }
678 if (!commandPayload.connectorId) {
679 let clearedCP = false;
08f130a0
JB
680 for (const connectorId of chargingStation.connectors.keys()) {
681 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
682 chargingStation
e7aeea18
JB
683 .getConnectorStatus(connectorId)
684 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
685 let clearCurrentCP = false;
686 if (chargingProfile.chargingProfileId === commandPayload.id) {
687 clearCurrentCP = true;
688 }
689 if (
690 !commandPayload.chargingProfilePurpose &&
691 chargingProfile.stackLevel === commandPayload.stackLevel
692 ) {
693 clearCurrentCP = true;
694 }
695 if (
696 !chargingProfile.stackLevel &&
697 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
698 ) {
699 clearCurrentCP = true;
700 }
701 if (
702 chargingProfile.stackLevel === commandPayload.stackLevel &&
703 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
704 ) {
705 clearCurrentCP = true;
706 }
707 if (clearCurrentCP) {
ccb1d6e9 708 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 709 logger.debug(
08f130a0 710 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
711 commandPayload.connectorId
712 }, dump their stack: %j`,
658e2d16 713 connectorStatus.chargingProfiles
e7aeea18
JB
714 );
715 clearedCP = true;
716 }
717 });
c0560973
JB
718 }
719 }
720 if (clearedCP) {
bf53cadf 721 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
722 }
723 }
bf53cadf 724 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
725 }
726
e7aeea18 727 private async handleRequestChangeAvailability(
08f130a0 728 chargingStation: ChargingStation,
e7aeea18
JB
729 commandPayload: ChangeAvailabilityRequest
730 ): Promise<ChangeAvailabilityResponse> {
c0560973 731 const connectorId: number = commandPayload.connectorId;
c60ed4b8 732 if (chargingStation.connectors.has(connectorId) === false) {
e7aeea18 733 logger.error(
08f130a0 734 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 735 );
bf53cadf 736 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 737 }
e7aeea18
JB
738 const chargePointStatus: OCPP16ChargePointStatus =
739 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
740 ? OCPP16ChargePointStatus.AVAILABLE
741 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 742 if (connectorId === 0) {
bf53cadf 743 let response: ChangeAvailabilityResponse = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 744 for (const id of chargingStation.connectors.keys()) {
5e3cb728 745 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
bf53cadf 746 response = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 747 }
08f130a0 748 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
bf53cadf 749 if (response === OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 750 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
751 OCPP16StatusNotificationRequest,
752 OCPP16StatusNotificationResponse
08f130a0 753 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
754 connectorId: id,
755 status: chargePointStatus,
756 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
757 });
08f130a0 758 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
759 }
760 }
761 return response;
e7aeea18
JB
762 } else if (
763 connectorId > 0 &&
56eb297e
JB
764 (chargingStation.isChargingStationAvailable() === true ||
765 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
766 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
767 ) {
5e3cb728 768 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 769 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
bf53cadf 770 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 771 }
08f130a0
JB
772 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
773 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
774 OCPP16StatusNotificationRequest,
775 OCPP16StatusNotificationResponse
08f130a0 776 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
777 connectorId,
778 status: chargePointStatus,
779 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
780 });
08f130a0 781 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
bf53cadf 782 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 783 }
bf53cadf 784 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
785 }
786
e7aeea18 787 private async handleRequestRemoteStartTransaction(
08f130a0 788 chargingStation: ChargingStation,
e7aeea18
JB
789 commandPayload: RemoteStartTransactionRequest
790 ): Promise<DefaultResponse> {
658e2d16 791 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 792 if (chargingStation.connectors.has(transactionConnectorId) === true) {
91a4f151
JB
793 const remoteStartTransactionLogMsg =
794 chargingStation.logPrefix() +
795 ' Transaction remotely STARTED on ' +
796 chargingStation.stationInfo.chargingStationId +
797 '#' +
798 transactionConnectorId.toString() +
799 " for idTag '" +
800 commandPayload.idTag +
801 "'";
08f130a0 802 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
803 OCPP16StatusNotificationRequest,
804 OCPP16StatusNotificationResponse
08f130a0 805 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
806 connectorId: transactionConnectorId,
807 status: OCPP16ChargePointStatus.PREPARING,
808 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
809 });
1789ba2c 810 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
658e2d16 811 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
1789ba2c 812 if (chargingStation.isChargingStationAvailable() === true) {
e060fe58 813 // Check if authorized
1789ba2c 814 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 815 let authorized = false;
e7aeea18 816 if (
1789ba2c
JB
817 chargingStation.getLocalAuthListEnabled() === true &&
818 chargingStation.hasAuthorizedTags() === true &&
9d7484a4
JB
819 chargingStation.authorizedTagsCache
820 .getAuthorizedTags(
821 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
822 )
1789ba2c 823 .find((idTag) => idTag === commandPayload.idTag)
e7aeea18 824 ) {
658e2d16
JB
825 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
826 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 827 authorized = true;
1789ba2c 828 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 829 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 830 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 831 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
832 OCPP16AuthorizeRequest,
833 OCPP16AuthorizeResponse
08f130a0 834 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
835 idTag: commandPayload.idTag,
836 });
a7fc8211
JB
837 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
838 authorized = true;
a7fc8211 839 }
71068fb9 840 } else {
e7aeea18 841 logger.warn(
08f130a0 842 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 843 );
a7fc8211 844 }
1789ba2c 845 if (authorized === true) {
a7fc8211 846 // Authorization successful, start transaction
e7aeea18
JB
847 if (
848 this.setRemoteStartTransactionChargingProfile(
08f130a0 849 chargingStation,
e7aeea18
JB
850 transactionConnectorId,
851 commandPayload.chargingProfile
1789ba2c 852 ) === true
e7aeea18 853 ) {
658e2d16 854 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
855 if (
856 (
08f130a0 857 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
858 OCPP16StartTransactionRequest,
859 OCPP16StartTransactionResponse
08f130a0 860 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
861 connectorId: transactionConnectorId,
862 idTag: commandPayload.idTag,
863 })
e7aeea18
JB
864 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
865 ) {
91a4f151 866 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 867 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 868 }
e7aeea18 869 return this.notifyRemoteStartTransactionRejected(
08f130a0 870 chargingStation,
e7aeea18
JB
871 transactionConnectorId,
872 commandPayload.idTag
873 );
e060fe58 874 }
e7aeea18 875 return this.notifyRemoteStartTransactionRejected(
08f130a0 876 chargingStation,
e7aeea18
JB
877 transactionConnectorId,
878 commandPayload.idTag
879 );
a7fc8211 880 }
e7aeea18 881 return this.notifyRemoteStartTransactionRejected(
08f130a0 882 chargingStation,
e7aeea18
JB
883 transactionConnectorId,
884 commandPayload.idTag
885 );
36f6a92e 886 }
a7fc8211 887 // No authorization check required, start transaction
e7aeea18
JB
888 if (
889 this.setRemoteStartTransactionChargingProfile(
08f130a0 890 chargingStation,
e7aeea18
JB
891 transactionConnectorId,
892 commandPayload.chargingProfile
1789ba2c 893 ) === true
e7aeea18 894 ) {
658e2d16 895 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
896 if (
897 (
08f130a0 898 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
899 OCPP16StartTransactionRequest,
900 OCPP16StartTransactionResponse
08f130a0 901 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
902 connectorId: transactionConnectorId,
903 idTag: commandPayload.idTag,
904 })
e7aeea18
JB
905 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
906 ) {
91a4f151 907 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 908 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 909 }
e7aeea18 910 return this.notifyRemoteStartTransactionRejected(
08f130a0 911 chargingStation,
e7aeea18
JB
912 transactionConnectorId,
913 commandPayload.idTag
914 );
e060fe58 915 }
e7aeea18 916 return this.notifyRemoteStartTransactionRejected(
08f130a0 917 chargingStation,
e7aeea18
JB
918 transactionConnectorId,
919 commandPayload.idTag
920 );
c0560973 921 }
e7aeea18 922 return this.notifyRemoteStartTransactionRejected(
08f130a0 923 chargingStation,
e7aeea18
JB
924 transactionConnectorId,
925 commandPayload.idTag
926 );
c0560973 927 }
08f130a0
JB
928 return this.notifyRemoteStartTransactionRejected(
929 chargingStation,
930 transactionConnectorId,
931 commandPayload.idTag
932 );
a7fc8211
JB
933 }
934
e7aeea18 935 private async notifyRemoteStartTransactionRejected(
08f130a0 936 chargingStation: ChargingStation,
e7aeea18
JB
937 connectorId: number,
938 idTag: string
939 ): Promise<DefaultResponse> {
940 if (
08f130a0 941 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 942 ) {
08f130a0 943 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
944 OCPP16StatusNotificationRequest,
945 OCPP16StatusNotificationResponse
08f130a0 946 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
947 connectorId,
948 status: OCPP16ChargePointStatus.AVAILABLE,
949 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
950 });
08f130a0 951 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 952 }
e7aeea18 953 logger.warn(
08f130a0 954 chargingStation.logPrefix() +
e7aeea18
JB
955 ' Remote starting transaction REJECTED on connector Id ' +
956 connectorId.toString() +
91a4f151 957 ", idTag '" +
e7aeea18 958 idTag +
91a4f151 959 "', availability '" +
08f130a0 960 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
961 "', status '" +
962 chargingStation.getConnectorStatus(connectorId).status +
963 "'"
e7aeea18 964 );
bf53cadf 965 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
966 }
967
e7aeea18 968 private setRemoteStartTransactionChargingProfile(
08f130a0 969 chargingStation: ChargingStation,
e7aeea18
JB
970 connectorId: number,
971 cp: OCPP16ChargingProfile
972 ): boolean {
a7fc8211 973 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 974 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 975 logger.debug(
08f130a0
JB
976 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
977 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 978 );
a7fc8211
JB
979 return true;
980 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 981 logger.warn(
08f130a0 982 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
983 cp.chargingProfilePurpose
984 } charging profile(s) at remote start transaction`
985 );
a7fc8211 986 return false;
e060fe58
JB
987 } else if (!cp) {
988 return true;
a7fc8211
JB
989 }
990 }
991
e7aeea18 992 private async handleRequestRemoteStopTransaction(
08f130a0 993 chargingStation: ChargingStation,
e7aeea18
JB
994 commandPayload: RemoteStopTransactionRequest
995 ): Promise<DefaultResponse> {
c0560973 996 const transactionId = commandPayload.transactionId;
08f130a0 997 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
998 if (
999 connectorId > 0 &&
08f130a0 1000 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 1001 ) {
08f130a0 1002 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1003 OCPP16StatusNotificationRequest,
1004 OCPP16StatusNotificationResponse
08f130a0 1005 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
1006 connectorId,
1007 status: OCPP16ChargePointStatus.FINISHING,
1008 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1009 });
08f130a0 1010 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
1011 const stopResponse = await chargingStation.stopTransactionOnConnector(
1012 connectorId,
a65319ba 1013 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
1014 );
1015 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 1016 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 1017 }
bf53cadf 1018 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
1019 }
1020 }
44b9b577 1021 logger.warn(
08f130a0 1022 chargingStation.logPrefix() +
e7aeea18
JB
1023 ' Trying to remote stop a non existing transaction ' +
1024 transactionId.toString()
1025 );
bf53cadf 1026 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973 1027 }
47e22477 1028
b03df580
JB
1029 private handleRequestUpdateFirmware(
1030 chargingStation: ChargingStation,
1031 commandPayload: OCPP16UpdateFirmwareRequest
1032 ): OCPP16UpdateFirmwareResponse {
1033 if (
1034 OCPP16ServiceUtils.checkFeatureProfile(
1035 chargingStation,
1036 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1037 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1038 ) === false
1039 ) {
bf53cadf 1040 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580 1041 }
c9a4f9ea
JB
1042 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
1043 if (retrieveDate.getTime() <= Date.now()) {
1044 this.asyncResource
1045 .runInAsyncScope(
1046 this.updateFirmware.bind(this) as (
1047 this: OCPP16IncomingRequestService,
1048 ...args: any[]
1049 ) => Promise<void>,
1050 this,
1051 chargingStation
1052 )
1053 .catch(() => {
1054 /* This is intentional */
1055 });
1056 } else {
1057 setTimeout(() => {
1058 this.updateFirmware(chargingStation).catch(() => {
1059 /* Intentional */
1060 });
1061 }, retrieveDate.getTime() - Date.now());
1062 }
1063 return OCPPConstants.OCPP_RESPONSE_EMPTY;
1064 }
1065
1066 private async updateFirmware(
1067 chargingStation: ChargingStation,
1068 minDelay = 15,
1069 maxDelay = 30
1070 ): Promise<void> {
1071 chargingStation.stopAutomaticTransactionGenerator();
1072 for (const connectorId of chargingStation.connectors.keys()) {
1073 if (
1074 connectorId > 0 &&
1075 chargingStation.getConnectorStatus(connectorId).transactionStarted === false
1076 ) {
1077 await chargingStation.ocppRequestService.requestHandler<
1078 OCPP16StatusNotificationRequest,
1079 OCPP16StatusNotificationResponse
1080 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
1081 connectorId,
1082 status: OCPP16ChargePointStatus.UNAVAILABLE,
1083 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1084 });
1085 }
1086 }
1087 await chargingStation.ocppRequestService.requestHandler<
1088 OCPP16FirmwareStatusNotificationRequest,
1089 OCPP16FirmwareStatusNotificationResponse
1090 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1091 status: OCPP16FirmwareStatus.Downloading,
1092 });
1093 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1094 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1095 await chargingStation.ocppRequestService.requestHandler<
1096 OCPP16FirmwareStatusNotificationRequest,
1097 OCPP16FirmwareStatusNotificationResponse
1098 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1099 status: OCPP16FirmwareStatus.Downloaded,
1100 });
1101 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1102 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1103 await chargingStation.ocppRequestService.requestHandler<
1104 OCPP16FirmwareStatusNotificationRequest,
1105 OCPP16FirmwareStatusNotificationResponse
1106 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1107 status: OCPP16FirmwareStatus.Installing,
1108 });
1109 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1110 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1111 await chargingStation.reset();
b03df580
JB
1112 }
1113
e7aeea18 1114 private async handleRequestGetDiagnostics(
08f130a0 1115 chargingStation: ChargingStation,
e7aeea18
JB
1116 commandPayload: GetDiagnosticsRequest
1117 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1118 if (
1789ba2c 1119 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1120 chargingStation,
370ae4ee
JB
1121 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1122 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1123 ) === false
68cb8b91 1124 ) {
bf53cadf 1125 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1126 }
a3868ec4 1127 const uri = new URL(commandPayload.location);
47e22477
JB
1128 if (uri.protocol.startsWith('ftp:')) {
1129 let ftpClient: Client;
1130 try {
e7aeea18 1131 const logFiles = fs
0d8140bd 1132 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1133 .filter((file) => file.endsWith('.log'))
1134 .map((file) => path.join('./', file));
08f130a0 1135 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1136 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1137 ftpClient = new Client();
1138 const accessResponse = await ftpClient.access({
1139 host: uri.host,
e8191622
JB
1140 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1141 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1142 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1143 });
1144 let uploadResponse: FTPResponse;
1145 if (accessResponse.code === 220) {
1146 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1147 ftpClient.trackProgress(async (info) => {
e7aeea18 1148 logger.info(
08f130a0 1149 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1150 info.bytes / 1024
1151 } bytes transferred from diagnostics archive ${info.name}`
1152 );
08f130a0 1153 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1154 OCPP16DiagnosticsStatusNotificationRequest,
1155 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1156 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1157 status: OCPP16DiagnosticsStatus.Uploading,
1158 });
47e22477 1159 });
e7aeea18 1160 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1161 path.join(
1162 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1163 diagnosticsArchive
1164 ),
e7aeea18
JB
1165 uri.pathname + diagnosticsArchive
1166 );
47e22477 1167 if (uploadResponse.code === 226) {
08f130a0 1168 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1169 OCPP16DiagnosticsStatusNotificationRequest,
1170 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1171 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1172 status: OCPP16DiagnosticsStatus.Uploaded,
1173 });
47e22477
JB
1174 if (ftpClient) {
1175 ftpClient.close();
1176 }
1177 return { fileName: diagnosticsArchive };
1178 }
e7aeea18
JB
1179 throw new OCPPError(
1180 ErrorType.GENERIC_ERROR,
1181 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1182 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1183 }`,
1184 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1185 );
47e22477 1186 }
e7aeea18
JB
1187 throw new OCPPError(
1188 ErrorType.GENERIC_ERROR,
1189 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1190 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1191 }`,
1192 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1193 );
47e22477 1194 } catch (error) {
08f130a0 1195 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1196 OCPP16DiagnosticsStatusNotificationRequest,
1197 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1198 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1199 status: OCPP16DiagnosticsStatus.UploadFailed,
1200 });
47e22477
JB
1201 if (ftpClient) {
1202 ftpClient.close();
1203 }
e7aeea18 1204 return this.handleIncomingRequestError(
08f130a0 1205 chargingStation,
e7aeea18
JB
1206 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1207 error as Error,
bf53cadf 1208 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1209 );
47e22477
JB
1210 }
1211 } else {
e7aeea18 1212 logger.error(
08f130a0 1213 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1214 uri.protocol
1215 } to transfer the diagnostic logs archive`
1216 );
08f130a0 1217 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1218 OCPP16DiagnosticsStatusNotificationRequest,
1219 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1220 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1221 status: OCPP16DiagnosticsStatus.UploadFailed,
1222 });
bf53cadf 1223 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1224 }
1225 }
802cfa13 1226
e7aeea18 1227 private handleRequestTriggerMessage(
08f130a0 1228 chargingStation: ChargingStation,
e7aeea18
JB
1229 commandPayload: OCPP16TriggerMessageRequest
1230 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1231 if (
1232 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1233 chargingStation,
370ae4ee
JB
1234 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1235 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1236 ) ||
1237 !OCPP16ServiceUtils.isMessageTriggerSupported(
1238 chargingStation,
1239 commandPayload.requestedMessage
370ae4ee
JB
1240 )
1241 ) {
bf53cadf 1242 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1243 }
c60ed4b8 1244 if (
4caa7e67 1245 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1246 chargingStation,
1247 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1248 commandPayload.connectorId
1249 )
1250 ) {
bf53cadf 1251 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1252 }
802cfa13
JB
1253 try {
1254 switch (commandPayload.requestedMessage) {
c60ed4b8 1255 case OCPP16MessageTrigger.BootNotification:
802cfa13 1256 setTimeout(() => {
08f130a0 1257 chargingStation.ocppRequestService
f7f98c68 1258 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1259 chargingStation,
6a8b180d 1260 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1261 chargingStation.bootNotificationRequest,
6a8b180d 1262 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1263 )
8bfbc743
JB
1264 .then((response) => {
1265 chargingStation.bootNotificationResponse = response;
ae711c83 1266 })
e7aeea18
JB
1267 .catch(() => {
1268 /* This is intentional */
1269 });
802cfa13 1270 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1271 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1272 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1273 setTimeout(() => {
08f130a0 1274 chargingStation.ocppRequestService
f7f98c68 1275 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1276 chargingStation,
ef6fa3fb
JB
1277 OCPP16RequestCommand.HEARTBEAT,
1278 null,
1279 {
1280 triggerMessage: true,
1281 }
1282 )
e7aeea18
JB
1283 .catch(() => {
1284 /* This is intentional */
1285 });
802cfa13 1286 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1287 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1288 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1289 setTimeout(() => {
1290 if (commandPayload?.connectorId) {
08f130a0 1291 chargingStation.ocppRequestService
dc661702 1292 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1293 chargingStation,
dc661702
JB
1294 OCPP16RequestCommand.STATUS_NOTIFICATION,
1295 {
1296 connectorId: commandPayload.connectorId,
1297 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1298 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1299 },
1300 {
1301 triggerMessage: true,
1302 }
1303 )
1304 .catch(() => {
1305 /* This is intentional */
1306 });
1307 } else {
08f130a0
JB
1308 for (const connectorId of chargingStation.connectors.keys()) {
1309 chargingStation.ocppRequestService
dc661702
JB
1310 .requestHandler<
1311 OCPP16StatusNotificationRequest,
1312 OCPP16StatusNotificationResponse
1313 >(
08f130a0 1314 chargingStation,
dc661702
JB
1315 OCPP16RequestCommand.STATUS_NOTIFICATION,
1316 {
1317 connectorId,
1318 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1319 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1320 },
1321 {
1322 triggerMessage: true,
1323 }
1324 )
1325 .catch(() => {
1326 /* This is intentional */
1327 });
1328 }
1329 }
1330 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1331 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1332 default:
bf53cadf 1333 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1334 }
1335 } catch (error) {
e7aeea18 1336 return this.handleIncomingRequestError(
08f130a0 1337 chargingStation,
e7aeea18
JB
1338 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1339 error as Error,
bf53cadf 1340 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1341 );
802cfa13
JB
1342 }
1343 }
77b95a89
JB
1344
1345 private handleRequestDataTransfer(
1346 chargingStation: ChargingStation,
1347 commandPayload: OCPP16DataTransferRequest
1348 ): OCPP16DataTransferResponse {
1349 try {
1350 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1351 return {
1352 status: OCPP16DataTransferStatus.ACCEPTED,
1353 };
1354 }
1355 return {
1356 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1357 };
1358 } catch (error) {
1359 return this.handleIncomingRequestError(
1360 chargingStation,
1361 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1362 error as Error,
bf53cadf 1363 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1364 );
1365 }
1366 }
c0560973 1367}