UI Protocol: Expose ATG status and use array for all list
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 3 Sep 2022 15:16:55 +0000 (17:16 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 3 Sep 2022 15:16:55 +0000 (17:16 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
25 files changed:
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationUtils.ts
src/charging-station/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/MessageChannelUtils.ts
src/charging-station/UIServiceWorkerBroadcastChannel.ts
src/charging-station/WorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/types/ChargingStationWorker.ts
src/ui/web/package-lock.json
src/ui/web/package.json
src/ui/web/src/components/charging-stations/CSData.vue
src/ui/web/src/components/charging-stations/CSTable.vue
src/ui/web/src/composables/UIClient.ts
src/ui/web/src/types/ChargingStationType.ts
src/ui/web/src/types/UIProtocol.ts
src/ui/web/src/views/ChargingStationsView.vue
src/ui/web/tests/unit/CSTable.spec.ts
src/utils/Utils.ts

index bf28e55bfdcbc9e4894996f1b383d47a3e0af0b8..e8a4b50f1a1b19a9c72c44d0d0f7117beec6a9e7 100644 (file)
@@ -5,8 +5,7 @@ import type {
   AutomaticTransactionGeneratorConfiguration,
   Status,
 } from '../types/AutomaticTransactionGenerator';
-import { MeterValuesRequest, RequestCommand } from '../types/ocpp/Requests';
-import type { MeterValuesResponse } from '../types/ocpp/Responses';
+import { RequestCommand } from '../types/ocpp/Requests';
 import {
   AuthorizationStatus,
   AuthorizeRequest,
@@ -14,14 +13,12 @@ import {
   StartTransactionRequest,
   StartTransactionResponse,
   StopTransactionReason,
-  StopTransactionRequest,
   StopTransactionResponse,
 } from '../types/ocpp/Transaction';
 import Constants from '../utils/Constants';
 import logger from '../utils/Logger';
 import Utils from '../utils/Utils';
 import type ChargingStation from './ChargingStation';
-import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
 
 export default class AutomaticTransactionGenerator {
   private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<
@@ -29,10 +26,10 @@ export default class AutomaticTransactionGenerator {
     AutomaticTransactionGenerator
   >();
 
+  public readonly connectorsStatus: Map<number, Status>;
   public readonly configuration: AutomaticTransactionGeneratorConfiguration;
   public started: boolean;
   private readonly chargingStation: ChargingStation;
-  private readonly connectorsStatus: Map<number, Status>;
 
   private constructor(
     automaticTransactionGeneratorConfiguration: AutomaticTransactionGeneratorConfiguration,
@@ -344,48 +341,16 @@ export default class AutomaticTransactionGenerator {
 
   private async stopTransaction(
     connectorId: number,
-    reason: StopTransactionReason = StopTransactionReason.NONE
+    reason: StopTransactionReason = StopTransactionReason.LOCAL
   ): Promise<StopTransactionResponse> {
     const measureId = 'StopTransaction with ATG';
     const beginId = PerformanceStatistics.beginMeasure(measureId);
-    let transactionId = 0;
     let stopResponse: StopTransactionResponse;
     if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
-      transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
-      if (
-        this.chargingStation.getBeginEndMeterValues() &&
-        this.chargingStation.getOcppStrictCompliance() &&
-        !this.chargingStation.getOutOfOrderEndMeterValues()
-      ) {
-        // FIXME: Implement OCPP version agnostic helpers
-        const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
-          this.chargingStation,
-          connectorId,
-          this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)
-        );
-        await this.chargingStation.ocppRequestService.requestHandler<
-          MeterValuesRequest,
-          MeterValuesResponse
-        >(this.chargingStation, RequestCommand.METER_VALUES, {
-          connectorId,
-          transactionId,
-          meterValue: [transactionEndMeterValue],
-        });
-      }
-      stopResponse = await this.chargingStation.ocppRequestService.requestHandler<
-        StopTransactionRequest,
-        StopTransactionResponse
-      >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
-        transactionId,
-        meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
-          transactionId,
-          true
-        ),
-        idTag: this.chargingStation.getTransactionIdTag(transactionId),
-        reason,
-      });
+      stopResponse = await this.chargingStation.stopTransactionOnConnector(connectorId, reason);
       this.connectorsStatus.get(connectorId).stopTransactionRequests++;
     } else {
+      const transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
       logger.warn(
         `${this.logPrefix(connectorId)} trying to stop a not started transaction${
           transactionId ? ' ' + transactionId.toString() : ''
index 78db2058c3bcac8b170a823b354cb7431a5b1013..c42ff2b9625b126cdfccf0ce9f5df145315d644d 100644 (file)
@@ -425,13 +425,13 @@ export default class ChargingStation {
       );
       return;
     }
-    if (!this.getConnectorStatus(connectorId)?.transactionStarted) {
+    if (this.getConnectorStatus(connectorId)?.transactionStarted === false) {
       logger.error(
         `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`
       );
       return;
     } else if (
-      this.getConnectorStatus(connectorId)?.transactionStarted &&
+      this.getConnectorStatus(connectorId)?.transactionStarted === true &&
       !this.getConnectorStatus(connectorId)?.transactionId
     ) {
       logger.error(
@@ -519,9 +519,7 @@ export default class ChargingStation {
     parentPort.postMessage(MessageChannelUtils.buildStartedMessage(this));
   }
 
-  public async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise<void> {
-    // Stop message sequence
-    await this.stopMessageSequence(reason);
+  public async stop(): Promise<void> {
     for (const connectorId of this.connectors.keys()) {
       if (connectorId > 0) {
         await this.ocppRequestService.requestHandler<
@@ -547,8 +545,8 @@ export default class ChargingStation {
     parentPort.postMessage(MessageChannelUtils.buildStoppedMessage(this));
   }
 
-  public async reset(reason?: StopTransactionReason): Promise<void> {
-    await this.stop(reason);
+  public async reset(): Promise<void> {
+    await this.stop();
     await Utils.sleep(this.stationInfo.resetTime);
     this.initialize();
     this.start();
@@ -596,7 +594,6 @@ export default class ChargingStation {
                 ? limit
                 : DCElectricUtils.power(this.getVoltageOut(), limit);
         }
-
         const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
         if (limit > connectorMaximumPower) {
           logger.error(
@@ -770,6 +767,62 @@ export default class ChargingStation {
     }
   }
 
+  public getNumberOfRunningTransactions(): number {
+    let trxCount = 0;
+    for (const connectorId of this.connectors.keys()) {
+      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+        trxCount++;
+      }
+    }
+    return trxCount;
+  }
+
+  public async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
+    for (const connectorId of this.connectors.keys()) {
+      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+        await this.stopTransactionOnConnector(connectorId, reason);
+      }
+    }
+  }
+
+  public async stopTransactionOnConnector(
+    connectorId: number,
+    reason = StopTransactionReason.NONE
+  ): Promise<StopTransactionResponse> {
+    const transactionId = this.getConnectorStatus(connectorId).transactionId;
+    if (
+      this.getBeginEndMeterValues() &&
+      this.getOcppStrictCompliance() &&
+      !this.getOutOfOrderEndMeterValues()
+    ) {
+      // FIXME: Implement OCPP version agnostic helpers
+      const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
+        this,
+        connectorId,
+        this.getEnergyActiveImportRegisterByTransactionId(transactionId)
+      );
+      await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
+        this,
+        RequestCommand.METER_VALUES,
+        {
+          connectorId,
+          transactionId,
+          meterValue: [transactionEndMeterValue],
+        }
+      );
+    }
+    return this.ocppRequestService.requestHandler<StopTransactionRequest, StopTransactionResponse>(
+      this,
+      RequestCommand.STOP_TRANSACTION,
+      {
+        transactionId,
+        meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
+        idTag: this.getTransactionIdTag(transactionId),
+        reason,
+      }
+    );
+  }
+
   private flushMessageBuffer(): void {
     if (this.messageBuffer.size > 0) {
       this.messageBuffer.forEach((message) => {
@@ -1233,7 +1286,7 @@ export default class ChargingStation {
     }
     // Initialize transaction attributes on connectors
     for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && !this.getConnectorStatus(connectorId)?.transactionStarted) {
+      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === false) {
         this.initializeConnectorStatus(connectorId);
       }
     }
@@ -1395,6 +1448,7 @@ export default class ChargingStation {
       this.started === false && (this.started = true);
       this.autoReconnectRetryCount = 0;
       this.wsConnectionRestarted = false;
+      parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
     } else {
       logger.warn(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
@@ -1408,22 +1462,24 @@ export default class ChargingStation {
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
       case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
         logger.info(
-          `${this.logPrefix()} WebSocket normally closed with status '${ChargingStationUtils.getWebSocketCloseEventStatusString(
+          `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
             code
           )}' and reason '${reason}'`
         );
         this.autoReconnectRetryCount = 0;
+        await this.stopMessageSequence(StopTransactionReason.OTHER);
         break;
       // Abnormal close
       default:
         logger.error(
-          `${this.logPrefix()} WebSocket abnormally closed with status '${ChargingStationUtils.getWebSocketCloseEventStatusString(
+          `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
             code
           )}' and reason '${reason}'`
         );
         await this.reconnect(code);
         break;
     }
