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