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