Refine UI protocol documentation
[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) {
91a4f151
JB
765 const remoteStartTransactionLogMsg =
766 chargingStation.logPrefix() +
767 ' Transaction remotely STARTED on ' +
768 chargingStation.stationInfo.chargingStationId +
769 '#' +
770 transactionConnectorId.toString() +
771 " for idTag '" +
772 commandPayload.idTag +
773 "'";
08f130a0 774 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
775 OCPP16StatusNotificationRequest,
776 OCPP16StatusNotificationResponse
08f130a0 777 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
778 connectorId: transactionConnectorId,
779 status: OCPP16ChargePointStatus.PREPARING,
780 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
781 });
658e2d16 782 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
08f130a0 783 if (chargingStation.isChargingStationAvailable() && connectorStatus) {
e060fe58 784 // Check if authorized
08f130a0 785 if (chargingStation.getAuthorizeRemoteTxRequests()) {
a7fc8211 786 let authorized = false;
e7aeea18 787 if (
08f130a0
JB
788 chargingStation.getLocalAuthListEnabled() &&
789 chargingStation.hasAuthorizedTags() &&
9d7484a4
JB
790 chargingStation.authorizedTagsCache
791 .getAuthorizedTags(
792 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
793 )
794 .find((value) => value === commandPayload.idTag)
e7aeea18 795 ) {
658e2d16
JB
796 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
797 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 798 authorized = true;
03ebf4c1 799 } else if (chargingStation.getMustAuthorizeAtRemoteStart()) {
658e2d16 800 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 801 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 802 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
803 OCPP16AuthorizeRequest,
804 OCPP16AuthorizeResponse
08f130a0 805 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
806 idTag: commandPayload.idTag,
807 });
a7fc8211
JB
808 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
809 authorized = true;
a7fc8211 810 }
71068fb9 811 } else {
e7aeea18 812 logger.warn(
08f130a0 813 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 814 );
a7fc8211
JB
815 }
816 if (authorized) {
817 // Authorization successful, start transaction
e7aeea18
JB
818 if (
819 this.setRemoteStartTransactionChargingProfile(
08f130a0 820 chargingStation,
e7aeea18
JB
821 transactionConnectorId,
822 commandPayload.chargingProfile
823 )
824 ) {
658e2d16 825 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
826 if (
827 (
08f130a0 828 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
829 OCPP16StartTransactionRequest,
830 OCPP16StartTransactionResponse
08f130a0 831 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
832 connectorId: transactionConnectorId,
833 idTag: commandPayload.idTag,
834 })
e7aeea18
JB
835 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
836 ) {
91a4f151 837 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
838 return Constants.OCPP_RESPONSE_ACCEPTED;
839 }
e7aeea18 840 return this.notifyRemoteStartTransactionRejected(
08f130a0 841 chargingStation,
e7aeea18
JB
842 transactionConnectorId,
843 commandPayload.idTag
844 );
e060fe58 845 }
e7aeea18 846 return this.notifyRemoteStartTransactionRejected(
08f130a0 847 chargingStation,
e7aeea18
JB
848 transactionConnectorId,
849 commandPayload.idTag
850 );
a7fc8211 851 }
e7aeea18 852 return this.notifyRemoteStartTransactionRejected(
08f130a0 853 chargingStation,
e7aeea18
JB
854 transactionConnectorId,
855 commandPayload.idTag
856 );
36f6a92e 857 }
a7fc8211 858 // No authorization check required, start transaction
e7aeea18
JB
859 if (
860 this.setRemoteStartTransactionChargingProfile(
08f130a0 861 chargingStation,
e7aeea18
JB
862 transactionConnectorId,
863 commandPayload.chargingProfile
864 )
865 ) {
658e2d16 866 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
867 if (
868 (
08f130a0 869 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
870 OCPP16StartTransactionRequest,
871 OCPP16StartTransactionResponse
08f130a0 872 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
873 connectorId: transactionConnectorId,
874 idTag: commandPayload.idTag,
875 })
e7aeea18
JB
876 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
877 ) {
91a4f151 878 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
879 return Constants.OCPP_RESPONSE_ACCEPTED;
880 }
e7aeea18 881 return this.notifyRemoteStartTransactionRejected(
08f130a0 882 chargingStation,
e7aeea18
JB
883 transactionConnectorId,
884 commandPayload.idTag
885 );
e060fe58 886 }
e7aeea18 887 return this.notifyRemoteStartTransactionRejected(
08f130a0 888 chargingStation,
e7aeea18
JB
889 transactionConnectorId,
890 commandPayload.idTag
891 );
c0560973 892 }
e7aeea18 893 return this.notifyRemoteStartTransactionRejected(
08f130a0 894 chargingStation,
e7aeea18
JB
895 transactionConnectorId,
896 commandPayload.idTag
897 );
c0560973 898 }
08f130a0
JB
899 return this.notifyRemoteStartTransactionRejected(
900 chargingStation,
901 transactionConnectorId,
902 commandPayload.idTag
903 );
a7fc8211
JB
904 }
905
e7aeea18 906 private async notifyRemoteStartTransactionRejected(
08f130a0 907 chargingStation: ChargingStation,
e7aeea18
JB
908 connectorId: number,
909 idTag: string
910 ): Promise<DefaultResponse> {
911 if (
08f130a0 912 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 913 ) {
08f130a0 914 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
915 OCPP16StatusNotificationRequest,
916 OCPP16StatusNotificationResponse
08f130a0 917 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
918 connectorId,
919 status: OCPP16ChargePointStatus.AVAILABLE,
920 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
921 });
08f130a0 922 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 923 }
e7aeea18 924 logger.warn(
08f130a0 925 chargingStation.logPrefix() +
e7aeea18
JB
926 ' Remote starting transaction REJECTED on connector Id ' +
927 connectorId.toString() +
91a4f151 928 ", idTag '" +
e7aeea18 929 idTag +
91a4f151 930 "', availability '" +
08f130a0 931 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
932 "', status '" +
933 chargingStation.getConnectorStatus(connectorId).status +
934 "'"
e7aeea18 935 );
c0560973
JB
936 return Constants.OCPP_RESPONSE_REJECTED;
937 }
938
e7aeea18 939 private setRemoteStartTransactionChargingProfile(
08f130a0 940 chargingStation: ChargingStation,
e7aeea18
JB
941 connectorId: number,
942 cp: OCPP16ChargingProfile
943 ): boolean {
a7fc8211 944 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
08f130a0 945 chargingStation.setChargingProfile(connectorId, cp);
e7aeea18 946 logger.debug(
08f130a0
JB
947 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
948 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 949 );
a7fc8211
JB
950 return true;
951 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 952 logger.warn(
08f130a0 953 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
954 cp.chargingProfilePurpose
955 } charging profile(s) at remote start transaction`
956 );
a7fc8211 957 return false;
e060fe58
JB
958 } else if (!cp) {
959 return true;
a7fc8211
JB
960 }
961 }
962
e7aeea18 963 private async handleRequestRemoteStopTransaction(
08f130a0 964 chargingStation: ChargingStation,
e7aeea18
JB
965 commandPayload: RemoteStopTransactionRequest
966 ): Promise<DefaultResponse> {
c0560973 967 const transactionId = commandPayload.transactionId;
08f130a0 968 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
969 if (
970 connectorId > 0 &&
08f130a0 971 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 972 ) {
08f130a0 973 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
974 OCPP16StatusNotificationRequest,
975 OCPP16StatusNotificationResponse
08f130a0 976 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
977 connectorId,
978 status: OCPP16ChargePointStatus.FINISHING,
979 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
980 });
08f130a0 981 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
68c993d5 982 if (
08f130a0
JB
983 chargingStation.getBeginEndMeterValues() &&
984 chargingStation.getOcppStrictCompliance() &&
985 !chargingStation.getOutOfOrderEndMeterValues()
68c993d5
JB
986 ) {
987 // FIXME: Implement OCPP version agnostic helpers
988 const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
08f130a0 989 chargingStation,
68c993d5 990 connectorId,
08f130a0 991 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)
68c993d5 992 );
08f130a0 993 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
994 OCPP16MeterValuesRequest,
995 OCPP16MeterValuesResponse
08f130a0 996 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
ef6fa3fb 997 connectorId,
68c993d5 998 transactionId,
7369e417 999 meterValue: [transactionEndMeterValue],
ef6fa3fb
JB
1000 });
1001 }
08f130a0 1002 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1003 OCPP16StopTransactionRequest,
1004 OCPP16StopTransactionResponse
08f130a0 1005 >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
ef6fa3fb 1006 transactionId,
08f130a0
JB
1007 meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
1008 idTag: chargingStation.getTransactionIdTag(transactionId),
ef6fa3fb 1009 });
c0560973
JB
1010 return Constants.OCPP_RESPONSE_ACCEPTED;
1011 }
1012 }
44b9b577 1013 logger.warn(
08f130a0 1014 chargingStation.logPrefix() +
e7aeea18
JB
1015 ' Trying to remote stop a non existing transaction ' +
1016 transactionId.toString()
1017 );
c0560973
JB
1018 return Constants.OCPP_RESPONSE_REJECTED;
1019 }
47e22477 1020
e7aeea18 1021 private async handleRequestGetDiagnostics(
08f130a0 1022 chargingStation: ChargingStation,
e7aeea18
JB
1023 commandPayload: GetDiagnosticsRequest
1024 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1025 if (
370ae4ee 1026 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1027 chargingStation,
370ae4ee
JB
1028 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1029 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1030 )
68cb8b91 1031 ) {
68cb8b91
JB
1032 return Constants.OCPP_RESPONSE_EMPTY;
1033 }
e7aeea18 1034 logger.debug(
08f130a0 1035 chargingStation.logPrefix() +
e7aeea18
JB
1036 ' ' +
1037 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
1038 ' request received: %j',
1039 commandPayload
1040 );
a3868ec4 1041 const uri = new URL(commandPayload.location);
47e22477
JB
1042 if (uri.protocol.startsWith('ftp:')) {
1043 let ftpClient: Client;
1044 try {
e7aeea18 1045 const logFiles = fs
0d8140bd 1046 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1047 .filter((file) => file.endsWith('.log'))
1048 .map((file) => path.join('./', file));
08f130a0 1049 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1050 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1051 ftpClient = new Client();
1052 const accessResponse = await ftpClient.access({
1053 host: uri.host,
e8191622
JB
1054 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1055 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1056 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1057 });
1058 let uploadResponse: FTPResponse;
1059 if (accessResponse.code === 220) {
1060 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1061 ftpClient.trackProgress(async (info) => {
e7aeea18 1062 logger.info(
08f130a0 1063 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1064 info.bytes / 1024
1065 } bytes transferred from diagnostics archive ${info.name}`
1066 );
08f130a0 1067 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1068 DiagnosticsStatusNotificationRequest,
1069 DiagnosticsStatusNotificationResponse
08f130a0 1070 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1071 status: OCPP16DiagnosticsStatus.Uploading,
1072 });
47e22477 1073 });
e7aeea18 1074 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1075 path.join(
1076 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1077 diagnosticsArchive
1078 ),
e7aeea18
JB
1079 uri.pathname + diagnosticsArchive
1080 );
47e22477 1081 if (uploadResponse.code === 226) {
08f130a0 1082 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1083 DiagnosticsStatusNotificationRequest,
1084 DiagnosticsStatusNotificationResponse
08f130a0 1085 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1086 status: OCPP16DiagnosticsStatus.Uploaded,
1087 });
47e22477
JB
1088 if (ftpClient) {
1089 ftpClient.close();
1090 }
1091 return { fileName: diagnosticsArchive };
1092 }
e7aeea18
JB
1093 throw new OCPPError(
1094 ErrorType.GENERIC_ERROR,
1095 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1096 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1097 }`,
1098 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1099 );
47e22477 1100 }
e7aeea18
JB
1101 throw new OCPPError(
1102 ErrorType.GENERIC_ERROR,
1103 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1104 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1105 }`,
1106 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1107 );
47e22477 1108 } catch (error) {
08f130a0 1109 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1110 DiagnosticsStatusNotificationRequest,
1111 DiagnosticsStatusNotificationResponse
08f130a0 1112 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1113 status: OCPP16DiagnosticsStatus.UploadFailed,
1114 });
47e22477
JB
1115 if (ftpClient) {
1116 ftpClient.close();
1117 }
e7aeea18 1118 return this.handleIncomingRequestError(
08f130a0 1119 chargingStation,
e7aeea18
JB
1120 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1121 error as Error,
1122 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
1123 );
47e22477
JB
1124 }
1125 } else {
e7aeea18 1126 logger.error(
08f130a0 1127 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1128 uri.protocol
1129 } to transfer the diagnostic logs archive`
1130 );
08f130a0 1131 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1132 DiagnosticsStatusNotificationRequest,
1133 DiagnosticsStatusNotificationResponse
08f130a0 1134 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1135 status: OCPP16DiagnosticsStatus.UploadFailed,
1136 });
47e22477
JB
1137 return Constants.OCPP_RESPONSE_EMPTY;
1138 }
1139 }
802cfa13 1140
e7aeea18 1141 private handleRequestTriggerMessage(
08f130a0 1142 chargingStation: ChargingStation,
e7aeea18
JB
1143 commandPayload: OCPP16TriggerMessageRequest
1144 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1145 if (
1146 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1147 chargingStation,
370ae4ee
JB
1148 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1149 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1150 )
1151 ) {
68cb8b91
JB
1152 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1153 }
dc661702
JB
1154 // TODO: factor out the check on connector id
1155 if (commandPayload?.connectorId < 0) {
1156 logger.warn(
08f130a0 1157 `${chargingStation.logPrefix()} ${
dc661702
JB
1158 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1159 } incoming request received with invalid connectorId ${commandPayload.connectorId}`
1160 );
1161 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1162 }
802cfa13
JB
1163 try {
1164 switch (commandPayload.requestedMessage) {
1165 case MessageTrigger.BootNotification:
1166 setTimeout(() => {
08f130a0 1167 chargingStation.ocppRequestService
f7f98c68 1168 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1169 chargingStation,
6a8b180d
JB
1170 OCPP16RequestCommand.BOOT_NOTIFICATION,
1171 {
08f130a0
JB
1172 chargePointModel: chargingStation.getBootNotificationRequest().chargePointModel,
1173 chargePointVendor: chargingStation.getBootNotificationRequest().chargePointVendor,
6a8b180d 1174 chargeBoxSerialNumber:
08f130a0
JB
1175 chargingStation.getBootNotificationRequest().chargeBoxSerialNumber,
1176 firmwareVersion: chargingStation.getBootNotificationRequest().firmwareVersion,
6a8b180d 1177 chargePointSerialNumber:
08f130a0
JB
1178 chargingStation.getBootNotificationRequest().chargePointSerialNumber,
1179 iccid: chargingStation.getBootNotificationRequest().iccid,
1180 imsi: chargingStation.getBootNotificationRequest().imsi,
1181 meterSerialNumber: chargingStation.getBootNotificationRequest().meterSerialNumber,
1182 meterType: chargingStation.getBootNotificationRequest().meterType,
6a8b180d
JB
1183 },
1184 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1185 )
ae711c83 1186 .then((value) => {
08f130a0 1187 chargingStation.bootNotificationResponse = value;
ae711c83 1188 })
e7aeea18
JB
1189 .catch(() => {
1190 /* This is intentional */
1191 });
802cfa13
JB
1192 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1193 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1194 case MessageTrigger.Heartbeat:
1195 setTimeout(() => {
08f130a0 1196 chargingStation.ocppRequestService
f7f98c68 1197 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1198 chargingStation,
ef6fa3fb
JB
1199 OCPP16RequestCommand.HEARTBEAT,
1200 null,
1201 {
1202 triggerMessage: true,
1203 }
1204 )
e7aeea18
JB
1205 .catch(() => {
1206 /* This is intentional */
1207 });
802cfa13
JB
1208 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1209 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
dc661702
JB
1210 case MessageTrigger.StatusNotification:
1211 setTimeout(() => {
1212 if (commandPayload?.connectorId) {
08f130a0 1213 chargingStation.ocppRequestService
dc661702 1214 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1215 chargingStation,
dc661702
JB
1216 OCPP16RequestCommand.STATUS_NOTIFICATION,
1217 {
1218 connectorId: commandPayload.connectorId,
1219 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1220 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1221 },
1222 {
1223 triggerMessage: true,
1224 }
1225 )
1226 .catch(() => {
1227 /* This is intentional */
1228 });
1229 } else {
08f130a0
JB
1230 for (const connectorId of chargingStation.connectors.keys()) {
1231 chargingStation.ocppRequestService
dc661702
JB
1232 .requestHandler<
1233 OCPP16StatusNotificationRequest,
1234 OCPP16StatusNotificationResponse
1235 >(
08f130a0 1236 chargingStation,
dc661702
JB
1237 OCPP16RequestCommand.STATUS_NOTIFICATION,
1238 {
1239 connectorId,
1240 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1241 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1242 },
1243 {
1244 triggerMessage: true,
1245 }
1246 )
1247 .catch(() => {
1248 /* This is intentional */
1249 });
1250 }
1251 }
1252 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1253 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13
JB
1254 default:
1255 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1256 }
1257 } catch (error) {
e7aeea18 1258 return this.handleIncomingRequestError(
08f130a0 1259 chargingStation,
e7aeea18
JB
1260 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1261 error as Error,
1262 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1263 );
802cfa13
JB
1264 }
1265 }
c0560973 1266}