From 5e3cb7281de2b6fa8b61a453f964c2f213fefa80 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 3 Sep 2022 17:16:55 +0200 Subject: [PATCH] UI Protocol: Expose ATG status and use array for all list MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../AutomaticTransactionGenerator.ts | 45 +------ src/charging-station/ChargingStation.ts | 124 ++++++++++-------- src/charging-station/ChargingStationUtils.ts | 27 ---- .../ChargingStationWorkerBroadcastChannel.ts | 9 +- src/charging-station/MessageChannelUtils.ts | 6 + .../UIServiceWorkerBroadcastChannel.ts | 6 +- .../WorkerBroadcastChannel.ts | 3 +- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 98 ++++---------- .../ocpp/1.6/OCPP16ResponseService.ts | 12 +- .../ocpp/OCPPRequestService.ts | 5 + .../ui-server/AbstractUIServer.ts | 20 +-- .../ui-server/UIHttpServer.ts | 9 +- .../ui-server/UIWebSocketServer.ts | 60 ++++++--- .../ui-services/AbstractUIService.ts | 34 +---- src/types/ChargingStationWorker.ts | 3 + src/ui/web/package-lock.json | 4 +- src/ui/web/package.json | 2 +- .../components/charging-stations/CSData.vue | 15 +++ .../components/charging-stations/CSTable.vue | 4 +- src/ui/web/src/composables/UIClient.ts | 26 ++-- src/ui/web/src/types/ChargingStationType.ts | 1 + src/ui/web/src/types/UIProtocol.ts | 1 + src/ui/web/src/views/ChargingStationsView.vue | 9 +- src/ui/web/tests/unit/CSTable.spec.ts | 4 +- src/utils/Utils.ts | 28 ++++ 25 files changed, 261 insertions(+), 294 deletions(-) diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index bf28e55b..e8a4b50f 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -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 = new Map< @@ -29,10 +26,10 @@ export default class AutomaticTransactionGenerator { AutomaticTransactionGenerator >(); + public readonly connectorsStatus: Map; public readonly configuration: AutomaticTransactionGeneratorConfiguration; public started: boolean; private readonly chargingStation: ChargingStation; - private readonly connectorsStatus: Map; 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 { 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() : '' diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 78db2058..c42ff2b9 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -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 { - // Stop message sequence - await this.stopMessageSequence(reason); + public async stop(): Promise { 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 { - await this.stop(reason); + public async reset(): Promise { + 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 { + 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 { + 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( + this, + RequestCommand.METER_VALUES, + { + connectorId, + transactionId, + meterValue: [transactionEndMeterValue], + } + ); + } + return this.ocppRequestService.requestHandler( + 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 { @@ -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( - 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); } } } diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index 60e143fa..28f38b46 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -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, diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index ecc3ab49..21b83b9f 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -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); diff --git a/src/charging-station/MessageChannelUtils.ts b/src/charging-station/MessageChannelUtils.ts index e87cdbc0..19266f73 100644 --- a/src/charging-station/MessageChannelUtils.ts +++ b/src/charging-station/MessageChannelUtils.ts @@ -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(), + ], + }), }; } } diff --git a/src/charging-station/UIServiceWorkerBroadcastChannel.ts b/src/charging-station/UIServiceWorkerBroadcastChannel.ts index 652d825a..a2b20922 100644 --- a/src/charging-station/UIServiceWorkerBroadcastChannel.ts +++ b/src/charging-station/UIServiceWorkerBroadcastChannel.ts @@ -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 } ); } } diff --git a/src/charging-station/WorkerBroadcastChannel.ts b/src/charging-station/WorkerBroadcastChannel.ts index b19bf946..2fa082dd 100644 --- a/src/charging-station/WorkerBroadcastChannel.ts +++ b/src/charging-station/WorkerBroadcastChannel.ts @@ -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; } } diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 0589da01..e1d29e6f 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -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 => { - 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( diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 9e4dfd08..c9f49178 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -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 ' + diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index 25c89435..e00e2b99 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -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); } }), diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index 91c69a76..9a19d39d 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -15,8 +15,8 @@ import type AbstractUIService from './ui-services/AbstractUIService'; export abstract class AbstractUIServer { public readonly chargingStations: Map; - protected readonly uiServices: Map; protected server: WebSocket.Server | HttpServer; + protected readonly uiServices: Map; public constructor() { this.chargingStations = new Map(); @@ -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; } diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index 98c3f1cc..15252937 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -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}` ); } } diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index 8e11b66d..c4829dcc 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -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; + } } diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index c9bc8ff5..7d4f69d0 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -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(); } - public async requestHandler(request: RawData | JsonType): Promise { + public async requestHandler(request: ProtocolRequest): Promise { 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 { diff --git a/src/types/ChargingStationWorker.ts b/src/types/ChargingStationWorker.ts index 4cbe8ba7..020ec7bd 100644 --- a/src/types/ChargingStationWorker.ts +++ b/src/types/ChargingStationWorker.ts @@ -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 { diff --git a/src/ui/web/package-lock.json b/src/ui/web/package-lock.json index a5512d2f..59fae6d2 100644 --- a/src/ui/web/package-lock.json +++ b/src/ui/web/package-lock.json @@ -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", diff --git a/src/ui/web/package.json b/src/ui/web/package.json index 99e1ee7d..ea6e12a7 100644 --- a/src/ui/web/package.json +++ b/src/ui/web/package.json @@ -1,6 +1,6 @@ { "name": "webui", - "version": "0.1.0", + "version": "0.1.1", "readme": "README.md", "gitHooks": { "pre-commit": "lint-staged" diff --git a/src/ui/web/src/components/charging-stations/CSData.vue b/src/ui/web/src/components/charging-stations/CSData.vue index a6a819aa..186fa7a4 100644 --- a/src/ui/web/src/components/charging-stations/CSData.vue +++ b/src/ui/web/src/components/charging-stations/CSData.vue @@ -9,6 +9,7 @@ /> {{ getId() }} {{ getStarted() }} + {{ getWsState() }} {{ getRegistrationStatus() }} {{ getVendor() }} {{ getModel() }} @@ -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 ?? 'Ø'; } diff --git a/src/ui/web/src/components/charging-stations/CSTable.vue b/src/ui/web/src/components/charging-stations/CSTable.vue index cf0835bc..2c0bc1fa 100644 --- a/src/ui/web/src/components/charging-stations/CSTable.vue +++ b/src/ui/web/src/components/charging-stations/CSTable.vue @@ -8,6 +8,7 @@ Transaction Name Started + WebSocket State Registration Status Vendor Model @@ -30,7 +31,7 @@ import CSData from './CSData.vue'; import type { ChargingStationData } from '@/types/ChargingStationType'; const props = defineProps<{ - chargingStations: Record; + chargingStations: ChargingStationData[]; idTag: string; }>(); @@ -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, diff --git a/src/ui/web/src/composables/UIClient.ts b/src/ui/web/src/composables/UIClient.ts index 556e9bb7..e920545a 100644 --- a/src/ui/web/src/composables/UIClient.ts +++ b/src/ui/web/src/composables/UIClient.ts @@ -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): 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)); } } } diff --git a/src/ui/web/src/types/ChargingStationType.ts b/src/ui/web/src/types/ChargingStationType.ts index d99aba18..8ec35f25 100644 --- a/src/ui/web/src/types/ChargingStationType.ts +++ b/src/ui/web/src/types/ChargingStationType.ts @@ -3,6 +3,7 @@ import type { JsonObject } from './JsonType'; export type ChargingStationData = { stationInfo: ChargingStationInfo; started: boolean; + wsState?: number; bootNotificationResponse: BootNotificationResponse; connectors: ConnectorStatus[]; }; diff --git a/src/ui/web/src/types/UIProtocol.ts b/src/ui/web/src/types/UIProtocol.ts index f3a9d072..97f44b07 100644 --- a/src/ui/web/src/types/UIProtocol.ts +++ b/src/ui/web/src/types/UIProtocol.ts @@ -1,3 +1,4 @@ +import type { ChargingStationData } from './ChargingStationType'; import type { JsonObject } from './JsonType'; export enum Protocol { diff --git a/src/ui/web/src/views/ChargingStationsView.vue b/src/ui/web/src/views/ChargingStationsView.vue index 5476ac4b..25ea51d9 100644 --- a/src/ui/web/src/views/ChargingStationsView.vue +++ b/src/ui/web/src/views/ChargingStationsView.vue @@ -33,21 +33,22 @@ onMounted(() => { type State = { isLoading: boolean; - chargingStations: Record; + chargingStations: ChargingStationData[]; idTag: string; }; const state: State = reactive({ isLoading: false, - chargingStations: {}, + chargingStations: [], idTag: '', }); async function load(): Promise { if (state.isLoading === true) return; state.isLoading = true; - const chargingStationsList = await UIClientInstance.listChargingStations(); - state.chargingStations = chargingStationsList as unknown as Record; + const listChargingStationsPayload = await UIClientInstance.listChargingStations(); + state.chargingStations = + listChargingStationsPayload.chargingStations as unknown as ChargingStationData[]; state.isLoading = false; } diff --git a/src/ui/web/tests/unit/CSTable.spec.ts b/src/ui/web/tests/unit/CSTable.spec.ts index 570c9f1f..6b13495a 100644 --- a/src/ui/web/tests/unit/CSTable.spec.ts +++ b/src/ui/web/tests/unit/CSTable.spec.ts @@ -5,9 +5,7 @@ import type { ChargingStationData } from '@/types/ChargingStationType'; describe('CSTable.vue', () => { it('renders CS table columns name', () => { - const chargingStations: Record = { - '0': {} as unknown as ChargingStationData, - }; + const chargingStations: ChargingStationData[] = []; const wrapper = shallowMount(CSTable, { props: { chargingStations, idTag: '0' }, }); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 5b66368f..2dff293a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -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)'; + } } -- 2.34.1