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