Implement firmwareUpgrade CS template section support
[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
11import OCPPError from '../../../exception/OCPPError';
6c1761d4 12import type { JsonObject, JsonType } from '../../../types/JsonType';
8114d10e
JB
13import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
14import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
15import {
16 ChargingProfilePurposeType,
27782dbc 17 type OCPP16ChargingProfile,
8114d10e
JB
18} from '../../../types/ocpp/1.6/ChargingProfile';
19import {
20 OCPP16StandardParametersKey,
21 OCPP16SupportedFeatureProfiles,
22} from '../../../types/ocpp/1.6/Configuration';
23import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
e7aeea18 24import {
27782dbc
JB
25 type ChangeAvailabilityRequest,
26 type ChangeConfigurationRequest,
27 type ClearChargingProfileRequest,
27782dbc
JB
28 type GetConfigurationRequest,
29 type GetDiagnosticsRequest,
e7aeea18 30 OCPP16AvailabilityType,
27782dbc
JB
31 type OCPP16BootNotificationRequest,
32 type OCPP16ClearCacheRequest,
33 type OCPP16DataTransferRequest,
77b95a89 34 OCPP16DataTransferVendorId,
c9a4f9ea
JB
35 type OCPP16DiagnosticsStatusNotificationRequest,
36 OCPP16FirmwareStatus,
37 type OCPP16FirmwareStatusNotificationRequest,
27782dbc 38 type OCPP16HeartbeatRequest,
e7aeea18 39 OCPP16IncomingRequestCommand,
c60ed4b8 40 OCPP16MessageTrigger,
94a464f9 41 OCPP16RequestCommand,
27782dbc
JB
42 type OCPP16StatusNotificationRequest,
43 type OCPP16TriggerMessageRequest,
44 type OCPP16UpdateFirmwareRequest,
45 type RemoteStartTransactionRequest,
46 type RemoteStopTransactionRequest,
47 type ResetRequest,
48 type SetChargingProfileRequest,
49 type UnlockConnectorRequest,
e7aeea18 50} from '../../../types/ocpp/1.6/Requests';
77b95a89 51import {
27782dbc
JB
52 type ChangeAvailabilityResponse,
53 type ChangeConfigurationResponse,
54 type ClearChargingProfileResponse,
27782dbc
JB
55 type GetConfigurationResponse,
56 type GetDiagnosticsResponse,
57 type OCPP16BootNotificationResponse,
58 type OCPP16DataTransferResponse,
77b95a89 59 OCPP16DataTransferStatus,
c9a4f9ea
JB
60 type OCPP16DiagnosticsStatusNotificationResponse,
61 type OCPP16FirmwareStatusNotificationResponse,
27782dbc
JB
62 type OCPP16HeartbeatResponse,
63 type OCPP16StatusNotificationResponse,
64 type OCPP16TriggerMessageResponse,
65 type OCPP16UpdateFirmwareResponse,
66 type SetChargingProfileResponse,
67 type UnlockConnectorResponse,
e7aeea18 68} from '../../../types/ocpp/1.6/Responses';
e7aeea18
JB
69import {
70 OCPP16AuthorizationStatus,
27782dbc
JB
71 type OCPP16AuthorizeRequest,
72 type OCPP16AuthorizeResponse,
73 type OCPP16StartTransactionRequest,
74 type OCPP16StartTransactionResponse,
e7aeea18
JB
75 OCPP16StopTransactionReason,
76} from '../../../types/ocpp/1.6/Transaction';
6c1761d4 77import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
8114d10e 78import { ErrorType } from '../../../types/ocpp/ErrorType';
d270cc87 79import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
6c1761d4 80import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
f03e1042 81import type { GenericResponse } from '../../../types/ocpp/Responses';
8114d10e
JB
82import Constants from '../../../utils/Constants';
83import logger from '../../../utils/Logger';
84import Utils from '../../../utils/Utils';
73b9adec 85import type ChargingStation from '../../ChargingStation';
17ac262c 86import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
9d7484a4 87import { ChargingStationUtils } from '../../ChargingStationUtils';
bf53cadf 88import OCPPConstants from '../OCPPConstants';
c0560973 89import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
8114d10e 90import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
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
JB
947 logger.warn(
948 `${chargingStation.logPrefix()} Cannot simulate firmware update: feature profile not supported`
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(
957 `${chargingStation.logPrefix()} Cannot simulate firmware update: firmware update is already in progress`
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,
987 minDelay = 15,
988 maxDelay = 30
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
JB
1008 if (
1009 chargingStation.getFirmwareUpgrade()?.failureStatus &&
1010 !Utils.isEmptyString(chargingStation.getFirmwareUpgrade().failureStatus)
1011 ) {
1012 await chargingStation.ocppRequestService.requestHandler<
1013 OCPP16FirmwareStatusNotificationRequest,
1014 OCPP16FirmwareStatusNotificationResponse
1015 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1016 status: chargingStation.getFirmwareUpgrade().failureStatus,
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;
1027 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
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;
1035 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
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;
5d280aae
JB
1043 if (chargingStation.getFirmwareUpgrade().reset === true) {
1044 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
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 ) {
bf53cadf 1060 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1061 }
a3868ec4 1062 const uri = new URL(commandPayload.location);
47e22477
JB
1063 if (uri.protocol.startsWith('ftp:')) {
1064 let ftpClient: Client;
1065 try {
e7aeea18 1066 const logFiles = fs
0d8140bd 1067 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1068 .filter((file) => file.endsWith('.log'))
1069 .map((file) => path.join('./', file));
08f130a0 1070 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1071 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1072 ftpClient = new Client();
1073 const accessResponse = await ftpClient.access({
1074 host: uri.host,
e8191622
JB
1075 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1076 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1077 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1078 });
1079 let uploadResponse: FTPResponse;
1080 if (accessResponse.code === 220) {
1081 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1082 ftpClient.trackProgress(async (info) => {
e7aeea18 1083 logger.info(
08f130a0 1084 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1085 info.bytes / 1024
1086 } bytes transferred from diagnostics archive ${info.name}`
1087 );
08f130a0 1088 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1089 OCPP16DiagnosticsStatusNotificationRequest,
1090 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1091 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1092 status: OCPP16DiagnosticsStatus.Uploading,
1093 });
47e22477 1094 });
e7aeea18 1095 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1096 path.join(
1097 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1098 diagnosticsArchive
1099 ),
e7aeea18
JB
1100 uri.pathname + diagnosticsArchive
1101 );
47e22477 1102 if (uploadResponse.code === 226) {
08f130a0 1103 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1104 OCPP16DiagnosticsStatusNotificationRequest,
1105 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1106 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1107 status: OCPP16DiagnosticsStatus.Uploaded,
1108 });
47e22477
JB
1109 if (ftpClient) {
1110 ftpClient.close();
1111 }
1112 return { fileName: diagnosticsArchive };
1113 }
e7aeea18
JB
1114 throw new OCPPError(
1115 ErrorType.GENERIC_ERROR,
1116 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1117 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1118 }`,
1119 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1120 );
47e22477 1121 }
e7aeea18
JB
1122 throw new OCPPError(
1123 ErrorType.GENERIC_ERROR,
1124 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1125 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1126 }`,
1127 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1128 );
47e22477 1129 } catch (error) {
08f130a0 1130 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1131 OCPP16DiagnosticsStatusNotificationRequest,
1132 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1133 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1134 status: OCPP16DiagnosticsStatus.UploadFailed,
1135 });
47e22477
JB
1136 if (ftpClient) {
1137 ftpClient.close();
1138 }
e7aeea18 1139 return this.handleIncomingRequestError(
08f130a0 1140 chargingStation,
e7aeea18
JB
1141 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1142 error as Error,
bf53cadf 1143 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1144 );
47e22477
JB
1145 }
1146 } else {
e7aeea18 1147 logger.error(
08f130a0 1148 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1149 uri.protocol
1150 } to transfer the diagnostic logs archive`
1151 );
08f130a0 1152 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1153 OCPP16DiagnosticsStatusNotificationRequest,
1154 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1155 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1156 status: OCPP16DiagnosticsStatus.UploadFailed,
1157 });
bf53cadf 1158 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1159 }
1160 }
802cfa13 1161
e7aeea18 1162 private handleRequestTriggerMessage(
08f130a0 1163 chargingStation: ChargingStation,
e7aeea18
JB
1164 commandPayload: OCPP16TriggerMessageRequest
1165 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1166 if (
1167 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1168 chargingStation,
370ae4ee
JB
1169 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1170 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1171 ) ||
1172 !OCPP16ServiceUtils.isMessageTriggerSupported(
1173 chargingStation,
1174 commandPayload.requestedMessage
370ae4ee
JB
1175 )
1176 ) {
bf53cadf 1177 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1178 }
c60ed4b8 1179 if (
4caa7e67 1180 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1181 chargingStation,
1182 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1183 commandPayload.connectorId
1184 )
1185 ) {
bf53cadf 1186 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1187 }
802cfa13
JB
1188 try {
1189 switch (commandPayload.requestedMessage) {
c60ed4b8 1190 case OCPP16MessageTrigger.BootNotification:
802cfa13 1191 setTimeout(() => {
08f130a0 1192 chargingStation.ocppRequestService
f7f98c68 1193 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1194 chargingStation,
6a8b180d 1195 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1196 chargingStation.bootNotificationRequest,
6a8b180d 1197 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1198 )
8bfbc743
JB
1199 .then((response) => {
1200 chargingStation.bootNotificationResponse = response;
ae711c83 1201 })
e7aeea18
JB
1202 .catch(() => {
1203 /* This is intentional */
1204 });
802cfa13 1205 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1206 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1207 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1208 setTimeout(() => {
08f130a0 1209 chargingStation.ocppRequestService
f7f98c68 1210 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1211 chargingStation,
ef6fa3fb
JB
1212 OCPP16RequestCommand.HEARTBEAT,
1213 null,
1214 {
1215 triggerMessage: true,
1216 }
1217 )
e7aeea18
JB
1218 .catch(() => {
1219 /* This is intentional */
1220 });
802cfa13 1221 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1222 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1223 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1224 setTimeout(() => {
1225 if (commandPayload?.connectorId) {
08f130a0 1226 chargingStation.ocppRequestService
dc661702 1227 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1228 chargingStation,
dc661702
JB
1229 OCPP16RequestCommand.STATUS_NOTIFICATION,
1230 {
1231 connectorId: commandPayload.connectorId,
1232 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1233 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1234 },
1235 {
1236 triggerMessage: true,
1237 }
1238 )
1239 .catch(() => {
1240 /* This is intentional */
1241 });
1242 } else {
08f130a0
JB
1243 for (const connectorId of chargingStation.connectors.keys()) {
1244 chargingStation.ocppRequestService
dc661702
JB
1245 .requestHandler<
1246 OCPP16StatusNotificationRequest,
1247 OCPP16StatusNotificationResponse
1248 >(
08f130a0 1249 chargingStation,
dc661702
JB
1250 OCPP16RequestCommand.STATUS_NOTIFICATION,
1251 {
1252 connectorId,
1253 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1254 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1255 },
1256 {
1257 triggerMessage: true,
1258 }
1259 )
1260 .catch(() => {
1261 /* This is intentional */
1262 });
1263 }
1264 }
1265 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1266 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1267 default:
bf53cadf 1268 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1269 }
1270 } catch (error) {
e7aeea18 1271 return this.handleIncomingRequestError(
08f130a0 1272 chargingStation,
e7aeea18
JB
1273 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1274 error as Error,
bf53cadf 1275 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1276 );
802cfa13
JB
1277 }
1278 }
77b95a89
JB
1279
1280 private handleRequestDataTransfer(
1281 chargingStation: ChargingStation,
1282 commandPayload: OCPP16DataTransferRequest
1283 ): OCPP16DataTransferResponse {
1284 try {
1285 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1286 return {
1287 status: OCPP16DataTransferStatus.ACCEPTED,
1288 };
1289 }
1290 return {
1291 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1292 };
1293 } catch (error) {
1294 return this.handleIncomingRequestError(
1295 chargingStation,
1296 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1297 error as Error,
bf53cadf 1298 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1299 );
1300 }
1301 }
e9a4164c
JB
1302
1303 private parseJsonSchemaFile<T extends JsonType>(relativePath: string): JSONSchemaType<T> {
1304 return JSON.parse(
1305 fs.readFileSync(
1306 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
1307 'utf8'
1308 )
1309 ) as JSONSchemaType<T>;
1310 }
c0560973 1311}