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