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