+    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
   private async onMessage(data: Data): Promise<void> {
@@ -1606,16 +1662,6 @@ export default class ChargingStation {
       : true;
   }
 
-  private getNumberOfRunningTransactions(): number {
-    let trxCount = 0;
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted) {
-        trxCount++;
-      }
-    }
-    return trxCount;
-  }
-
   // 0 for disabling
   private getConnectionTimeout(): number | undefined {
     if (
@@ -1804,41 +1850,7 @@ export default class ChargingStation {
       if (this.automaticTransactionGenerator?.started) {
         this.stopAutomaticTransactionGenerator();
       } else {
-        for (const connectorId of this.connectors.keys()) {
-          if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted) {
-            const transactionId = this.getConnectorStatus(connectorId).transactionId;
-            if (
-              this.getBeginEndMeterValues() &&
-              this.getOcppStrictCompliance() &&
-              !this.getOutOfOrderEndMeterValues()
-            ) {
-              // FIXME: Implement OCPP version agnostic helpers
-              const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
-                this,
-                connectorId,
-                this.getEnergyActiveImportRegisterByTransactionId(transactionId)
-              );
-              await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
-                this,
-                RequestCommand.METER_VALUES,
-                {
-                  connectorId,
-                  transactionId,
-                  meterValue: [transactionEndMeterValue],
-                }
-              );
-            }
-            await this.ocppRequestService.requestHandler<
-              StopTransactionRequest,
-              StopTransactionResponse
-            >(this, RequestCommand.STOP_TRANSACTION, {
-              transactionId,
-              meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
-              idTag: this.getTransactionIdTag(transactionId),
-              reason,
-            });
-          }
-        }
+        await this.stopRunningTransactions(reason);
       }
     }
   }
