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