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