index 60e143faaa51c55a470b453fe623d3379dc12370..28f38b4619738e81ba07708dc5c5676bf93017e7 100644 (file)
@@ -21,7 +21,6 @@ import {
   IncomingRequestCommand,
   RequestCommand,
 } from '../types/ocpp/Requests';
-import { WebSocketCloseEventStatusString } from '../types/WebSocket';
 import { WorkerProcessType } from '../types/Worker';
 import Configuration from '../utils/Configuration';
 import Constants from '../utils/Constants';
@@ -176,32 +175,6 @@ export class ChargingStationUtils {
     return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL;
   }
 
-  /**
-   * Convert websocket error code to human readable string message
-   *
-   * @param code websocket error code
-   * @returns human readable string message
-   */
-  public static getWebSocketCloseEventStatusString(code: number): string {
-    if (code >= 0 && code <= 999) {
-      return '(Unused)';
-    } else if (code >= 1016) {
-      if (code <= 1999) {
-        return '(For WebSocket standard)';
-      } else if (code <= 2999) {
-        return '(For WebSocket extensions)';
-      } else if (code <= 3999) {
-        return '(For libraries and frameworks)';
-      } else if (code <= 4999) {
-        return '(For applications)';
-      }
-    }
-    if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) {
-      return WebSocketCloseEventStatusString[code] as string;
-    }
-    return '(Unknown)';
-  }
-
   public static warnDeprecatedTemplateKey(
     template: ChargingStationTemplate,
     key: string,
index ecc3ab49a36a44fc5a38aab0ee120460c6a7e887..21b83b9f3f92894c6e3f3b4c5fbea169d39e0b6c 100644 (file)
@@ -38,9 +38,8 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
     if (this.isResponse(messageEvent.data)) {
       return;
     }
-    this.validateMessageEvent(messageEvent);
-
-    const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
+    const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent)
+      .data as BroadcastChannelRequest;
 
     if (requestPayload?.hashIds !== undefined || requestPayload?.hashId !== undefined) {
       if (
@@ -98,7 +97,7 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
   private messageErrorHandler(messageEvent: MessageEvent): void {
     logger.error(
       `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
-      { messageEvent, messageEventData: messageEvent.data }
+      { messageEvent }
     );
   }
 
@@ -138,7 +137,7 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
             true
           ),
           idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
-          reason: StopTransactionReason.NONE,
+          reason: requestPayload.reason ?? StopTransactionReason.NONE,
         });
       case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
         this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
index e87cdbc0b6cbaa4d326cd55e79a10b5a46bc872a..19266f73f6fee0348814dd7e4643838c50376a74 100644 (file)
@@ -53,11 +53,17 @@ export class MessageChannelUtils {
     return {
       stationInfo: chargingStation.stationInfo,
       started: chargingStation.started,
+      wsState: chargingStation?.wsConnection?.readyState,
       bootNotificationResponse: chargingStation.bootNotificationResponse,
       connectors: [...chargingStation.connectors.values()].map(
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
       ),
+      ...(chargingStation.automaticTransactionGenerator && {
+        automaticTransactionGeneratorStatuses: [
+          ...chargingStation.automaticTransactionGenerator.connectorsStatus.values(),
+        ],
+      }),
     };
   }
 }
index 652d825a537b2a7d60233ca4e6f14734731ec385..a2b209220ae084ba84e1fa7accda1e782443929b 100644 (file)
@@ -32,8 +32,8 @@ export default class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChan
     if (this.isRequest(messageEvent.data)) {
       return;
     }
-    this.validateMessageEvent(messageEvent);
-    const [uuid, responsePayload] = messageEvent.data as BroadcastChannelResponse;
+    const [uuid, responsePayload] = this.validateMessageEvent(messageEvent)
+      .data as BroadcastChannelResponse;
     if (this.responses.has(uuid) === false) {
       this.responses.set(uuid, {
         responsesExpected: this.uiService.getBroadcastChannelExpectedResponses(uuid),
@@ -89,7 +89,7 @@ export default class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChan
   private messageErrorHandler(messageEvent: MessageEvent): void {
     logger.error(
       `${this.uiService.logPrefix(moduleName, 'messageErrorHandler')} Error at handling message:`,
-      { messageEvent, messageEventData: messageEvent.data }
+      { messageEvent }
     );
   }
 }
index b19bf946dc633db77e5c690e75dd4b921de6868a..2fa082dddfd844fb872068f91a03e7c957f60ccb 100644 (file)
@@ -29,9 +29,10 @@ export default abstract class WorkerBroadcastChannel extends BroadcastChannel {
     return Array.isArray(message) && message.length === 2;
   }
 
-  protected validateMessageEvent(messageEvent: MessageEvent): void {
+  protected validateMessageEvent(messageEvent: MessageEvent): MessageEvent {
     if (Array.isArray(messageEvent.data) === false) {
       throw new BaseError('Worker broadcast channel protocol message event data is not an array');
     }
+    return messageEvent;
   }
 }
index 0589da01c4d6fc3452009275ac1e64a4bffe0b3a..e1d29e6f791bfe618d753de8287ca6dc3b43aeaf 100644 (file)
@@ -21,10 +21,6 @@ import {
   OCPP16SupportedFeatureProfiles,
 } from '../../../types/ocpp/1.6/Configuration';
 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
-import type {
-  OCPP16MeterValuesRequest,
-  OCPP16MeterValuesResponse,
-} from '../../../types/ocpp/1.6/MeterValues';
 import {
   ChangeAvailabilityRequest,
   ChangeConfigurationRequest,
@@ -68,13 +64,12 @@ import {
   OCPP16StartTransactionRequest,
   OCPP16StartTransactionResponse,
   OCPP16StopTransactionReason,
-  OCPP16StopTransactionRequest,
-  OCPP16StopTransactionResponse,
 } from '../../../types/ocpp/1.6/Transaction';
 import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
 import type { DefaultResponse } from '../../../types/ocpp/Responses';
+import { StopTransactionReason } from '../../../types/ocpp/Transaction';
 import Constants from '../../../utils/Constants';
 import logger from '../../../utils/Logger';
 import Utils from '../../../utils/Utils';
@@ -386,7 +381,12 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
   ): DefaultResponse {
     // eslint-disable-next-line @typescript-eslint/no-misused-promises
     setImmediate(async (): Promise<void> => {
-      await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
+      if (chargingStation.getNumberOfRunningTransactions() > 0) {
+        await chargingStation.stopRunningTransactions(
+          (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
+        );
+      }
+      await chargingStation.reset();
     });
     logger.info(
       `${chargingStation.logPrefix()} ${
@@ -413,40 +413,11 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
       );
       return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
     }
-    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
-      const transactionId = chargingStation.getConnectorStatus(connectorId).transactionId;
-      if (
-        chargingStation.getBeginEndMeterValues() &&
-        chargingStation.getOcppStrictCompliance() &&
-        !chargingStation.getOutOfOrderEndMeterValues()
-      ) {
-        // FIXME: Implement OCPP version agnostic helpers
-        const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
-          chargingStation,
-          connectorId,
-          chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)
-        );
-        await chargingStation.ocppRequestService.requestHandler<
-          OCPP16MeterValuesRequest,
-          OCPP16MeterValuesResponse
-        >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
-          connectorId,
-          transactionId,
-          meterValue: [transactionEndMeterValue],
-        });
-      }
-      const stopResponse = await chargingStation.ocppRequestService.requestHandler<
-        OCPP16StopTransactionRequest,
-        OCPP16StopTransactionResponse
-      >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
-        transactionId,
-        meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
-          transactionId,
-          true
-        ),
-        idTag: chargingStation.getTransactionIdTag(transactionId),
-        reason: OCPP16StopTransactionReason.UNLOCK_COMMAND,
-      });
+    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
+      const stopResponse = await chargingStation.stopTransactionOnConnector(
+        connectorId,
+        OCPP16StopTransactionReason.UNLOCK_COMMAND
+      );
       if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
         return Constants.OCPP_RESPONSE_UNLOCKED;
       }
