Initial code cleanups for the future OCPP requests code optimization
[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
e7aeea18
JB
3import {
4 ChangeAvailabilityRequest,
5 ChangeConfigurationRequest,
6 ClearChargingProfileRequest,
7 GetConfigurationRequest,
8 GetDiagnosticsRequest,
9 MessageTrigger,
10 OCPP16AvailabilityType,
11 OCPP16IncomingRequestCommand,
e7aeea18
JB
12 OCPP16TriggerMessageRequest,
13 RemoteStartTransactionRequest,
14 RemoteStopTransactionRequest,
15 ResetRequest,
16 SetChargingProfileRequest,
17 UnlockConnectorRequest,
18} from '../../../types/ocpp/1.6/Requests';
19import {
20 ChangeAvailabilityResponse,
21 ChangeConfigurationResponse,
22 ClearChargingProfileResponse,
23 GetConfigurationResponse,
24 GetDiagnosticsResponse,
25 OCPP16TriggerMessageResponse,
26 SetChargingProfileResponse,
27 UnlockConnectorResponse,
28} from '../../../types/ocpp/1.6/Responses';
29import {
30 ChargingProfilePurposeType,
31 OCPP16ChargingProfile,
32} from '../../../types/ocpp/1.6/ChargingProfile';
47e22477 33import { Client, FTPResponse } from 'basic-ftp';
e7aeea18
JB
34import {
35 OCPP16AuthorizationStatus,
36 OCPP16StopTransactionReason,
37} from '../../../types/ocpp/1.6/Transaction';
c0560973 38
73b9adec 39import type ChargingStation from '../../ChargingStation';
c0560973 40import Constants from '../../../utils/Constants';
9ccca265 41import { DefaultResponse } from '../../../types/ocpp/Responses';
c0560973 42import { ErrorType } from '../../../types/ocpp/ErrorType';
58144adb 43import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
d1888640 44import { JsonType } from '../../../types/JsonType';
c0560973 45import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
47e22477 46import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
c0560973
JB
47import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
48import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
e58068fd 49import OCPPError from '../../../exception/OCPPError';
c0560973 50import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
a3868ec4 51import { URL } from 'url';
c0560973 52import Utils from '../../../utils/Utils';
47e22477 53import fs from 'fs';
9f2e3130 54import logger from '../../../utils/Logger';
47e22477
JB
55import path from 'path';
56import tar from 'tar';
c0560973 57
2a115f87 58const moduleName = 'OCPP16IncomingRequestService';
909dcf2d 59
c0560973 60export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
58144adb
JB
61 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
62
9f2e3130 63 public constructor(chargingStation: ChargingStation) {
909dcf2d 64 if (new.target?.name === moduleName) {
06127450 65 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 66 }
58144adb
JB
67 super(chargingStation);
68 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
69 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
70 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
71 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
e7aeea18
JB
72 [
73 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
74 this.handleRequestGetConfiguration.bind(this),
75 ],
76 [
77 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
78 this.handleRequestChangeConfiguration.bind(this),
79 ],
80 [
81 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
82 this.handleRequestSetChargingProfile.bind(this),
83 ],
84 [
85 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
86 this.handleRequestClearChargingProfile.bind(this),
87 ],
88 [
89 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
90 this.handleRequestChangeAvailability.bind(this),
91 ],
92 [
93 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
94 this.handleRequestRemoteStartTransaction.bind(this),
95 ],
96 [
97 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
98 this.handleRequestRemoteStopTransaction.bind(this),
99 ],
734d790d 100 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
e7aeea18 101 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
58144adb
JB
102 ]);
103 }
104
e7aeea18
JB
105 public async handleRequest(
106 messageId: string,
107 commandName: OCPP16IncomingRequestCommand,
108 commandPayload: JsonType
109 ): Promise<void> {
d1888640 110 let result: JsonType;
e7aeea18
JB
111 if (
112 this.chargingStation.getOcppStrictCompliance() &&
113 this.chargingStation.isInPendingState() &&
114 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
115 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
116 ) {
117 throw new OCPPError(
118 ErrorType.SECURITY_ERROR,
119 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
120 commandPayload,
121 null,
122 2
123 )} while the charging station is in pending state on the central server`,
124 commandName
125 );
caad9d6b 126 }
e7aeea18
JB
127 if (
128 this.chargingStation.isRegistered() ||
129 (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())
130 ) {
124f3553
JB
131 if (this.incomingRequestHandlers.has(commandName)) {
132 try {
133 // Call the method to build the result
134 result = await this.incomingRequestHandlers.get(commandName)(commandPayload);
135 } catch (error) {
136 // Log
9f2e3130 137 logger.error(this.chargingStation.logPrefix() + ' Handle request error: %j', error);
124f3553
JB
138 throw error;
139 }
140 } else {
141 // Throw exception
e7aeea18
JB
142 throw new OCPPError(
143 ErrorType.NOT_IMPLEMENTED,
144 `${commandName} is not implemented to handle request payload ${JSON.stringify(
145 commandPayload,
146 null,
147 2
148 )}`,
149 commandName
150 );
c0560973
JB
151 }
152 } else {
e7aeea18
JB
153 throw new OCPPError(
154 ErrorType.SECURITY_ERROR,
155 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
156 commandPayload,
157 null,
158 2
159 )} while the charging station is not registered on the central server.`,
160 commandName
161 );
c0560973 162 }
de3dbcf5
JB
163 // Send the built result
164 await this.chargingStation.ocppRequestService.sendResult(messageId, result, commandName);
c0560973
JB
165 }
166
167 // Simulate charging station restart
168 private handleRequestReset(commandPayload: ResetRequest): DefaultResponse {
71623267
JB
169 // eslint-disable-next-line @typescript-eslint/no-misused-promises
170 setImmediate(async (): Promise<void> => {
e7aeea18
JB
171 await this.chargingStation.stop(
172 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
173 );
c0560973 174 await Utils.sleep(this.chargingStation.stationInfo.resetTime);
71623267 175 this.chargingStation.start();
c0560973 176 });
e7aeea18
JB
177 logger.info(
178 `${this.chargingStation.logPrefix()} ${
179 commandPayload.type
180 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
181 this.chargingStation.stationInfo.resetTime
182 )}`
183 );
c0560973
JB
184 return Constants.OCPP_RESPONSE_ACCEPTED;
185 }
186
187 private handleRequestClearCache(): DefaultResponse {
188 return Constants.OCPP_RESPONSE_ACCEPTED;
189 }
190
e7aeea18
JB
191 private async handleRequestUnlockConnector(
192 commandPayload: UnlockConnectorRequest
193 ): Promise<UnlockConnectorResponse> {
c0560973
JB
194 const connectorId = commandPayload.connectorId;
195 if (connectorId === 0) {
e7aeea18
JB
196 logger.error(
197 this.chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
198 );
c0560973
JB
199 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
200 }
734d790d
JB
201 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
202 const transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
e7aeea18
JB
203 const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(
204 transactionId,
6ed92bc1
JB
205 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
206 this.chargingStation.getTransactionIdTag(transactionId),
e7aeea18
JB
207 OCPP16StopTransactionReason.UNLOCK_COMMAND
208 );
c0560973
JB
209 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
210 return Constants.OCPP_RESPONSE_UNLOCKED;
211 }
212 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
213 }
e7aeea18
JB
214 await this.chargingStation.ocppRequestService.sendStatusNotification(
215 connectorId,
216 OCPP16ChargePointStatus.AVAILABLE
217 );
734d790d 218 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
219 return Constants.OCPP_RESPONSE_UNLOCKED;
220 }
221
e7aeea18
JB
222 private handleRequestGetConfiguration(
223 commandPayload: GetConfigurationRequest
224 ): GetConfigurationResponse {
c0560973
JB
225 const configurationKey: OCPPConfigurationKey[] = [];
226 const unknownKey: string[] = [];
227 if (Utils.isEmptyArray(commandPayload.key)) {
228 for (const configuration of this.chargingStation.configuration.configurationKey) {
229 if (Utils.isUndefined(configuration.visible)) {
230 configuration.visible = true;
231 }
232 if (!configuration.visible) {
233 continue;
234 }
235 configurationKey.push({
236 key: configuration.key,
237 readonly: configuration.readonly,
238 value: configuration.value,
239 });
240 }
241 } else {
242 for (const key of commandPayload.key) {
243 const keyFound = this.chargingStation.getConfigurationKey(key);
244 if (keyFound) {
245 if (Utils.isUndefined(keyFound.visible)) {
246 keyFound.visible = true;
247 }
248 if (!keyFound.visible) {
249 continue;
250 }
251 configurationKey.push({
252 key: keyFound.key,
253 readonly: keyFound.readonly,
254 value: keyFound.value,
255 });
256 } else {
257 unknownKey.push(key);
258 }
259 }
260 }
261 return {
262 configurationKey,
263 unknownKey,
264 };
265 }
266
e7aeea18
JB
267 private handleRequestChangeConfiguration(
268 commandPayload: ChangeConfigurationRequest
269 ): ChangeConfigurationResponse {
c0560973
JB
270 // JSON request fields type sanity check
271 if (!Utils.isString(commandPayload.key)) {
e7aeea18
JB
272 logger.error(
273 `${this.chargingStation.logPrefix()} ${
78085c42 274 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
e7aeea18
JB
275 } request key field is not a string:`,
276 commandPayload
277 );
c0560973
JB
278 }
279 if (!Utils.isString(commandPayload.value)) {
e7aeea18
JB
280 logger.error(
281 `${this.chargingStation.logPrefix()} ${
78085c42 282 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
e7aeea18
JB
283 } request value field is not a string:`,
284 commandPayload
285 );
c0560973
JB
286 }
287 const keyToChange = this.chargingStation.getConfigurationKey(commandPayload.key, true);
288 if (!keyToChange) {
289 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
290 } else if (keyToChange && keyToChange.readonly) {
291 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
292 } else if (keyToChange && !keyToChange.readonly) {
293 const keyIndex = this.chargingStation.configuration.configurationKey.indexOf(keyToChange);
294 let valueChanged = false;
e7aeea18
JB
295 if (
296 this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value
297 ) {
c0560973
JB
298 this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
299 valueChanged = true;
300 }
301 let triggerHeartbeatRestart = false;
302 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
e7aeea18
JB
303 this.chargingStation.setConfigurationKeyValue(
304 OCPP16StandardParametersKey.HeartbeatInterval,
305 commandPayload.value
306 );
c0560973
JB
307 triggerHeartbeatRestart = true;
308 }
309 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
e7aeea18
JB
310 this.chargingStation.setConfigurationKeyValue(
311 OCPP16StandardParametersKey.HeartBeatInterval,
312 commandPayload.value
313 );
c0560973
JB
314 triggerHeartbeatRestart = true;
315 }
316 if (triggerHeartbeatRestart) {
317 this.chargingStation.restartHeartbeat();
318 }
319 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
320 this.chargingStation.restartWebSocketPing();
321 }
322 if (keyToChange.reboot) {
323 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
324 }
325 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
326 }
327 }
328
e7aeea18
JB
329 private handleRequestSetChargingProfile(
330 commandPayload: SetChargingProfileRequest
331 ): SetChargingProfileResponse {
734d790d 332 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18
JB
333 logger.error(
334 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
335 commandPayload.connectorId
336 }`
337 );
c0560973
JB
338 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
339 }
e7aeea18
JB
340 if (
341 commandPayload.csChargingProfiles.chargingProfilePurpose ===
342 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
343 commandPayload.connectorId !== 0
344 ) {
c0560973
JB
345 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
346 }
e7aeea18
JB
347 if (
348 commandPayload.csChargingProfiles.chargingProfilePurpose ===
349 ChargingProfilePurposeType.TX_PROFILE &&
350 (commandPayload.connectorId === 0 ||
351 !this.chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)
352 ) {
c0560973
JB
353 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
354 }
e7aeea18
JB
355 this.chargingStation.setChargingProfile(
356 commandPayload.connectorId,
357 commandPayload.csChargingProfiles
358 );
359 logger.debug(
360 `${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`,
361 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
362 );
c0560973
JB
363 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
364 }
365
e7aeea18
JB
366 private handleRequestClearChargingProfile(
367 commandPayload: ClearChargingProfileRequest
368 ): ClearChargingProfileResponse {
734d790d 369 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18
JB
370 logger.error(
371 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
372 commandPayload.connectorId
373 }`
374 );
c0560973
JB
375 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
376 }
e7aeea18
JB
377 if (
378 commandPayload.connectorId &&
379 !Utils.isEmptyArray(
380 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
381 )
382 ) {
734d790d 383 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
e7aeea18
JB
384 logger.debug(
385 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
386 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
387 );
c0560973
JB
388 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
389 }
390 if (!commandPayload.connectorId) {
391 let clearedCP = false;
734d790d 392 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
393 if (
394 !Utils.isEmptyArray(this.chargingStation.getConnectorStatus(connectorId).chargingProfiles)
395 ) {
396 this.chargingStation
397 .getConnectorStatus(connectorId)
398 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
399 let clearCurrentCP = false;
400 if (chargingProfile.chargingProfileId === commandPayload.id) {
401 clearCurrentCP = true;
402 }
403 if (
404 !commandPayload.chargingProfilePurpose &&
405 chargingProfile.stackLevel === commandPayload.stackLevel
406 ) {
407 clearCurrentCP = true;
408 }
409 if (
410 !chargingProfile.stackLevel &&
411 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
412 ) {
413 clearCurrentCP = true;
414 }
415 if (
416 chargingProfile.stackLevel === commandPayload.stackLevel &&
417 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
418 ) {
419 clearCurrentCP = true;
420 }
421 if (clearCurrentCP) {
422 this.chargingStation.getConnectorStatus(
423 commandPayload.connectorId
424 ).chargingProfiles[index] = {} as OCPP16ChargingProfile;
425 logger.debug(
426 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
427 this.chargingStation.getConnectorStatus(commandPayload.connectorId)
428 .chargingProfiles
429 );
430 clearedCP = true;
431 }
432 });
c0560973
JB
433 }
434 }
435 if (clearedCP) {
436 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
437 }
438 }
439 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
440 }
441
e7aeea18
JB
442 private async handleRequestChangeAvailability(
443 commandPayload: ChangeAvailabilityRequest
444 ): Promise<ChangeAvailabilityResponse> {
c0560973 445 const connectorId: number = commandPayload.connectorId;
734d790d 446 if (!this.chargingStation.getConnectorStatus(connectorId)) {
e7aeea18
JB
447 logger.error(
448 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
449 );
c0560973
JB
450 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
451 }
e7aeea18
JB
452 const chargePointStatus: OCPP16ChargePointStatus =
453 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
454 ? OCPP16ChargePointStatus.AVAILABLE
455 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
456 if (connectorId === 0) {
457 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
734d790d
JB
458 for (const id of this.chargingStation.connectors.keys()) {
459 if (this.chargingStation.getConnectorStatus(id)?.transactionStarted) {
c0560973
JB
460 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
461 }
734d790d 462 this.chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 463 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
e7aeea18
JB
464 await this.chargingStation.ocppRequestService.sendStatusNotification(
465 id,
466 chargePointStatus
467 );
734d790d 468 this.chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
469 }
470 }
471 return response;
e7aeea18
JB
472 } else if (
473 connectorId > 0 &&
474 (this.chargingStation.getConnectorStatus(0).availability ===
475 OCPP16AvailabilityType.OPERATIVE ||
476 (this.chargingStation.getConnectorStatus(0).availability ===
477 OCPP16AvailabilityType.INOPERATIVE &&
478 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
479 ) {
734d790d
JB
480 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
481 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
482 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
483 }
734d790d 484 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
e7aeea18
JB
485 await this.chargingStation.ocppRequestService.sendStatusNotification(
486 connectorId,
487 chargePointStatus
488 );
734d790d 489 this.chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
490 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
491 }
492 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
493 }
494
e7aeea18
JB
495 private async handleRequestRemoteStartTransaction(
496 commandPayload: RemoteStartTransactionRequest
497 ): Promise<DefaultResponse> {
a7fc8211
JB
498 const transactionConnectorId: number = commandPayload.connectorId;
499 if (transactionConnectorId) {
e7aeea18
JB
500 await this.chargingStation.ocppRequestService.sendStatusNotification(
501 transactionConnectorId,
502 OCPP16ChargePointStatus.PREPARING
503 );
504 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
505 OCPP16ChargePointStatus.PREPARING;
506 if (
507 this.chargingStation.isChargingStationAvailable() &&
508 this.chargingStation.isConnectorAvailable(transactionConnectorId)
509 ) {
e060fe58 510 // Check if authorized
a7fc8211
JB
511 if (this.chargingStation.getAuthorizeRemoteTxRequests()) {
512 let authorized = false;
e7aeea18
JB
513 if (
514 this.chargingStation.getLocalAuthListEnabled() &&
515 this.chargingStation.hasAuthorizedTags() &&
516 this.chargingStation.authorizedTags.find((value) => value === commandPayload.idTag)
517 ) {
518 this.chargingStation.getConnectorStatus(transactionConnectorId).localAuthorizeIdTag =
519 commandPayload.idTag;
520 this.chargingStation.getConnectorStatus(transactionConnectorId).idTagLocalAuthorized =
521 true;
36f6a92e 522 authorized = true;
71068fb9 523 } else if (this.chargingStation.getMayAuthorizeAtRemoteStart()) {
e7aeea18
JB
524 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(
525 transactionConnectorId,
526 commandPayload.idTag
527 );
a7fc8211
JB
528 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
529 authorized = true;
a7fc8211 530 }
71068fb9 531 } else {
e7aeea18
JB
532 logger.warn(
533 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
534 );
a7fc8211
JB
535 }
536 if (authorized) {
537 // Authorization successful, start transaction
e7aeea18
JB
538 if (
539 this.setRemoteStartTransactionChargingProfile(
540 transactionConnectorId,
541 commandPayload.chargingProfile
542 )
543 ) {
544 this.chargingStation.getConnectorStatus(
545 transactionConnectorId
546 ).transactionRemoteStarted = true;
547 if (
548 (
549 await this.chargingStation.ocppRequestService.sendStartTransaction(
550 transactionConnectorId,
551 commandPayload.idTag
552 )
553 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
554 ) {
555 logger.debug(
556 this.chargingStation.logPrefix() +
557 ' Transaction remotely STARTED on ' +
558 this.chargingStation.stationInfo.chargingStationId +
559 '#' +
560 transactionConnectorId.toString() +
561 ' for idTag ' +
562 commandPayload.idTag
563 );
e060fe58
JB
564 return Constants.OCPP_RESPONSE_ACCEPTED;
565 }
e7aeea18
JB
566 return this.notifyRemoteStartTransactionRejected(
567 transactionConnectorId,
568 commandPayload.idTag
569 );
e060fe58 570 }
e7aeea18
JB
571 return this.notifyRemoteStartTransactionRejected(
572 transactionConnectorId,
573 commandPayload.idTag
574 );
a7fc8211 575 }
e7aeea18
JB
576 return this.notifyRemoteStartTransactionRejected(
577 transactionConnectorId,
578 commandPayload.idTag
579 );
36f6a92e 580 }
a7fc8211 581 // No authorization check required, start transaction
e7aeea18
JB
582 if (
583 this.setRemoteStartTransactionChargingProfile(
584 transactionConnectorId,
585 commandPayload.chargingProfile
586 )
587 ) {
588 this.chargingStation.getConnectorStatus(transactionConnectorId).transactionRemoteStarted =
589 true;
590 if (
591 (
592 await this.chargingStation.ocppRequestService.sendStartTransaction(
593 transactionConnectorId,
594 commandPayload.idTag
595 )
596 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
597 ) {
598 logger.debug(
599 this.chargingStation.logPrefix() +
600 ' Transaction remotely STARTED on ' +
601 this.chargingStation.stationInfo.chargingStationId +
602 '#' +
603 transactionConnectorId.toString() +
604 ' for idTag ' +
605 commandPayload.idTag
606 );
e060fe58
JB
607 return Constants.OCPP_RESPONSE_ACCEPTED;
608 }
e7aeea18
JB
609 return this.notifyRemoteStartTransactionRejected(
610 transactionConnectorId,
611 commandPayload.idTag
612 );
e060fe58 613 }
e7aeea18
JB
614 return this.notifyRemoteStartTransactionRejected(
615 transactionConnectorId,
616 commandPayload.idTag
617 );
c0560973 618 }
e7aeea18
JB
619 return this.notifyRemoteStartTransactionRejected(
620 transactionConnectorId,
621 commandPayload.idTag
622 );
c0560973 623 }
57939a9d 624 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
a7fc8211
JB
625 }
626
e7aeea18
JB
627 private async notifyRemoteStartTransactionRejected(
628 connectorId: number,
629 idTag: string
630 ): Promise<DefaultResponse> {
631 if (
632 this.chargingStation.getConnectorStatus(connectorId).status !==
633 OCPP16ChargePointStatus.AVAILABLE
634 ) {
635 await this.chargingStation.ocppRequestService.sendStatusNotification(
636 connectorId,
637 OCPP16ChargePointStatus.AVAILABLE
638 );
639 this.chargingStation.getConnectorStatus(connectorId).status =
640 OCPP16ChargePointStatus.AVAILABLE;
e060fe58 641 }
e7aeea18
JB
642 logger.warn(
643 this.chargingStation.logPrefix() +
644 ' Remote starting transaction REJECTED on connector Id ' +
645 connectorId.toString() +
646 ', idTag ' +
647 idTag +
648 ', availability ' +
649 this.chargingStation.getConnectorStatus(connectorId).availability +
650 ', status ' +
651 this.chargingStation.getConnectorStatus(connectorId).status
652 );
c0560973
JB
653 return Constants.OCPP_RESPONSE_REJECTED;
654 }
655
e7aeea18
JB
656 private setRemoteStartTransactionChargingProfile(
657 connectorId: number,
658 cp: OCPP16ChargingProfile
659 ): boolean {
a7fc8211
JB
660 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
661 this.chargingStation.setChargingProfile(connectorId, cp);
e7aeea18
JB
662 logger.debug(
663 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`,
664 this.chargingStation.getConnectorStatus(connectorId).chargingProfiles
665 );
a7fc8211
JB
666 return true;
667 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18
JB
668 logger.warn(
669 `${this.chargingStation.logPrefix()} Not allowed to set ${
670 cp.chargingProfilePurpose
671 } charging profile(s) at remote start transaction`
672 );
a7fc8211 673 return false;
e060fe58
JB
674 } else if (!cp) {
675 return true;
a7fc8211
JB
676 }
677 }
678
e7aeea18
JB
679 private async handleRequestRemoteStopTransaction(
680 commandPayload: RemoteStopTransactionRequest
681 ): Promise<DefaultResponse> {
c0560973 682 const transactionId = commandPayload.transactionId;
734d790d 683 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
684 if (
685 connectorId > 0 &&
686 this.chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
687 ) {
688 await this.chargingStation.ocppRequestService.sendStatusNotification(
689 connectorId,
690 OCPP16ChargePointStatus.FINISHING
691 );
692 this.chargingStation.getConnectorStatus(connectorId).status =
693 OCPP16ChargePointStatus.FINISHING;
694 await this.chargingStation.ocppRequestService.sendStopTransaction(
695 transactionId,
696 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
697 this.chargingStation.getTransactionIdTag(transactionId)
698 );
c0560973
JB
699 return Constants.OCPP_RESPONSE_ACCEPTED;
700 }
701 }
e7aeea18
JB
702 logger.info(
703 this.chargingStation.logPrefix() +
704 ' Trying to remote stop a non existing transaction ' +
705 transactionId.toString()
706 );
c0560973
JB
707 return Constants.OCPP_RESPONSE_REJECTED;
708 }
47e22477 709
e7aeea18
JB
710 private async handleRequestGetDiagnostics(
711 commandPayload: GetDiagnosticsRequest
712 ): Promise<GetDiagnosticsResponse> {
713 logger.debug(
714 this.chargingStation.logPrefix() +
715 ' ' +
716 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
717 ' request received: %j',
718 commandPayload
719 );
a3868ec4 720 const uri = new URL(commandPayload.location);
47e22477
JB
721 if (uri.protocol.startsWith('ftp:')) {
722 let ftpClient: Client;
723 try {
e7aeea18
JB
724 const logFiles = fs
725 .readdirSync(path.resolve(__dirname, '../../../../'))
726 .filter((file) => file.endsWith('.log'))
727 .map((file) => path.join('./', file));
728 const diagnosticsArchive =
729 this.chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
730 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
731 ftpClient = new Client();
732 const accessResponse = await ftpClient.access({
733 host: uri.host,
e8191622
JB
734 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
735 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
736 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
737 });
738 let uploadResponse: FTPResponse;
739 if (accessResponse.code === 220) {
740 // eslint-disable-next-line @typescript-eslint/no-misused-promises
741 ftpClient.trackProgress(async (info) => {
e7aeea18
JB
742 logger.info(
743 `${this.chargingStation.logPrefix()} ${
744 info.bytes / 1024
745 } bytes transferred from diagnostics archive ${info.name}`
746 );
747 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
748 OCPP16DiagnosticsStatus.Uploading
749 );
47e22477 750 });
e7aeea18
JB
751 uploadResponse = await ftpClient.uploadFrom(
752 path.join(path.resolve(__dirname, '../../../../'), diagnosticsArchive),
753 uri.pathname + diagnosticsArchive
754 );
47e22477 755 if (uploadResponse.code === 226) {
e7aeea18
JB
756 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
757 OCPP16DiagnosticsStatus.Uploaded
758 );
47e22477
JB
759 if (ftpClient) {
760 ftpClient.close();
761 }
762 return { fileName: diagnosticsArchive };
763 }
e7aeea18
JB
764 throw new OCPPError(
765 ErrorType.GENERIC_ERROR,
766 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
767 uploadResponse?.code && '|' + uploadResponse?.code.toString()
768 }`,
769 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
770 );
47e22477 771 }
e7aeea18
JB
772 throw new OCPPError(
773 ErrorType.GENERIC_ERROR,
774 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
775 uploadResponse?.code && '|' + uploadResponse?.code.toString()
776 }`,
777 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
778 );
47e22477 779 } catch (error) {
e7aeea18
JB
780 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
781 OCPP16DiagnosticsStatus.UploadFailed
782 );
47e22477
JB
783 if (ftpClient) {
784 ftpClient.close();
785 }
e7aeea18
JB
786 return this.handleIncomingRequestError(
787 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
788 error as Error,
789 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
790 );
47e22477
JB
791 }
792 } else {
e7aeea18
JB
793 logger.error(
794 `${this.chargingStation.logPrefix()} Unsupported protocol ${
795 uri.protocol
796 } to transfer the diagnostic logs archive`
797 );
798 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
799 OCPP16DiagnosticsStatus.UploadFailed
800 );
47e22477
JB
801 return Constants.OCPP_RESPONSE_EMPTY;
802 }
803 }
802cfa13 804
e7aeea18
JB
805 private handleRequestTriggerMessage(
806 commandPayload: OCPP16TriggerMessageRequest
807 ): OCPP16TriggerMessageResponse {
802cfa13
JB
808 try {
809 switch (commandPayload.requestedMessage) {
810 case MessageTrigger.BootNotification:
811 setTimeout(() => {
e7aeea18
JB
812 this.chargingStation.ocppRequestService
813 .sendBootNotification(
814 this.chargingStation.getBootNotificationRequest().chargePointModel,
815 this.chargingStation.getBootNotificationRequest().chargePointVendor,
816 this.chargingStation.getBootNotificationRequest().chargeBoxSerialNumber,
817 this.chargingStation.getBootNotificationRequest().firmwareVersion,
818 this.chargingStation.getBootNotificationRequest().chargePointSerialNumber,
819 this.chargingStation.getBootNotificationRequest().iccid,
820 this.chargingStation.getBootNotificationRequest().imsi,
821 this.chargingStation.getBootNotificationRequest().meterSerialNumber,
822 this.chargingStation.getBootNotificationRequest().meterType,
823 { triggerMessage: true }
824 )
825 .catch(() => {
826 /* This is intentional */
827 });
802cfa13
JB
828 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
829 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
830 case MessageTrigger.Heartbeat:
831 setTimeout(() => {
e7aeea18
JB
832 this.chargingStation.ocppRequestService
833 .sendHeartbeat({ triggerMessage: true })
834 .catch(() => {
835 /* This is intentional */
836 });
802cfa13
JB
837 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
838 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
839 default:
840 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
841 }
842 } catch (error) {
e7aeea18
JB
843 return this.handleIncomingRequestError(
844 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
845 error as Error,
846 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
847 );
802cfa13
JB
848 }
849 }
c0560973 850}