README.md: formatting fixes
[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,
07989fad
JB
443 meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
444 transactionId,
445 true
446 ),
08f130a0 447 idTag: chargingStation.getTransactionIdTag(transactionId),
ef6fa3fb
JB
448 reason: OCPP16StopTransactionReason.UNLOCK_COMMAND,
449 });
c0560973
JB
450 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
451 return Constants.OCPP_RESPONSE_UNLOCKED;
452 }
453 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
454 }
08f130a0 455 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
456 OCPP16StatusNotificationRequest,
457 OCPP16StatusNotificationResponse
08f130a0 458 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
459 connectorId,
460 status: OCPP16ChargePointStatus.AVAILABLE,
461 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
462 });
08f130a0 463 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
464 return Constants.OCPP_RESPONSE_UNLOCKED;
465 }
466
e7aeea18 467 private handleRequestGetConfiguration(
08f130a0 468 chargingStation: ChargingStation,
e7aeea18
JB
469 commandPayload: GetConfigurationRequest
470 ): GetConfigurationResponse {
c0560973
JB
471 const configurationKey: OCPPConfigurationKey[] = [];
472 const unknownKey: string[] = [];
473 if (Utils.isEmptyArray(commandPayload.key)) {
08f130a0 474 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
7f7b65ca
JB
475 if (Utils.isUndefined(configuration.visible)) {
476 configuration.visible = true;
c0560973 477 }
7f7b65ca 478 if (!configuration.visible) {
c0560973
JB
479 continue;
480 }
481 configurationKey.push({
7f7b65ca
JB
482 key: configuration.key,
483 readonly: configuration.readonly,
484 value: configuration.value,
c0560973
JB
485 });
486 }
487 } else {
488 for (const key of commandPayload.key) {
17ac262c
JB
489 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
490 chargingStation,
491 key
492 );
c0560973
JB
493 if (keyFound) {
494 if (Utils.isUndefined(keyFound.visible)) {
495 keyFound.visible = true;
496 }
497 if (!keyFound.visible) {
498 continue;
499 }
500 configurationKey.push({
501 key: keyFound.key,
502 readonly: keyFound.readonly,
503 value: keyFound.value,
504 });
505 } else {
506 unknownKey.push(key);
507 }
508 }
509 }
510 return {
511 configurationKey,
512 unknownKey,
513 };
514 }
515
e7aeea18 516 private handleRequestChangeConfiguration(
08f130a0 517 chargingStation: ChargingStation,
e7aeea18
JB
518 commandPayload: ChangeConfigurationRequest
519 ): ChangeConfigurationResponse {
17ac262c
JB
520 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
521 chargingStation,
522 commandPayload.key,
523 true
524 );
c0560973
JB
525 if (!keyToChange) {
526 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
527 } else if (keyToChange && keyToChange.readonly) {
528 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
529 } else if (keyToChange && !keyToChange.readonly) {
c0560973 530 let valueChanged = false;
a95873d8 531 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
532 ChargingStationConfigurationUtils.setConfigurationKeyValue(
533 chargingStation,
534 commandPayload.key,
535 commandPayload.value,
536 true
537 );
c0560973
JB
538 valueChanged = true;
539 }
540 let triggerHeartbeatRestart = false;
541 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
542 ChargingStationConfigurationUtils.setConfigurationKeyValue(
543 chargingStation,
e7aeea18
JB
544 OCPP16StandardParametersKey.HeartbeatInterval,
545 commandPayload.value
546 );
c0560973
JB
547 triggerHeartbeatRestart = true;
548 }
549 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
550 ChargingStationConfigurationUtils.setConfigurationKeyValue(
551 chargingStation,
e7aeea18
JB
552 OCPP16StandardParametersKey.HeartBeatInterval,
553 commandPayload.value
554 );
c0560973
JB
555 triggerHeartbeatRestart = true;
556 }
557 if (triggerHeartbeatRestart) {
08f130a0 558 chargingStation.restartHeartbeat();
c0560973
JB
559 }
560 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 561 chargingStation.restartWebSocketPing();
c0560973
JB
562 }
563 if (keyToChange.reboot) {
564 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
565 }
566 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
567 }
568 }
569
e7aeea18 570 private handleRequestSetChargingProfile(
08f130a0 571 chargingStation: ChargingStation,
e7aeea18
JB
572 commandPayload: SetChargingProfileRequest
573 ): SetChargingProfileResponse {
370ae4ee
JB
574 if (
575 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 576 chargingStation,
370ae4ee
JB
577 OCPP16SupportedFeatureProfiles.SmartCharging,
578 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
579 )
580 ) {
68cb8b91
JB
581 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
582 }
08f130a0 583 if (!chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18 584 logger.error(
08f130a0 585 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
586 commandPayload.connectorId
587 }`
588 );
c0560973
JB
589 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
590 }
e7aeea18
JB
591 if (
592 commandPayload.csChargingProfiles.chargingProfilePurpose ===
593 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
594 commandPayload.connectorId !== 0
595 ) {
c0560973
JB
596 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
597 }
e7aeea18
JB
598 if (
599 commandPayload.csChargingProfiles.chargingProfilePurpose ===
600 ChargingProfilePurposeType.TX_PROFILE &&
601 (commandPayload.connectorId === 0 ||
08f130a0 602 !chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)
e7aeea18 603 ) {
c0560973
JB
604 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
605 }
08f130a0 606 chargingStation.setChargingProfile(
e7aeea18
JB
607 commandPayload.connectorId,
608 commandPayload.csChargingProfiles
609 );
610 logger.debug(
08f130a0 611 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
612 commandPayload.connectorId
613 }, dump their stack: %j`,
08f130a0 614 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 615 );
c0560973
JB
616 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
617 }
618
e7aeea18 619 private handleRequestClearChargingProfile(
08f130a0 620 chargingStation: ChargingStation,
e7aeea18
JB
621 commandPayload: ClearChargingProfileRequest
622 ): ClearChargingProfileResponse {
370ae4ee
JB
623 if (
624 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 625 chargingStation,
370ae4ee
JB
626 OCPP16SupportedFeatureProfiles.SmartCharging,
627 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
628 )
629 ) {
68cb8b91
JB
630 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
631 }
08f130a0 632 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16 633 if (!connectorStatus) {
e7aeea18 634 logger.error(
08f130a0 635 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
636 commandPayload.connectorId
637 }`
638 );
c0560973
JB
639 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
640 }
658e2d16
JB
641 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
642 connectorStatus.chargingProfiles = [];
e7aeea18 643 logger.debug(
08f130a0 644 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
645 commandPayload.connectorId
646 }, dump their stack: %j`,
658e2d16 647 connectorStatus.chargingProfiles
e7aeea18 648 );
c0560973
JB
649 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
650 }
651 if (!commandPayload.connectorId) {
652 let clearedCP = false;
08f130a0
JB
653 for (const connectorId of chargingStation.connectors.keys()) {
654 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
655 chargingStation
e7aeea18
JB
656 .getConnectorStatus(connectorId)
657 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
658 let clearCurrentCP = false;
659 if (chargingProfile.chargingProfileId === commandPayload.id) {
660 clearCurrentCP = true;
661 }
662 if (
663 !commandPayload.chargingProfilePurpose &&
664 chargingProfile.stackLevel === commandPayload.stackLevel
665 ) {
666 clearCurrentCP = true;
667 }
668 if (
669 !chargingProfile.stackLevel &&
670 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
671 ) {
672 clearCurrentCP = true;
673 }
674 if (
675 chargingProfile.stackLevel === commandPayload.stackLevel &&
676 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
677 ) {
678 clearCurrentCP = true;
679 }
680 if (clearCurrentCP) {
ccb1d6e9 681 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 682 logger.debug(
08f130a0 683 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
684 commandPayload.connectorId
685 }, dump their stack: %j`,
658e2d16 686 connectorStatus.chargingProfiles
e7aeea18
JB
687 );
688 clearedCP = true;
689 }
690 });
c0560973
JB
691 }
692 }
693 if (clearedCP) {
694 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
695 }
696 }
697 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
698 }
699
e7aeea18 700 private async handleRequestChangeAvailability(
08f130a0 701 chargingStation: ChargingStation,
e7aeea18
JB
702 commandPayload: ChangeAvailabilityRequest
703 ): Promise<ChangeAvailabilityResponse> {
c0560973 704 const connectorId: number = commandPayload.connectorId;
08f130a0 705 if (!chargingStation.getConnectorStatus(connectorId)) {
e7aeea18 706 logger.error(
08f130a0 707 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 708 );
c0560973
JB
709 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
710 }
e7aeea18
JB
711 const chargePointStatus: OCPP16ChargePointStatus =
712 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
713 ? OCPP16ChargePointStatus.AVAILABLE
714 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
715 if (connectorId === 0) {
716 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0
JB
717 for (const id of chargingStation.connectors.keys()) {
718 if (chargingStation.getConnectorStatus(id)?.transactionStarted) {
c0560973
JB
719 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
720 }
08f130a0 721 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 722 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 723 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
724 OCPP16StatusNotificationRequest,
725 OCPP16StatusNotificationResponse
08f130a0 726 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
727 connectorId: id,
728 status: chargePointStatus,
729 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
730 });
08f130a0 731 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
732 }
733 }
734 return response;
e7aeea18
JB
735 } else if (
736 connectorId > 0 &&
08f130a0
JB
737 (chargingStation.getConnectorStatus(0).availability === OCPP16AvailabilityType.OPERATIVE ||
738 (chargingStation.getConnectorStatus(0).availability ===
e7aeea18
JB
739 OCPP16AvailabilityType.INOPERATIVE &&
740 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
741 ) {
08f130a0
JB
742 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
743 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
744 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
745 }
08f130a0
JB
746 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
747 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
748 OCPP16StatusNotificationRequest,
749 OCPP16StatusNotificationResponse
08f130a0 750 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
751 connectorId,
752 status: chargePointStatus,
753 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
754 });
08f130a0 755 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
756 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
757 }
758 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
759 }
760
e7aeea18 761 private async handleRequestRemoteStartTransaction(
08f130a0 762 chargingStation: ChargingStation,
e7aeea18
JB
763 commandPayload: RemoteStartTransactionRequest
764 ): Promise<DefaultResponse> {
658e2d16 765 const transactionConnectorId = commandPayload.connectorId;
08f130a0 766 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
a7fc8211 767 if (transactionConnectorId) {
91a4f151
JB
768 const remoteStartTransactionLogMsg =
769 chargingStation.logPrefix() +
770 ' Transaction remotely STARTED on ' +
771 chargingStation.stationInfo.chargingStationId +
772 '#' +
773 transactionConnectorId.toString() +
774 " for idTag '" +
775 commandPayload.idTag +
776 "'";
08f130a0 777 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
778 OCPP16StatusNotificationRequest,
779 OCPP16StatusNotificationResponse
08f130a0 780 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
781 connectorId: transactionConnectorId,
782 status: OCPP16ChargePointStatus.PREPARING,
783 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
784 });
658e2d16 785 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
08f130a0 786 if (chargingStation.isChargingStationAvailable() && connectorStatus) {
e060fe58 787 // Check if authorized
08f130a0 788 if (chargingStation.getAuthorizeRemoteTxRequests()) {
a7fc8211 789 let authorized = false;
e7aeea18 790 if (
08f130a0
JB
791 chargingStation.getLocalAuthListEnabled() &&
792 chargingStation.hasAuthorizedTags() &&
9d7484a4
JB
793 chargingStation.authorizedTagsCache
794 .getAuthorizedTags(
795 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
796 )
797 .find((value) => value === commandPayload.idTag)
e7aeea18 798 ) {
658e2d16
JB
799 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
800 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 801 authorized = true;
03ebf4c1 802 } else if (chargingStation.getMustAuthorizeAtRemoteStart()) {
658e2d16 803 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 804 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 805 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
806 OCPP16AuthorizeRequest,
807 OCPP16AuthorizeResponse
08f130a0 808 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
809 idTag: commandPayload.idTag,
810 });
a7fc8211
JB
811 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
812 authorized = true;
a7fc8211 813 }
71068fb9 814 } else {
e7aeea18 815 logger.warn(
08f130a0 816 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 817 );
a7fc8211
JB
818 }
819 if (authorized) {
820 // Authorization successful, start transaction
e7aeea18
JB
821 if (
822 this.setRemoteStartTransactionChargingProfile(
08f130a0 823 chargingStation,
e7aeea18
JB
824 transactionConnectorId,
825 commandPayload.chargingProfile
826 )
827 ) {
658e2d16 828 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
829 if (
830 (
08f130a0 831 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
832 OCPP16StartTransactionRequest,
833 OCPP16StartTransactionResponse
08f130a0 834 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
835 connectorId: transactionConnectorId,
836 idTag: commandPayload.idTag,
837 })
e7aeea18
JB
838 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
839 ) {
91a4f151 840 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
841 return Constants.OCPP_RESPONSE_ACCEPTED;
842 }
e7aeea18 843 return this.notifyRemoteStartTransactionRejected(
08f130a0 844 chargingStation,
e7aeea18
JB
845 transactionConnectorId,
846 commandPayload.idTag
847 );
e060fe58 848 }
e7aeea18 849 return this.notifyRemoteStartTransactionRejected(
08f130a0 850 chargingStation,
e7aeea18
JB
851 transactionConnectorId,
852 commandPayload.idTag
853 );
a7fc8211 854 }
e7aeea18 855 return this.notifyRemoteStartTransactionRejected(
08f130a0 856 chargingStation,
e7aeea18
JB
857 transactionConnectorId,
858 commandPayload.idTag
859 );
36f6a92e 860 }
a7fc8211 861 // No authorization check required, start transaction
e7aeea18
JB
862 if (
863 this.setRemoteStartTransactionChargingProfile(
08f130a0 864 chargingStation,
e7aeea18
JB
865 transactionConnectorId,
866 commandPayload.chargingProfile
867 )
868 ) {
658e2d16 869 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
870 if (
871 (
08f130a0 872 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
873 OCPP16StartTransactionRequest,
874 OCPP16StartTransactionResponse
08f130a0 875 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
876 connectorId: transactionConnectorId,
877 idTag: commandPayload.idTag,
878 })
e7aeea18
JB
879 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
880 ) {
91a4f151 881 logger.debug(remoteStartTransactionLogMsg);
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() +
91a4f151 931 ", idTag '" +
e7aeea18 932 idTag +
91a4f151 933 "', availability '" +
08f130a0 934 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
935 "', status '" +
936 chargingStation.getConnectorStatus(connectorId).status +
937 "'"
e7aeea18 938 );
c0560973
JB
939 return Constants.OCPP_RESPONSE_REJECTED;
940 }
941
e7aeea18 942 private setRemoteStartTransactionChargingProfile(
08f130a0 943 chargingStation: ChargingStation,
e7aeea18
JB
944 connectorId: number,
945 cp: OCPP16ChargingProfile
946 ): boolean {
a7fc8211 947 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
08f130a0 948 chargingStation.setChargingProfile(connectorId, cp);
e7aeea18 949 logger.debug(
08f130a0
JB
950 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
951 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 952 );
a7fc8211
JB
953 return true;
954 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 955 logger.warn(
08f130a0 956 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
957 cp.chargingProfilePurpose
958 } charging profile(s) at remote start transaction`
959 );
a7fc8211 960 return false;
e060fe58
JB
961 } else if (!cp) {
962 return true;
a7fc8211
JB
963 }
964 }
965
e7aeea18 966 private async handleRequestRemoteStopTransaction(
08f130a0 967 chargingStation: ChargingStation,
e7aeea18
JB
968 commandPayload: RemoteStopTransactionRequest
969 ): Promise<DefaultResponse> {
c0560973 970 const transactionId = commandPayload.transactionId;
08f130a0 971 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
972 if (
973 connectorId > 0 &&
08f130a0 974 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 975 ) {
08f130a0 976 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
977 OCPP16StatusNotificationRequest,
978 OCPP16StatusNotificationResponse
08f130a0 979 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
980 connectorId,
981 status: OCPP16ChargePointStatus.FINISHING,
982 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
983 });
08f130a0 984 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
68c993d5 985 if (
08f130a0
JB
986 chargingStation.getBeginEndMeterValues() &&
987 chargingStation.getOcppStrictCompliance() &&
988 !chargingStation.getOutOfOrderEndMeterValues()
68c993d5
JB
989 ) {
990 // FIXME: Implement OCPP version agnostic helpers
991 const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
08f130a0 992 chargingStation,
68c993d5 993 connectorId,
08f130a0 994 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)
68c993d5 995 );
08f130a0 996 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
997 OCPP16MeterValuesRequest,
998 OCPP16MeterValuesResponse
08f130a0 999 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
ef6fa3fb 1000 connectorId,
68c993d5 1001 transactionId,
7369e417 1002 meterValue: [transactionEndMeterValue],
ef6fa3fb
JB
1003 });
1004 }
08f130a0 1005 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1006 OCPP16StopTransactionRequest,
1007 OCPP16StopTransactionResponse
08f130a0 1008 >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
ef6fa3fb 1009 transactionId,
07989fad
JB
1010 meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
1011 transactionId,
1012 true
1013 ),
08f130a0 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 {
0a03f36c
JB
1178 chargePointModel: chargingStation.bootNotificationRequest.chargePointModel,
1179 chargePointVendor: chargingStation.bootNotificationRequest.chargePointVendor,
6a8b180d 1180 chargeBoxSerialNumber:
0a03f36c
JB
1181 chargingStation.bootNotificationRequest.chargeBoxSerialNumber,
1182 firmwareVersion: chargingStation.bootNotificationRequest.firmwareVersion,
6a8b180d 1183 chargePointSerialNumber:
0a03f36c
JB
1184 chargingStation.bootNotificationRequest.chargePointSerialNumber,
1185 iccid: chargingStation.bootNotificationRequest.iccid,
1186 imsi: chargingStation.bootNotificationRequest.imsi,
1187 meterSerialNumber: chargingStation.bootNotificationRequest.meterSerialNumber,
1188 meterType: chargingStation.bootNotificationRequest.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}