Improve charging profiles handling debug logs
[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,
340 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
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(
c60ed4b8 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);
658e2d16
JB
575 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
576 connectorStatus.chargingProfiles = [];
e7aeea18 577 logger.debug(
08f130a0 578 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7 579 commandPayload.connectorId
ad67a158 580 }`
e7aeea18 581 );
bf53cadf 582 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
583 }
584 if (!commandPayload.connectorId) {
585 let clearedCP = false;
08f130a0
JB
586 for (const connectorId of chargingStation.connectors.keys()) {
587 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
588 chargingStation
e7aeea18
JB
589 .getConnectorStatus(connectorId)
590 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
591 let clearCurrentCP = false;
592 if (chargingProfile.chargingProfileId === commandPayload.id) {
593 clearCurrentCP = true;
594 }
595 if (
596 !commandPayload.chargingProfilePurpose &&
597 chargingProfile.stackLevel === commandPayload.stackLevel
598 ) {
599 clearCurrentCP = true;
600 }
601 if (
602 !chargingProfile.stackLevel &&
603 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
604 ) {
605 clearCurrentCP = true;
606 }
607 if (
608 chargingProfile.stackLevel === commandPayload.stackLevel &&
609 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
610 ) {
611 clearCurrentCP = true;
612 }
613 if (clearCurrentCP) {
ccb1d6e9 614 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 615 logger.debug(
ad67a158
JB
616 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
617 chargingProfile
e7aeea18
JB
618 );
619 clearedCP = true;
620 }
621 });
c0560973
JB
622 }
623 }
624 if (clearedCP) {
bf53cadf 625 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
626 }
627 }
bf53cadf 628 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
629 }
630
e7aeea18 631 private async handleRequestChangeAvailability(
08f130a0 632 chargingStation: ChargingStation,
e7aeea18
JB
633 commandPayload: ChangeAvailabilityRequest
634 ): Promise<ChangeAvailabilityResponse> {
c0560973 635 const connectorId: number = commandPayload.connectorId;
c60ed4b8 636 if (chargingStation.connectors.has(connectorId) === false) {
e7aeea18 637 logger.error(
08f130a0 638 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 639 );
bf53cadf 640 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 641 }
e7aeea18
JB
642 const chargePointStatus: OCPP16ChargePointStatus =
643 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
644 ? OCPP16ChargePointStatus.AVAILABLE
645 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 646 if (connectorId === 0) {
bf53cadf 647 let response: ChangeAvailabilityResponse = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 648 for (const id of chargingStation.connectors.keys()) {
5e3cb728 649 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
bf53cadf 650 response = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 651 }
08f130a0 652 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
bf53cadf 653 if (response === OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 654 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
655 OCPP16StatusNotificationRequest,
656 OCPP16StatusNotificationResponse
08f130a0 657 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
658 connectorId: id,
659 status: chargePointStatus,
660 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
661 });
08f130a0 662 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
663 }
664 }
665 return response;
e7aeea18
JB
666 } else if (
667 connectorId > 0 &&
56eb297e
JB
668 (chargingStation.isChargingStationAvailable() === true ||
669 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
670 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
671 ) {
5e3cb728 672 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 673 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
bf53cadf 674 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 675 }
08f130a0
JB
676 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
677 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
678 OCPP16StatusNotificationRequest,
679 OCPP16StatusNotificationResponse
08f130a0 680 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
681 connectorId,
682 status: chargePointStatus,
683 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
684 });
08f130a0 685 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
bf53cadf 686 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 687 }
bf53cadf 688 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
689 }
690
e7aeea18 691 private async handleRequestRemoteStartTransaction(
08f130a0 692 chargingStation: ChargingStation,
e7aeea18 693 commandPayload: RemoteStartTransactionRequest
f03e1042 694 ): Promise<GenericResponse> {
658e2d16 695 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 696 if (chargingStation.connectors.has(transactionConnectorId) === true) {
91a4f151
JB
697 const remoteStartTransactionLogMsg =
698 chargingStation.logPrefix() +
699 ' Transaction remotely STARTED on ' +
700 chargingStation.stationInfo.chargingStationId +
701 '#' +
702 transactionConnectorId.toString() +
703 " for idTag '" +
704 commandPayload.idTag +
705 "'";
08f130a0 706 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
707 OCPP16StatusNotificationRequest,
708 OCPP16StatusNotificationResponse
08f130a0 709 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
710 connectorId: transactionConnectorId,
711 status: OCPP16ChargePointStatus.PREPARING,
712 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
713 });
1789ba2c 714 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
658e2d16 715 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
1789ba2c 716 if (chargingStation.isChargingStationAvailable() === true) {
e060fe58 717 // Check if authorized
1789ba2c 718 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 719 let authorized = false;
e7aeea18 720 if (
1789ba2c
JB
721 chargingStation.getLocalAuthListEnabled() === true &&
722 chargingStation.hasAuthorizedTags() === true &&
9d7484a4
JB
723 chargingStation.authorizedTagsCache
724 .getAuthorizedTags(
725 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
726 )
1789ba2c 727 .find((idTag) => idTag === commandPayload.idTag)
e7aeea18 728 ) {
658e2d16
JB
729 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
730 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 731 authorized = true;
1789ba2c 732 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 733 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 734 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 735 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
736 OCPP16AuthorizeRequest,
737 OCPP16AuthorizeResponse
08f130a0 738 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
739 idTag: commandPayload.idTag,
740 });
a7fc8211
JB
741 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
742 authorized = true;
a7fc8211 743 }
71068fb9 744 } else {
e7aeea18 745 logger.warn(
08f130a0 746 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 747 );
a7fc8211 748 }
1789ba2c 749 if (authorized === true) {
a7fc8211 750 // Authorization successful, start transaction
e7aeea18
JB
751 if (
752 this.setRemoteStartTransactionChargingProfile(
08f130a0 753 chargingStation,
e7aeea18
JB
754 transactionConnectorId,
755 commandPayload.chargingProfile
1789ba2c 756 ) === true
e7aeea18 757 ) {
658e2d16 758 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
759 if (
760 (
08f130a0 761 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
762 OCPP16StartTransactionRequest,
763 OCPP16StartTransactionResponse
08f130a0 764 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
765 connectorId: transactionConnectorId,
766 idTag: commandPayload.idTag,
767 })
e7aeea18
JB
768 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
769 ) {
91a4f151 770 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 771 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 772 }
e7aeea18 773 return this.notifyRemoteStartTransactionRejected(
08f130a0 774 chargingStation,
e7aeea18
JB
775 transactionConnectorId,
776 commandPayload.idTag
777 );
e060fe58 778 }
e7aeea18 779 return this.notifyRemoteStartTransactionRejected(
08f130a0 780 chargingStation,
e7aeea18
JB
781 transactionConnectorId,
782 commandPayload.idTag
783 );
a7fc8211 784 }
e7aeea18 785 return this.notifyRemoteStartTransactionRejected(
08f130a0 786 chargingStation,
e7aeea18
JB
787 transactionConnectorId,
788 commandPayload.idTag
789 );
36f6a92e 790 }
a7fc8211 791 // No authorization check required, start transaction
e7aeea18
JB
792 if (
793 this.setRemoteStartTransactionChargingProfile(
08f130a0 794 chargingStation,
e7aeea18
JB
795 transactionConnectorId,
796 commandPayload.chargingProfile
1789ba2c 797 ) === true
e7aeea18 798 ) {
658e2d16 799 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
800 if (
801 (
08f130a0 802 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
803 OCPP16StartTransactionRequest,
804 OCPP16StartTransactionResponse
08f130a0 805 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
806 connectorId: transactionConnectorId,
807 idTag: commandPayload.idTag,
808 })
e7aeea18
JB
809 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
810 ) {
91a4f151 811 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 812 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 813 }
e7aeea18 814 return this.notifyRemoteStartTransactionRejected(
08f130a0 815 chargingStation,
e7aeea18
JB
816 transactionConnectorId,
817 commandPayload.idTag
818 );
e060fe58 819 }
e7aeea18 820 return this.notifyRemoteStartTransactionRejected(
08f130a0 821 chargingStation,
e7aeea18
JB
822 transactionConnectorId,
823 commandPayload.idTag
824 );
c0560973 825 }
e7aeea18 826 return this.notifyRemoteStartTransactionRejected(
08f130a0 827 chargingStation,
e7aeea18
JB
828 transactionConnectorId,
829 commandPayload.idTag
830 );
c0560973 831 }
08f130a0
JB
832 return this.notifyRemoteStartTransactionRejected(
833 chargingStation,
834 transactionConnectorId,
835 commandPayload.idTag
836 );
a7fc8211
JB
837 }
838
e7aeea18 839 private async notifyRemoteStartTransactionRejected(
08f130a0 840 chargingStation: ChargingStation,
e7aeea18
JB
841 connectorId: number,
842 idTag: string
f03e1042 843 ): Promise<GenericResponse> {
e7aeea18 844 if (
08f130a0 845 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 846 ) {
08f130a0 847 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
848 OCPP16StatusNotificationRequest,
849 OCPP16StatusNotificationResponse
08f130a0 850 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
851 connectorId,
852 status: OCPP16ChargePointStatus.AVAILABLE,
853 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
854 });
08f130a0 855 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 856 }
e7aeea18 857 logger.warn(
08f130a0 858 chargingStation.logPrefix() +
e7aeea18
JB
859 ' Remote starting transaction REJECTED on connector Id ' +
860 connectorId.toString() +
91a4f151 861 ", idTag '" +
e7aeea18 862 idTag +
91a4f151 863 "', availability '" +
08f130a0 864 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
865 "', status '" +
866 chargingStation.getConnectorStatus(connectorId).status +
867 "'"
e7aeea18 868 );
bf53cadf 869 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
870 }
871
e7aeea18 872 private setRemoteStartTransactionChargingProfile(
08f130a0 873 chargingStation: ChargingStation,
e7aeea18
JB
874 connectorId: number,
875 cp: OCPP16ChargingProfile
876 ): boolean {
a7fc8211 877 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 878 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 879 logger.debug(
ad67a158
JB
880 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
881 cp
e7aeea18 882 );
a7fc8211
JB
883 return true;
884 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 885 logger.warn(
08f130a0 886 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
887 cp.chargingProfilePurpose
888 } charging profile(s) at remote start transaction`
889 );
a7fc8211 890 return false;
e060fe58
JB
891 } else if (!cp) {
892 return true;
a7fc8211
JB
893 }
894 }
895
e7aeea18 896 private async handleRequestRemoteStopTransaction(
08f130a0 897 chargingStation: ChargingStation,
e7aeea18 898 commandPayload: RemoteStopTransactionRequest
f03e1042 899 ): Promise<GenericResponse> {
c0560973 900 const transactionId = commandPayload.transactionId;
08f130a0 901 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
902 if (
903 connectorId > 0 &&
08f130a0 904 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 905 ) {
08f130a0 906 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
907 OCPP16StatusNotificationRequest,
908 OCPP16StatusNotificationResponse
08f130a0 909 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
910 connectorId,
911 status: OCPP16ChargePointStatus.FINISHING,
912 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
913 });
08f130a0 914 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
915 const stopResponse = await chargingStation.stopTransactionOnConnector(
916 connectorId,
a65319ba 917 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
918 );
919 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 920 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 921 }
bf53cadf 922 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
923 }
924 }
44b9b577 925 logger.warn(
08f130a0 926 chargingStation.logPrefix() +
e7aeea18
JB
927 ' Trying to remote stop a non existing transaction ' +
928 transactionId.toString()
929 );
bf53cadf 930 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973 931 }
47e22477 932
b03df580
JB
933 private handleRequestUpdateFirmware(
934 chargingStation: ChargingStation,
935 commandPayload: OCPP16UpdateFirmwareRequest
936 ): OCPP16UpdateFirmwareResponse {
937 if (
938 OCPP16ServiceUtils.checkFeatureProfile(
939 chargingStation,
940 OCPP16SupportedFeatureProfiles.FirmwareManagement,
941 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
942 ) === false
943 ) {
5d280aae 944 logger.warn(
90293abb 945 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
5d280aae
JB
946 );
947 return OCPPConstants.OCPP_RESPONSE_EMPTY;
948 }
949 if (
950 !Utils.isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
951 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
952 ) {
953 logger.warn(
90293abb 954 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
5d280aae 955 );
bf53cadf 956 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580 957 }
c9a4f9ea
JB
958 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
959 if (retrieveDate.getTime() <= Date.now()) {
960 this.asyncResource
961 .runInAsyncScope(
962 this.updateFirmware.bind(this) as (
963 this: OCPP16IncomingRequestService,
964 ...args: any[]
965 ) => Promise<void>,
966 this,
967 chargingStation
968 )
969 .catch(() => {
970 /* This is intentional */
971 });
972 } else {
973 setTimeout(() => {
974 this.updateFirmware(chargingStation).catch(() => {
975 /* Intentional */
976 });
977 }, retrieveDate.getTime() - Date.now());
978 }
979 return OCPPConstants.OCPP_RESPONSE_EMPTY;
980 }
981
982 private async updateFirmware(
983 chargingStation: ChargingStation,
90293abb
JB
984 maxDelay = 30,
985 minDelay = 15
c9a4f9ea
JB
986 ): Promise<void> {
987 chargingStation.stopAutomaticTransactionGenerator();
988 for (const connectorId of chargingStation.connectors.keys()) {
989 if (
990 connectorId > 0 &&
991 chargingStation.getConnectorStatus(connectorId).transactionStarted === false
992 ) {
993 await chargingStation.ocppRequestService.requestHandler<
994 OCPP16StatusNotificationRequest,
995 OCPP16StatusNotificationResponse
996 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
997 connectorId,
998 status: OCPP16ChargePointStatus.UNAVAILABLE,
999 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1000 });
3637ca2c
JB
1001 chargingStation.getConnectorStatus(connectorId).status =
1002 OCPP16ChargePointStatus.UNAVAILABLE;
c9a4f9ea
JB
1003 }
1004 }
5d280aae 1005 if (
15748260
JB
1006 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus &&
1007 !Utils.isEmptyString(chargingStation.stationInfo?.firmwareUpgrade?.failureStatus)
5d280aae
JB
1008 ) {
1009 await chargingStation.ocppRequestService.requestHandler<
1010 OCPP16FirmwareStatusNotificationRequest,
1011 OCPP16FirmwareStatusNotificationResponse
1012 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1013 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae
JB
1014 });
1015 return;
1016 }
c9a4f9ea
JB
1017 await chargingStation.ocppRequestService.requestHandler<
1018 OCPP16FirmwareStatusNotificationRequest,
1019 OCPP16FirmwareStatusNotificationResponse
1020 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1021 status: OCPP16FirmwareStatus.Downloading,
1022 });
1023 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
90293abb 1024 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1025 await chargingStation.ocppRequestService.requestHandler<
1026 OCPP16FirmwareStatusNotificationRequest,
1027 OCPP16FirmwareStatusNotificationResponse
1028 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1029 status: OCPP16FirmwareStatus.Downloaded,
1030 });
1031 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
90293abb 1032 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1033 await chargingStation.ocppRequestService.requestHandler<
1034 OCPP16FirmwareStatusNotificationRequest,
1035 OCPP16FirmwareStatusNotificationResponse
1036 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1037 status: OCPP16FirmwareStatus.Installing,
1038 });
1039 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
15748260 1040 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
90293abb 1041 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1042 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1043 }
b03df580
JB
1044 }
1045
e7aeea18 1046 private async handleRequestGetDiagnostics(
08f130a0 1047 chargingStation: ChargingStation,
e7aeea18
JB
1048 commandPayload: GetDiagnosticsRequest
1049 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1050 if (
1789ba2c 1051 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1052 chargingStation,
370ae4ee
JB
1053 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1054 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1055 ) === false
68cb8b91 1056 ) {
90293abb
JB
1057 logger.warn(
1058 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1059 );
bf53cadf 1060 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1061 }
a3868ec4 1062 const uri = new URL(commandPayload.location);
47e22477
JB
1063 if (uri.protocol.startsWith('ftp:')) {
1064 let ftpClient: Client;
1065 try {
e7aeea18 1066 const logFiles = fs
0d8140bd 1067 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1068 .filter((file) => file.endsWith('.log'))
1069 .map((file) => path.join('./', file));
08f130a0 1070 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1071 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1072 ftpClient = new Client();
1073 const accessResponse = await ftpClient.access({
1074 host: uri.host,
e8191622
JB
1075 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1076 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1077 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1078 });
1079 let uploadResponse: FTPResponse;
1080 if (accessResponse.code === 220) {
6a8329b4 1081 ftpClient.trackProgress((info) => {
e7aeea18 1082 logger.info(
08f130a0 1083 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1084 info.bytes / 1024
1085 } bytes transferred from diagnostics archive ${info.name}`
1086 );
6a8329b4
JB
1087 chargingStation.ocppRequestService
1088 .requestHandler<
1089 OCPP16DiagnosticsStatusNotificationRequest,
1090 OCPP16DiagnosticsStatusNotificationResponse
1091 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1092 status: OCPP16DiagnosticsStatus.Uploading,
1093 })
1094 .catch((error) => {
1095 logger.error(
1096 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1097 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1098 }'`,
1099 error
1100 );
1101 });
47e22477 1102 });
e7aeea18 1103 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1104 path.join(
1105 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1106 diagnosticsArchive
1107 ),
e7aeea18
JB
1108 uri.pathname + diagnosticsArchive
1109 );
47e22477 1110 if (uploadResponse.code === 226) {
08f130a0 1111 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1112 OCPP16DiagnosticsStatusNotificationRequest,
1113 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1114 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1115 status: OCPP16DiagnosticsStatus.Uploaded,
1116 });
47e22477
JB
1117 if (ftpClient) {
1118 ftpClient.close();
1119 }
1120 return { fileName: diagnosticsArchive };
1121 }
e7aeea18
JB
1122 throw new OCPPError(
1123 ErrorType.GENERIC_ERROR,
1124 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1125 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1126 }`,
1127 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1128 );
47e22477 1129 }
e7aeea18
JB
1130 throw new OCPPError(
1131 ErrorType.GENERIC_ERROR,
1132 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1133 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1134 }`,
1135 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1136 );
47e22477 1137 } catch (error) {
08f130a0 1138 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1139 OCPP16DiagnosticsStatusNotificationRequest,
1140 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1141 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1142 status: OCPP16DiagnosticsStatus.UploadFailed,
1143 });
47e22477
JB
1144 if (ftpClient) {
1145 ftpClient.close();
1146 }
e7aeea18 1147 return this.handleIncomingRequestError(
08f130a0 1148 chargingStation,
e7aeea18
JB
1149 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1150 error as Error,
bf53cadf 1151 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1152 );
47e22477
JB
1153 }
1154 } else {
e7aeea18 1155 logger.error(
08f130a0 1156 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1157 uri.protocol
1158 } to transfer the diagnostic logs archive`
1159 );
08f130a0 1160 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1161 OCPP16DiagnosticsStatusNotificationRequest,
1162 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1163 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1164 status: OCPP16DiagnosticsStatus.UploadFailed,
1165 });
bf53cadf 1166 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1167 }
1168 }
802cfa13 1169
e7aeea18 1170 private handleRequestTriggerMessage(
08f130a0 1171 chargingStation: ChargingStation,
e7aeea18
JB
1172 commandPayload: OCPP16TriggerMessageRequest
1173 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1174 if (
1175 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1176 chargingStation,
370ae4ee
JB
1177 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1178 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1179 ) ||
1180 !OCPP16ServiceUtils.isMessageTriggerSupported(
1181 chargingStation,
1182 commandPayload.requestedMessage
370ae4ee
JB
1183 )
1184 ) {
bf53cadf 1185 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1186 }
c60ed4b8 1187 if (
4caa7e67 1188 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1189 chargingStation,
1190 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1191 commandPayload.connectorId
1192 )
1193 ) {
bf53cadf 1194 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1195 }
802cfa13
JB
1196 try {
1197 switch (commandPayload.requestedMessage) {
c60ed4b8 1198 case OCPP16MessageTrigger.BootNotification:
802cfa13 1199 setTimeout(() => {
08f130a0 1200 chargingStation.ocppRequestService
f7f98c68 1201 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1202 chargingStation,
6a8b180d 1203 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1204 chargingStation.bootNotificationRequest,
6a8b180d 1205 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1206 )
8bfbc743
JB
1207 .then((response) => {
1208 chargingStation.bootNotificationResponse = response;
ae711c83 1209 })
e7aeea18
JB
1210 .catch(() => {
1211 /* This is intentional */
1212 });
802cfa13 1213 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1214 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1215 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1216 setTimeout(() => {
08f130a0 1217 chargingStation.ocppRequestService
f7f98c68 1218 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1219 chargingStation,
ef6fa3fb
JB
1220 OCPP16RequestCommand.HEARTBEAT,
1221 null,
1222 {
1223 triggerMessage: true,
1224 }
1225 )
e7aeea18
JB
1226 .catch(() => {
1227 /* This is intentional */
1228 });
802cfa13 1229 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1230 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1231 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1232 setTimeout(() => {
1233 if (commandPayload?.connectorId) {
08f130a0 1234 chargingStation.ocppRequestService
dc661702 1235 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1236 chargingStation,
dc661702
JB
1237 OCPP16RequestCommand.STATUS_NOTIFICATION,
1238 {
1239 connectorId: commandPayload.connectorId,
1240 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1241 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1242 },
1243 {
1244 triggerMessage: true,
1245 }
1246 )
1247 .catch(() => {
1248 /* This is intentional */
1249 });
1250 } else {
08f130a0
JB
1251 for (const connectorId of chargingStation.connectors.keys()) {
1252 chargingStation.ocppRequestService
dc661702
JB
1253 .requestHandler<
1254 OCPP16StatusNotificationRequest,
1255 OCPP16StatusNotificationResponse
1256 >(
08f130a0 1257 chargingStation,
dc661702
JB
1258 OCPP16RequestCommand.STATUS_NOTIFICATION,
1259 {
1260 connectorId,
1261 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1262 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1263 },
1264 {
1265 triggerMessage: true,
1266 }
1267 )
1268 .catch(() => {
1269 /* This is intentional */
1270 });
1271 }
1272 }
1273 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1274 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1275 default:
bf53cadf 1276 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1277 }
1278 } catch (error) {
e7aeea18 1279 return this.handleIncomingRequestError(
08f130a0 1280 chargingStation,
e7aeea18
JB
1281 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1282 error as Error,
bf53cadf 1283 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1284 );
802cfa13
JB
1285 }
1286 }
77b95a89
JB
1287
1288 private handleRequestDataTransfer(
1289 chargingStation: ChargingStation,
1290 commandPayload: OCPP16DataTransferRequest
1291 ): OCPP16DataTransferResponse {
1292 try {
1293 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1294 return {
1295 status: OCPP16DataTransferStatus.ACCEPTED,
1296 };
1297 }
1298 return {
1299 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1300 };
1301 } catch (error) {
1302 return this.handleIncomingRequestError(
1303 chargingStation,
1304 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1305 error as Error,
bf53cadf 1306 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1307 );
1308 }
1309 }
e9a4164c
JB
1310
1311 private parseJsonSchemaFile<T extends JsonType>(relativePath: string): JSONSchemaType<T> {
1312 return JSON.parse(
1313 fs.readFileSync(
1314 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
1315 'utf8'
1316 )
1317 ) as JSONSchemaType<T>;
1318 }
c0560973 1319}