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