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,
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<
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,
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() : ''
);
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(
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<
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();
? limit
: DCElectricUtils.power(this.getVoltageOut(), limit);
}
-
const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
if (limit > connectorMaximumPower) {
logger.error(
}
}
+ 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) => {
}
// 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);
}
}
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`
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> {
: 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 (
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);
}
}
}
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';
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,
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 (
private messageErrorHandler(messageEvent: MessageEvent): void {
logger.error(
`${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
- { messageEvent, messageEventData: messageEvent.data }
+ { messageEvent }
);
}
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);
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(),
+ ],
+ }),
};
}
}
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),
private messageErrorHandler(messageEvent: MessageEvent): void {
logger.error(
`${this.uiService.logPrefix(moduleName, 'messageErrorHandler')} Error at handling message:`,
- { messageEvent, messageEventData: messageEvent.data }
+ { messageEvent }
);
}
}
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;
}
}
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,
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';
): 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()} ${
);
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;
}
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;
}
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;
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;
}
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(
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() +
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() +
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 ' +
+import { parentPort } from 'worker_threads';
+
import type { JSONSchemaType } from 'ajv';
import Ajv from 'ajv-draft-04';
import ajvFormats from 'ajv-formats';
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';
reject(error);
} finally {
chargingStation.requests.delete(messageId);
+ // parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(chargingStation));
}
}
error
);
chargingStation.requests.delete(messageId);
+ // parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(chargingStation));
reject(error);
}
}),
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>();
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;
}
import {
ProcedureName,
Protocol,
+ ProtocolRequest,
ProtocolResponse,
ProtocolVersion,
RequestPayload,
}
// 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);
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}`
);
}
}
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';
}
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(
'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()}'`
);
});
});
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 {
}
}
}
+
+ 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;
+ }
}
-import type { RawData } from 'ws';
-
import BaseError from '../../../exception/BaseError';
import { Bootstrap } from '../../../internal';
-import type { JsonType } from '../../../types/JsonType';
import {
ProcedureName,
ProtocolRequest,
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(
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> {
+import type { Status } from './AutomaticTransactionGenerator';
import type ChargingStationInfo from './ChargingStationInfo';
import type { ConnectorStatus } from './ConnectorStatus';
import type { JsonObject } from './JsonType';
export interface ChargingStationData extends WorkerData {
stationInfo: ChargingStationInfo;
started: boolean;
+ wsState?: number;
bootNotificationResponse: BootNotificationResponse;
connectors: ConnectorStatus[];
+ automaticTransactionGeneratorStatuses?: Status[];
}
enum ChargingStationMessageEvents {
{
"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",
{
"name": "webui",
- "version": "0.1.0",
+ "version": "0.1.1",
"readme": "README.md",
"gitHooks": {
"pre-commit": "lint-staged"
/>
<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>
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 ?? 'Ø';
}
<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>
import type { ChargingStationData } from '@/types/ChargingStationType';
const props = defineProps<{
- chargingStations: Record<string, ChargingStationData>;
+ chargingStations: ChargingStationData[];
idTag: string;
}>();
</script>
.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,
console.error('WebSocket error: ', errorEvent);
};
this._ws.onclose = (closeEvent) => {
- console.info('WebSocket close: ', closeEvent);
+ console.info('WebSocket closed: ', closeEvent);
};
}
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);
}
}
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));
}
}
}
export type ChargingStationData = {
stationInfo: ChargingStationInfo;
started: boolean;
+ wsState?: number;
bootNotificationResponse: BootNotificationResponse;
connectors: ConnectorStatus[];
};
+import type { ChargingStationData } from './ChargingStationType';
import type { JsonObject } from './JsonType';
export enum Protocol {
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;
}
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' },
});
import { v4 as uuid } from 'uuid';
+import { WebSocketCloseEventStatusString } from '../types/WebSocket';
+
export default class Utils {
private constructor() {
// This is intentional
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)';
+ }
}