@@ -599,7 +570,8 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
       commandPayload.csChargingProfiles.chargingProfilePurpose ===
         ChargingProfilePurposeType.TX_PROFILE &&
       (commandPayload.connectorId === 0 ||
-        !chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)
+        chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
+          false)
     ) {
       return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
     }
@@ -715,7 +687,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     if (connectorId === 0) {
       let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
       for (const id of chargingStation.connectors.keys()) {
-        if (chargingStation.getConnectorStatus(id)?.transactionStarted) {
+        if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
           response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
         }
         chargingStation.getConnectorStatus(id).availability = commandPayload.type;
@@ -739,7 +711,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
           OCPP16AvailabilityType.INOPERATIVE &&
           commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
     ) {
-      if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
+      if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
         chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
         return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
       }
@@ -982,38 +954,14 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
           errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
         });
         chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
-        if (
-          chargingStation.getBeginEndMeterValues() &&
-          chargingStation.getOcppStrictCompliance() &&
-          !chargingStation.getOutOfOrderEndMeterValues()
-        ) {
-          // FIXME: Implement OCPP version agnostic helpers
-          const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
-            chargingStation,
-            connectorId,
-            chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)
-          );
-          await chargingStation.ocppRequestService.requestHandler<
-            OCPP16MeterValuesRequest,
-            OCPP16MeterValuesResponse
-          >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
-            connectorId,
-            transactionId,
-            meterValue: [transactionEndMeterValue],
-          });
+        const stopResponse = await chargingStation.stopTransactionOnConnector(
+          connectorId,
+          StopTransactionReason.REMOTE
+        );
+        if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+          return Constants.OCPP_RESPONSE_ACCEPTED;
         }
