Convert some type definitions to type syntax
[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
JB
80import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
81import type { DefaultResponse } 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
332 ): DefaultResponse {
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
JB
696 commandPayload: RemoteStartTransactionRequest
697 ): Promise<DefaultResponse> {
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
846 ): Promise<DefaultResponse> {
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
JB
901 commandPayload: RemoteStopTransactionRequest
902 ): Promise<DefaultResponse> {
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 ) {
bf53cadf 947 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580 948 }
c9a4f9ea
JB
949 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
950 if (retrieveDate.getTime() <= Date.now()) {
951 this.asyncResource
952 .runInAsyncScope(
953 this.updateFirmware.bind(this) as (
954 this: OCPP16IncomingRequestService,
955 ...args: any[]
956 ) => Promise<void>,
957 this,
958 chargingStation
959 )
960 .catch(() => {
961 /* This is intentional */
962 });
963 } else {
964 setTimeout(() => {
965 this.updateFirmware(chargingStation).catch(() => {
966 /* Intentional */
967 });
968 }, retrieveDate.getTime() - Date.now());
969 }
970 return OCPPConstants.OCPP_RESPONSE_EMPTY;
971 }
972
973 private async updateFirmware(
974 chargingStation: ChargingStation,
975 minDelay = 15,
976 maxDelay = 30
977 ): Promise<void> {
978 chargingStation.stopAutomaticTransactionGenerator();
979 for (const connectorId of chargingStation.connectors.keys()) {
980 if (
981 connectorId > 0 &&
982 chargingStation.getConnectorStatus(connectorId).transactionStarted === false
983 ) {
984 await chargingStation.ocppRequestService.requestHandler<
985 OCPP16StatusNotificationRequest,
986 OCPP16StatusNotificationResponse
987 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
988 connectorId,
989 status: OCPP16ChargePointStatus.UNAVAILABLE,
990 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
991 });
3637ca2c
JB
992 chargingStation.getConnectorStatus(connectorId).status =
993 OCPP16ChargePointStatus.UNAVAILABLE;
c9a4f9ea
JB
994 }
995 }
996 await chargingStation.ocppRequestService.requestHandler<
997 OCPP16FirmwareStatusNotificationRequest,
998 OCPP16FirmwareStatusNotificationResponse
999 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1000 status: OCPP16FirmwareStatus.Downloading,
1001 });
1002 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1003 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1004 await chargingStation.ocppRequestService.requestHandler<
1005 OCPP16FirmwareStatusNotificationRequest,
1006 OCPP16FirmwareStatusNotificationResponse
1007 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1008 status: OCPP16FirmwareStatus.Downloaded,
1009 });
1010 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1011 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
1012 await chargingStation.ocppRequestService.requestHandler<
1013 OCPP16FirmwareStatusNotificationRequest,
1014 OCPP16FirmwareStatusNotificationResponse
1015 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1016 status: OCPP16FirmwareStatus.Installing,
1017 });
1018 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1019 await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
f335c1d5 1020 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
b03df580
JB
1021 }
1022
e7aeea18 1023 private async handleRequestGetDiagnostics(
08f130a0 1024 chargingStation: ChargingStation,
e7aeea18
JB
1025 commandPayload: GetDiagnosticsRequest
1026 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1027 if (
1789ba2c 1028 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1029 chargingStation,
370ae4ee
JB
1030 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1031 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1032 ) === false
68cb8b91 1033 ) {
bf53cadf 1034 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1035 }
a3868ec4 1036 const uri = new URL(commandPayload.location);
47e22477
JB
1037 if (uri.protocol.startsWith('ftp:')) {
1038 let ftpClient: Client;
1039 try {
e7aeea18 1040 const logFiles = fs
0d8140bd 1041 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1042 .filter((file) => file.endsWith('.log'))
1043 .map((file) => path.join('./', file));
08f130a0 1044 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1045 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1046 ftpClient = new Client();
1047 const accessResponse = await ftpClient.access({
1048 host: uri.host,
e8191622
JB
1049 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1050 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1051 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1052 });
1053 let uploadResponse: FTPResponse;
1054 if (accessResponse.code === 220) {
1055 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1056 ftpClient.trackProgress(async (info) => {
e7aeea18 1057 logger.info(
08f130a0 1058 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1059 info.bytes / 1024
1060 } bytes transferred from diagnostics archive ${info.name}`
1061 );
08f130a0 1062 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1063 OCPP16DiagnosticsStatusNotificationRequest,
1064 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1065 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1066 status: OCPP16DiagnosticsStatus.Uploading,
1067 });
47e22477 1068 });
e7aeea18 1069 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1070 path.join(
1071 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1072 diagnosticsArchive
1073 ),
e7aeea18
JB
1074 uri.pathname + diagnosticsArchive
1075 );
47e22477 1076 if (uploadResponse.code === 226) {
08f130a0 1077 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1078 OCPP16DiagnosticsStatusNotificationRequest,
1079 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1080 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1081 status: OCPP16DiagnosticsStatus.Uploaded,
1082 });
47e22477
JB
1083 if (ftpClient) {
1084 ftpClient.close();
1085 }
1086 return { fileName: diagnosticsArchive };
1087 }
e7aeea18
JB
1088 throw new OCPPError(
1089 ErrorType.GENERIC_ERROR,
1090 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1091 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1092 }`,
1093 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1094 );
47e22477 1095 }
e7aeea18
JB
1096 throw new OCPPError(
1097 ErrorType.GENERIC_ERROR,
1098 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1099 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1100 }`,
1101 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1102 );
47e22477 1103 } catch (error) {
08f130a0 1104 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1105 OCPP16DiagnosticsStatusNotificationRequest,
1106 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1107 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1108 status: OCPP16DiagnosticsStatus.UploadFailed,
1109 });
47e22477
JB
1110 if (ftpClient) {
1111 ftpClient.close();
1112 }
e7aeea18 1113 return this.handleIncomingRequestError(
08f130a0 1114 chargingStation,
e7aeea18
JB
1115 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1116 error as Error,
bf53cadf 1117 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1118 );
47e22477
JB
1119 }
1120 } else {
e7aeea18 1121 logger.error(
08f130a0 1122 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1123 uri.protocol
1124 } to transfer the diagnostic logs archive`
1125 );
08f130a0 1126 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1127 OCPP16DiagnosticsStatusNotificationRequest,
1128 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1129 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1130 status: OCPP16DiagnosticsStatus.UploadFailed,
1131 });
bf53cadf 1132 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1133 }
1134 }
802cfa13 1135
e7aeea18 1136 private handleRequestTriggerMessage(
08f130a0 1137 chargingStation: ChargingStation,
e7aeea18
JB
1138 commandPayload: OCPP16TriggerMessageRequest
1139 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1140 if (
1141 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1142 chargingStation,
370ae4ee
JB
1143 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1144 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1145 ) ||
1146 !OCPP16ServiceUtils.isMessageTriggerSupported(
1147 chargingStation,
1148 commandPayload.requestedMessage
370ae4ee
JB
1149 )
1150 ) {
bf53cadf 1151 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1152 }
c60ed4b8 1153 if (
4caa7e67 1154 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1155 chargingStation,
1156 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1157 commandPayload.connectorId
1158 )
1159 ) {
bf53cadf 1160 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1161 }
802cfa13
JB
1162 try {
1163 switch (commandPayload.requestedMessage) {
c60ed4b8 1164 case OCPP16MessageTrigger.BootNotification:
802cfa13 1165 setTimeout(() => {
08f130a0 1166 chargingStation.ocppRequestService
f7f98c68 1167 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1168 chargingStation,
6a8b180d 1169 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1170 chargingStation.bootNotificationRequest,
6a8b180d 1171 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1172 )
8bfbc743
JB
1173 .then((response) => {
1174 chargingStation.bootNotificationResponse = response;
ae711c83 1175 })
e7aeea18
JB
1176 .catch(() => {
1177 /* This is intentional */
1178 });
802cfa13 1179 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1180 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1181 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1182 setTimeout(() => {
08f130a0 1183 chargingStation.ocppRequestService
f7f98c68 1184 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1185 chargingStation,
ef6fa3fb
JB
1186 OCPP16RequestCommand.HEARTBEAT,
1187 null,
1188 {
1189 triggerMessage: true,
1190 }
1191 )
e7aeea18
JB
1192 .catch(() => {
1193 /* This is intentional */
1194 });
802cfa13 1195 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1196 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1197 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1198 setTimeout(() => {
1199 if (commandPayload?.connectorId) {
08f130a0 1200 chargingStation.ocppRequestService
dc661702 1201 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1202 chargingStation,
dc661702
JB
1203 OCPP16RequestCommand.STATUS_NOTIFICATION,
1204 {
1205 connectorId: commandPayload.connectorId,
1206 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1207 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1208 },
1209 {
1210 triggerMessage: true,
1211 }
1212 )
1213 .catch(() => {
1214 /* This is intentional */
1215 });
1216 } else {
08f130a0
JB
1217 for (const connectorId of chargingStation.connectors.keys()) {
1218 chargingStation.ocppRequestService
dc661702
JB
1219 .requestHandler<
1220 OCPP16StatusNotificationRequest,
1221 OCPP16StatusNotificationResponse
1222 >(
08f130a0 1223 chargingStation,
dc661702
JB
1224 OCPP16RequestCommand.STATUS_NOTIFICATION,
1225 {
1226 connectorId,
1227 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1228 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1229 },
1230 {
1231 triggerMessage: true,
1232 }
1233 )
1234 .catch(() => {
1235 /* This is intentional */
1236 });
1237 }
1238 }
1239 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1240 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1241 default:
bf53cadf 1242 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1243 }
1244 } catch (error) {
e7aeea18 1245 return this.handleIncomingRequestError(
08f130a0 1246 chargingStation,
e7aeea18
JB
1247 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1248 error as Error,
bf53cadf 1249 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1250 );
802cfa13
JB
1251 }
1252 }
77b95a89
JB
1253
1254 private handleRequestDataTransfer(
1255 chargingStation: ChargingStation,
1256 commandPayload: OCPP16DataTransferRequest
1257 ): OCPP16DataTransferResponse {
1258 try {
1259 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1260 return {
1261 status: OCPP16DataTransferStatus.ACCEPTED,
1262 };
1263 }
1264 return {
1265 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1266 };
1267 } catch (error) {
1268 return this.handleIncomingRequestError(
1269 chargingStation,
1270 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1271 error as Error,
bf53cadf 1272 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1273 );
1274 }
1275 }
e9a4164c
JB
1276
1277 private parseJsonSchemaFile<T extends JsonType>(relativePath: string): JSONSchemaType<T> {
1278 return JSON.parse(
1279 fs.readFileSync(
1280 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
1281 'utf8'
1282 )
1283 ) as JSONSchemaType<T>;
1284 }
c0560973 1285}