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