-        await chargingStation.ocppRequestService.requestHandler<
-          OCPP16StopTransactionRequest,
-          OCPP16StopTransactionResponse
-        >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
-          transactionId,
-          meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
-            transactionId,
-            true
-          ),
-          idTag: chargingStation.getTransactionIdTag(transactionId),
-        });
-        return Constants.OCPP_RESPONSE_ACCEPTED;
+        return Constants.OCPP_RESPONSE_REJECTED;
       }
     }
     logger.warn(
index 9e4dfd083d11d63358ce5b13a0b0d988b97db2dd..c9f491780eda37975e055194705e3b768aff42d7 100644 (file)
@@ -330,11 +330,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (
-      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
+      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
       chargingStation.getAuthorizeRemoteTxRequests() &&
       chargingStation.getLocalAuthListEnabled() &&
       chargingStation.hasAuthorizedTags() &&
-      !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized
+      chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false
     ) {
       logger.error(
         chargingStation.logPrefix() +
@@ -347,11 +347,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (
-      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
+      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
       chargingStation.getAuthorizeRemoteTxRequests() &&
       chargingStation.getMustAuthorizeAtRemoteStart() &&
-      !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
-      !chargingStation.getConnectorStatus(connectorId).idTagAuthorized
+      chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false &&
+      chargingStation.getConnectorStatus(connectorId).idTagAuthorized === false
     ) {
       logger.error(
         chargingStation.logPrefix() +
@@ -395,7 +395,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
       return;
     }
-    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
+    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
       logger.debug(
         chargingStation.logPrefix() +
           ' Trying to start a transaction on an already used connector ' +
index 25c8943564a7f91afc639c22a7f99a0a28d247d9..e00e2b99f9a2bbbddbdca10a6ae1427ffe982d2c 100644 (file)
@@ -1,3 +1,5 @@
+import { parentPort } from 'worker_threads';
+
 import type { JSONSchemaType } from 'ajv';
 import Ajv from 'ajv-draft-04';
 import ajvFormats from 'ajv-formats';
@@ -21,6 +23,7 @@ import Constants from '../../utils/Constants';
 import logger from '../../utils/Logger';
 import Utils from '../../utils/Utils';
 import type ChargingStation from '../ChargingStation';
+import { MessageChannelUtils } from '../MessageChannelUtils';
 import type OCPPResponseService from './OCPPResponseService';
 import { OCPPServiceUtils } from './OCPPServiceUtils';
 
@@ -251,6 +254,7 @@ export default abstract class OCPPRequestService {
               reject(error);
             } finally {
               chargingStation.requests.delete(messageId);
+              // parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(chargingStation));
             }
           }
 
@@ -274,6 +278,7 @@ export default abstract class OCPPRequestService {
               error
             );
             chargingStation.requests.delete(messageId);
+            // parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(chargingStation));
             reject(error);
           }
         }),
index 91c69a76765b835447988ba46e106ed87829caf2..9a19d39d9a6d29550c614b937f14fd18bcb514e3 100644 (file)
@@ -15,8 +15,8 @@ import type AbstractUIService from './ui-services/AbstractUIService';
 
 export abstract class AbstractUIServer {
   public readonly chargingStations: Map<string, ChargingStationData>;
-  protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>;
   protected server: WebSocket.Server | HttpServer;
+  protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>;
 
   public constructor() {
     this.chargingStations = new Map<string, ChargingStationData>();
@@ -27,17 +27,21 @@ export abstract class AbstractUIServer {
     id: string,
     procedureName: ProcedureName,
     requestPayload: RequestPayload
-  ): string {
-    return JSON.stringify([id, procedureName, requestPayload] as ProtocolRequest);
+  ): ProtocolRequest {
+    return [id, procedureName, requestPayload];
   }
 
-  public buildProtocolResponse(id: string, responsePayload: ResponsePayload): string {
-    return JSON.stringify([id, responsePayload] as ProtocolResponse);
+  public buildProtocolResponse(id: string, responsePayload: ResponsePayload): ProtocolResponse {
+    return [id, responsePayload];
   }
 
   public abstract start(): void;
   public abstract stop(): void;
-  public abstract sendRequest(request: string): void;
-  public abstract sendResponse(response: string): void;
-  public abstract logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string;
+  public abstract sendRequest(request: ProtocolRequest): void;
+  public abstract sendResponse(response: ProtocolResponse): void;
+  public abstract logPrefix(
+    moduleName?: string,
+    methodName?: string,
+    prefixSuffix?: string
+  ): string;
 }
index 98c3f1cc7f27c9d42f35efdb82b19664a9a24975..152529373d375f033985f571989ed8e31cc63dba 100644 (file)
@@ -7,6 +7,7 @@ import type { ServerOptions } from '../../types/ConfigurationData';
 import {
   ProcedureName,
   Protocol,
+  ProtocolRequest,
   ProtocolResponse,
   ProtocolVersion,
   RequestPayload,
@@ -44,12 +45,12 @@ export default class UIHttpServer extends AbstractUIServer {
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  public sendRequest(request: string): void {
+  public sendRequest(request: ProtocolRequest): void {
     // This is intentionally left blank
   }
 
-  public sendResponse(response: string): void {
-    const [uuid, payload] = JSON.parse(response) as ProtocolResponse;
+  public sendResponse(response: ProtocolResponse): void {
+    const [uuid, payload] = response;
     const statusCode = this.responseStatusToStatusCode(payload.status);
     if (this.responseHandlers.has(uuid) === true) {
       const { res } = this.responseHandlers.get(uuid);
@@ -59,7 +60,7 @@ export default class UIHttpServer extends AbstractUIServer {
       this.responseHandlers.delete(uuid);
     } else {
       logger.error(
-        `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request: ${response}`
+        `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
       );
     }
   }
index 8e11b66da55e09c5b89dea39a998ed0135a3789d..c4829dcc29d9362d024fde7644c2df8355405218 100644 (file)
@@ -1,8 +1,10 @@
 import type { IncomingMessage } from 'http';
 
-import WebSocket from 'ws';
+import WebSocket, { RawData } from 'ws';
 
+import BaseError from '../../exception/BaseError';
 import type { ServerOptions } from '../../types/ConfigurationData';
+import type { ProtocolRequest, ProtocolResponse } from '../../types/UIProtocol';
 import { WebSocketCloseEventStatusCode } from '../../types/WebSocket';
 import Configuration from '../../utils/Configuration';
 import logger from '../../utils/Logger';
@@ -20,8 +22,8 @@ export default class UIWebSocketServer extends AbstractUIServer {
   }
 
   public start(): void {
-    this.server.on('connection', (socket: WebSocket, request: IncomingMessage): void => {
-      const [protocol, version] = UIServiceUtils.getProtocolAndVersion(socket.protocol);
+    this.server.on('connection', (ws: WebSocket, request: IncomingMessage): void => {
+      const [protocol, version] = UIServiceUtils.getProtocolAndVersion(ws.protocol);
       if (UIServiceUtils.isProtocolAndVersionSupported(protocol, version) === false) {
         logger.error(
           `${this.logPrefix(
@@ -29,24 +31,31 @@ export default class UIWebSocketServer extends AbstractUIServer {
             'start.server.onconnection'
           )} Unsupported UI protocol version: '${protocol}${version}'`
         );
-        socket.close(WebSocketCloseEventStatusCode.CLOSE_PROTOCOL_ERROR);
+        ws.close(WebSocketCloseEventStatusCode.CLOSE_PROTOCOL_ERROR);
       }
       if (!this.uiServices.has(version)) {
         this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
       }
-      // FIXME: check connection validity
-      socket.on('message', (rawData) => {
+      ws.on('message', (rawData) => {
+        const [messageId, procedureName, payload] = this.validateRawDataRequest(rawData);
         this.uiServices
           .get(version)
-          .requestHandler(rawData)
+          .requestHandler(this.buildProtocolRequest(messageId, procedureName, payload))
           .catch(() => {
             /* Error caught by AbstractUIService */
           });
       });
-      socket.on('error', (error) => {
-        logger.error(
-          `${this.logPrefix(moduleName, 'start.socket.onerror')} Error on WebSocket:`,
-          error
+      ws.on('error', (error) => {
+        logger.error(`${this.logPrefix(moduleName, 'start.ws.onerror')} WebSocket error:`, error);
+      });
+      ws.on('close', (code, reason) => {
+        logger.debug(
+          `${this.logPrefix(
+            moduleName,
+            'start.ws.onclose'
+          )} WebSocket closed: '${Utils.getWebSocketCloseEventStatusString(
+            code
+          )}' - '${reason.toString()}'`
         );
       });
     });
@@ -56,13 +65,13 @@ export default class UIWebSocketServer extends AbstractUIServer {
     this.chargingStations.clear();
   }
 
-  public sendRequest(request: string): void {
-    this.broadcastToClients(request);
+  public sendRequest(request: ProtocolRequest): void {
+    this.broadcastToClients(JSON.stringify(request));
   }
 
-  public sendResponse(response: string): void {
+  public sendResponse(response: ProtocolResponse): void {
     // TODO: send response only to the client that sent the request
-    this.broadcastToClients(response);
+    this.broadcastToClients(JSON.stringify(response));
   }
 
   public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
@@ -81,4 +90,25 @@ export default class UIWebSocketServer extends AbstractUIServer {
       }
     }
   }
+
+  private validateRawDataRequest(rawData: RawData): ProtocolRequest {
+    // logger.debug(
+    //   `${this.logPrefix(
+    //     moduleName,
+    //     'validateRawDataRequest'
+    //   )} Raw data received in string format: ${rawData.toString()}`
+    // );
+
+    const request = JSON.parse(rawData.toString()) as ProtocolRequest;
+
+    if (Array.isArray(request) === false) {
+      throw new BaseError('UI protocol request is not an array');
+    }
+
+    if (request.length !== 3) {
+      throw new BaseError('UI protocol request is malformed');
+    }
+
+    return request;
+  }
 }
index c9bc8ff56a6a528a94f691168af37fe336749cc3..7d4f69d0a784b1183883e66f04606f5584a2335e 100644 (file)
@@ -1,8 +1,5 @@
-import type { RawData } from 'ws';
-
 import BaseError from '../../../exception/BaseError';
 import { Bootstrap } from '../../../internal';
-import type { JsonType } from '../../../types/JsonType';
 import {
   ProcedureName,
   ProtocolRequest,
@@ -42,13 +39,13 @@ export default abstract class AbstractUIService {
     this.broadcastChannelRequests = new Map<string, number>();
   }
 
-  public async requestHandler(request: RawData | JsonType): Promise<void> {
+  public async requestHandler(request: ProtocolRequest): Promise<void> {
     let messageId: string;
     let command: ProcedureName;
     let requestPayload: RequestPayload | undefined;
     let responsePayload: ResponsePayload;
     try {
-      [messageId, command, requestPayload] = this.requestValidation(request);
+      [messageId, command, requestPayload] = request;
 
       if (this.requestHandlers.has(command) === false) {
         throw new BaseError(
@@ -133,34 +130,11 @@ export default abstract class AbstractUIService {
     this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses);
   }
 
-  // Validate the raw data received from the UI server
-  private requestValidation(rawData: RawData | JsonType): ProtocolRequest {
-    // logger.debug(
-    //   `${this.logPrefix(
-    //     moduleName,
-    //     'requestValidation'
-    //   )} Data received in string format: ${rawData.toString()}`
-    // );
-
-    const data = JSON.parse(rawData.toString()) as JsonType[];
-
-    if (Array.isArray(data) === false) {
-      throw new BaseError('UI protocol request is not an array');
-    }
-
-    if (data.length !== 3) {
-      throw new BaseError('UI protocol request is malformed');
-    }
-
-    return data as ProtocolRequest;
-  }
-
   private handleListChargingStations(): ResponsePayload {
-    // TODO: remove cast to unknown
     return {
       status: ResponseStatus.SUCCESS,
-      ...[...this.uiServer.chargingStations.values()],
-    } as unknown as ResponsePayload;
+      chargingStations: [...this.uiServer.chargingStations.values()],
+    } as ResponsePayload;
   }
 
   private async handleStartSimulator(): Promise<ResponsePayload> {
index 4cbe8ba7069706ea6080f41b2560705ed39c5f10..020ec7bd9b16295c72c5db64784fb0d86e47e475 100644 (file)
@@ -1,3 +1,4 @@
+import type { Status } from './AutomaticTransactionGenerator';
 import type ChargingStationInfo from './ChargingStationInfo';
 import type { ConnectorStatus } from './ConnectorStatus';
 import type { JsonObject } from './JsonType';
@@ -18,8 +19,10 @@ export interface ChargingStationWorkerData extends WorkerData {
 export interface ChargingStationData extends WorkerData {
   stationInfo: ChargingStationInfo;
   started: boolean;
+  wsState?: number;
   bootNotificationResponse: BootNotificationResponse;
   connectors: ConnectorStatus[];
+  automaticTransactionGeneratorStatuses?: Status[];
 }
 
 enum ChargingStationMessageEvents {
index a5512d2f73cf16a618a0cc02ccd0744315614c69..59fae6d210ac313c9a769720ed6bdbae7a3f6e01 100644 (file)
@@ -1,12 +1,12 @@
 {
   "name": "webui",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "webui",
-      "version": "0.1.0",
+      "version": "0.1.1",
       "dependencies": {
         "core-js": "^3.25.0",
         "finalhandler": "^1.2.0",
index 99e1ee7d0cb8b165af105453f9e4b9ef6a73ccdd..ea6e12a7150495f0c4171abaea402afad91bf7bb 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "webui",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "readme": "README.md",
   "gitHooks": {
     "pre-commit": "lint-staged"
index a6a819aa45fd14fddcfa5bce3410fc27e9199140..186fa7a4957749a0b09facd3034bfa5f40c5bec4 100644 (file)
@@ -9,6 +9,7 @@
     />
     <td class="cs-table__name-col">{{ getId() }}</td>
     <td class="cs-table__started-col">{{ getStarted() }}</td>
+    <td class="cs-table__wsState-col">{{ getWsState() }}</td>
     <td class="cs-table__registration-status-col">{{ getRegistrationStatus() }}</td>
     <td class="cs-table__vendor-col">{{ getVendor() }}</td>
     <td class="cs-table__model-col">{{ getModel() }}</td>
@@ -66,6 +67,20 @@ function getFirmwareVersion(): string {
 function getStarted(): string {
   return props.chargingStation.started === true ? 'Yes' : 'No';
 }
+function getWsState(): string {
+  switch (props.chargingStation?.wsState) {
+    case WebSocket.CONNECTING:
+      return 'Connecting';
+    case WebSocket.OPEN:
+      return 'Open';
+    case WebSocket.CLOSING:
+      return 'Closing';
+    case WebSocket.CLOSED:
+      return 'Closed';
+    default:
+      return 'Ø';
+  }
+}
 function getRegistrationStatus(): string {
   return props.chargingStation?.bootNotificationResponse?.status ?? 'Ø';
 }
index cf0835bcf9a9561686c45a2c0092bf02c1f22667..2c0bc1faec61203fcedc152e812d7526f4f8fb6d 100644 (file)
@@ -8,6 +8,7 @@
         <th scope="col" class="cs-table__transaction-col">Transaction</th>
         <th scope="col" class="cs-table__name-col">Name</th>
         <th scope="col" class="cs-table__started-col">Started</th>
+        <th scope="col" class="cs-table__wsState-col">WebSocket State</th>
         <th scope="col" class="cs-table__registration-status-col">Registration Status</th>
         <th scope="col" class="cs-table__vendor-col">Vendor</th>
         <th scope="col" class="cs-table__model-col">Model</th>
@@ -30,7 +31,7 @@ import CSData from './CSData.vue';
 import type { ChargingStationData } from '@/types/ChargingStationType';
 
 const props = defineProps<{
-  chargingStations: Record<string, ChargingStationData>;
+  chargingStations: ChargingStationData[];
   idTag: string;
 }>();
 </script>
@@ -82,6 +83,7 @@ const props = defineProps<{
 .cs-table__transaction-col,
 .cs-table__name-col,
 .cs-table__started-col,
+.cs-table__wsState-col,
 .cs-table__registration-status-col,
 .cs-table__model-col,
 .cs-table__vendor-col,
index 556e9bb7c4e4b458ecfaaa4499a6f32d9f9e713c..e920545a54d4260b6e5c7fdcabc1f2fab280c8fe 100644 (file)
@@ -117,7 +117,7 @@ export default class UIClient {
       console.error('WebSocket error: ', errorEvent);
     };
     this._ws.onclose = (closeEvent) => {
-      console.info('WebSocket close: ', closeEvent);
+      console.info('WebSocket closed: ', closeEvent);
     };
   }
 
@@ -154,13 +154,13 @@ export default class UIClient {
         if (this._ws.readyState === WebSocket.OPEN) {
           this._ws.send(msg);
         } else {
-          throw new Error(`Send request ${command} message: connection not opened`);
+          throw new Error(`Send request '${command}' message: connection not opened`);
         }
 
         this.setResponseHandler(uuid, command, resolve, reject);
       }),
-      60 * 1000,
-      Error(`Send request ${command} message timeout`),
+      120 * 1000,
+      Error(`Send request '${command}' message timeout`),
       () => {
         this._responseHandlers.delete(uuid);
       }
@@ -168,28 +168,28 @@ export default class UIClient {
   }
 
   private responseHandler(messageEvent: MessageEvent<string>): void {
-    const data = JSON.parse(messageEvent.data) as ProtocolResponse;
+    const response = JSON.parse(messageEvent.data) as ProtocolResponse;
 
-    if (Array.isArray(data) === false) {
-      throw new Error('Response not an array: ' + JSON.stringify(data, null, 2));
+    if (Array.isArray(response) === false) {
+      throw new Error('Response not an array: ' + JSON.stringify(response, null, 2));
     }
 
-    const [uuid, response] = data;
+    const [uuid, responsePayload] = response;
 
     if (this._responseHandlers.has(uuid) === true) {
-      switch (response.status) {
+      switch (responsePayload.status) {
         case ResponseStatus.SUCCESS:
-          this.getResponseHandler(uuid)?.resolve(response);
+          this.getResponseHandler(uuid)?.resolve(responsePayload);
           break;
         case ResponseStatus.FAILURE:
-          this.getResponseHandler(uuid)?.reject(response);
+          this.getResponseHandler(uuid)?.reject(responsePayload);
           break;
         default:
-          console.error(`Response status not supported: ${response.status}`);
+          console.error(`Response status not supported: ${responsePayload.status}`);
       }
       this.deleteResponseHandler(uuid);
     } else {
-      throw new Error('Not a response to a request: ' + JSON.stringify(data, null, 2));
+      throw new Error('Not a response to a request: ' + JSON.stringify(response, null, 2));
     }
   }
 }
index d99aba18999acd083272e3d2d37b421df8f5497a..8ec35f254bbd6659bc901d17485b4309c04ed10f 100644 (file)
@@ -3,6 +3,7 @@ import type { JsonObject } from './JsonType';
 export type ChargingStationData = {
   stationInfo: ChargingStationInfo;
   started: boolean;
+  wsState?: number;
   bootNotificationResponse: BootNotificationResponse;
   connectors: ConnectorStatus[];
 };
index f3a9d072a80cfd462e4fbbe813dbb364591f2da7..97f44b0752a89f0ccd3b2e61493b7e9cd56c4e26 100644 (file)
@@ -1,3 +1,4 @@
+import type { ChargingStationData } from './ChargingStationType';
 import type { JsonObject } from './JsonType';
 
 export enum Protocol {
index 5476ac4b678a2a057c9cea5716ddd354cb578002..25ea51d9bc02549c1526725e9f5e92b9fde96e5e 100644 (file)
@@ -33,21 +33,22 @@ onMounted(() => {
 
 type State = {
   isLoading: boolean;
-  chargingStations: Record<string, ChargingStationData>;
+  chargingStations: ChargingStationData[];
   idTag: string;
 };
 
 const state: State = reactive({
   isLoading: false,
-  chargingStations: {},
+  chargingStations: [],
   idTag: '',
 });
 
 async function load(): Promise<void> {
   if (state.isLoading === true) return;
   state.isLoading = true;
-  const chargingStationsList = await UIClientInstance.listChargingStations();
-  state.chargingStations = chargingStationsList as unknown as Record<string, ChargingStationData>;
+  const listChargingStationsPayload = await UIClientInstance.listChargingStations();
+  state.chargingStations =
+    listChargingStationsPayload.chargingStations as unknown as ChargingStationData[];
   state.isLoading = false;
 }
 
index 570c9f1fb066f675c139820b7be54952a7a241a2..6b13495ae3422a9209f7d6a77923e4316d8838bb 100644 (file)
@@ -5,9 +5,7 @@ import type { ChargingStationData } from '@/types/ChargingStationType';
 
 describe('CSTable.vue', () => {
   it('renders CS table columns name', () => {
-    const chargingStations: Record<string, ChargingStationData> = {
-      '0': {} as unknown as ChargingStationData,
-    };
+    const chargingStations: ChargingStationData[] = [];
     const wrapper = shallowMount(CSTable, {
       props: { chargingStations, idTag: '0' },
     });
index 5b66368fa87f264dc8a2d93c72c2a6c55ad809cc..2dff293af5f31479f5c8fde2f6cc43b3b84dc346 100644 (file)
@@ -2,6 +2,8 @@ import crypto from 'crypto';
 
 import { v4 as uuid } from 'uuid';
 
+import { WebSocketCloseEventStatusString } from '../types/WebSocket';
+
 export default class Utils {
   private constructor() {
     // This is intentional
@@ -257,4 +259,30 @@ export default class Utils {
       space
     );
   }
+
+  /**
+   * Convert websocket error code to human readable string message
+   *
+   * @param code websocket error code
+   * @returns human readable string message
+   */
+  public static getWebSocketCloseEventStatusString(code: number): string {
+    if (code >= 0 && code <= 999) {
+      return '(Unused)';
+    } else if (code >= 1016) {
+      if (code <= 1999) {
+        return '(For WebSocket standard)';
+      } else if (code <= 2999) {
+        return '(For WebSocket extensions)';
+      } else if (code <= 3999) {
+        return '(For libraries and frameworks)';
+      } else if (code <= 4999) {
+        return '(For applications)';
+      }
+    }
+    if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) {
+      return WebSocketCloseEventStatusString[code] as string;
+    }
+    return '(Unknown)';
+  }
 }