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