private readonly storage!: Storage;
private numberOfChargingStationTemplates!: number;
private numberOfChargingStations!: number;
+ private numberOfStartedChargingStations!: number;
private readonly version: string = version;
private started: boolean;
private readonly workerScript: string;
private workerEventStarted(data: ChargingStationData) {
this.uiServer?.chargingStations.set(data.hashId, data);
- this.started && ++this.numberOfChargingStations;
+ ++this.numberOfStartedChargingStations;
}
private workerEventStopped(data: ChargingStationData) {
- this.uiServer?.chargingStations.delete(data.hashId);
- this.started && --this.numberOfChargingStations;
+ this.uiServer?.chargingStations.set(data.hashId, data);
+ --this.numberOfStartedChargingStations;
}
private workerEventUpdated(data: ChargingStationData) {
};
private initialize() {
- this.numberOfChargingStations = 0;
this.numberOfChargingStationTemplates = 0;
+ this.numberOfChargingStations = 0;
+ this.numberOfStartedChargingStations = 0;
this.initializeWorkerImplementation();
}
),
};
await this.workerImplementation.addElement(workerData);
- this.numberOfChargingStations++;
+ ++this.numberOfChargingStations;
}
private logPrefix(): string {
import { URL } from 'url';
import { parentPort } from 'worker_threads';
-import WebSocket, { Data, MessageEvent, RawData } from 'ws';
+import WebSocket, { Data, RawData } from 'ws';
import BaseError from '../exception/BaseError';
import OCPPError from '../exception/OCPPError';
StatusNotificationResponse,
} from '../types/ocpp/Responses';
import {
- StartTransactionRequest,
- StartTransactionResponse,
StopTransactionReason,
StopTransactionRequest,
StopTransactionResponse,
} from '../types/ocpp/Transaction';
-import { ProcedureName } from '../types/UIProtocol';
import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
-import { WorkerBroadcastChannelData } from '../types/WorkerBroadcastChannel';
import Configuration from '../utils/Configuration';
import Constants from '../utils/Constants';
import { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils';
import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
import { ChargingStationUtils } from './ChargingStationUtils';
+import ChargingStationWorkerBroadcastChannel from './ChargingStationWorkerBroadcastChannel';
import { MessageChannelUtils } from './MessageChannelUtils';
import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
import OCPPRequestService from './ocpp/OCPPRequestService';
import SharedLRUCache from './SharedLRUCache';
-import WorkerBroadcastChannel from './WorkerBroadcastChannel';
export default class ChargingStation {
public hashId!: string;
public readonly templateFile: string;
public authorizedTagsCache: AuthorizedTagsCache;
public stationInfo!: ChargingStationInfo;
+ public stopped: boolean;
public readonly connectors: Map<number, ConnectorStatus>;
public ocppConfiguration!: ChargingStationOcppConfiguration;
public wsConnection!: WebSocket;
private configuredSupervisionUrl!: URL;
private wsConnectionRestarted: boolean;
private autoReconnectRetryCount: number;
- private stopped: boolean;
private templateFileWatcher!: fs.FSWatcher;
private readonly sharedLRUCache: SharedLRUCache;
private automaticTransactionGenerator!: AutomaticTransactionGenerator;
private webSocketPingSetInterval!: NodeJS.Timeout;
- private workerBroadcastChannel: WorkerBroadcastChannel;
+ private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
constructor(index: number, templateFile: string) {
this.index = index;
this.connectors = new Map<number, ConnectorStatus>();
this.requests = new Map<string, CachedRequest>();
this.messageBuffer = new Set<string>();
- this.workerBroadcastChannel = new WorkerBroadcastChannel();
-
- this.workerBroadcastChannel.onmessage = this.handleWorkerBroadcastChannelMessage.bind(this) as (
- message: MessageEvent
- ) => void;
+ this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
this.initialize();
}
this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0;
this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
}
-
- private async handleWorkerBroadcastChannelMessage(message: MessageEvent): Promise<void> {
- const [command, payload] = message.data as unknown as [
- ProcedureName,
- WorkerBroadcastChannelData
- ];
-
- if (payload.hashId !== this.hashId) {
- return;
- }
-
- switch (command) {
- case ProcedureName.START_TRANSACTION:
- await this.ocppRequestService.requestHandler<
- StartTransactionRequest,
- StartTransactionResponse
- >(this, RequestCommand.START_TRANSACTION, {
- connectorId: payload.connectorId,
- idTag: payload.idTag,
- });
- break;
- case ProcedureName.STOP_TRANSACTION:
- await this.ocppRequestService.requestHandler<
- StopTransactionRequest,
- StopTransactionResponse
- >(this, RequestCommand.STOP_TRANSACTION, {
- transactionId: payload.transactionId,
- meterStop: this.getEnergyActiveImportRegisterByTransactionId(payload.transactionId),
- idTag: this.getTransactionIdTag(payload.transactionId),
- reason: StopTransactionReason.NONE,
- });
- break;
- }
- }
}
--- /dev/null
+import { BroadcastChannel } from 'worker_threads';
+
+import { RequestCommand } from '../types/ocpp/Requests';
+import {
+ StartTransactionRequest,
+ StartTransactionResponse,
+ StopTransactionReason,
+ StopTransactionRequest,
+ StopTransactionResponse,
+} from '../types/ocpp/Transaction';
+import {
+ BroadcastChannelProcedureName,
+ BroadcastChannelRequest,
+} from '../types/WorkerBroadcastChannel';
+import ChargingStation from './ChargingStation';
+
+type MessageEvent = { data: unknown };
+
+export default class ChargingStationWorkerBroadcastChannel extends BroadcastChannel {
+ private readonly chargingStation: ChargingStation;
+
+ constructor(chargingStation: ChargingStation) {
+ super('worker');
+ this.chargingStation = chargingStation;
+ this.onmessage = this.handleRequest.bind(this) as (message: MessageEvent) => void;
+ }
+
+ private async handleRequest(messageEvent: MessageEvent): Promise<void> {
+ const [, command, payload] = messageEvent.data as BroadcastChannelRequest;
+
+ if (payload.hashId !== this.chargingStation.hashId) {
+ return;
+ }
+
+ // TODO: return a response stating the command success or failure
+ switch (command) {
+ case BroadcastChannelProcedureName.START_TRANSACTION:
+ await this.chargingStation.ocppRequestService.requestHandler<
+ StartTransactionRequest,
+ StartTransactionResponse
+ >(this.chargingStation, RequestCommand.START_TRANSACTION, {
+ connectorId: payload.connectorId,
+ idTag: payload.idTag,
+ });
+ break;
+ case BroadcastChannelProcedureName.STOP_TRANSACTION:
+ await this.chargingStation.ocppRequestService.requestHandler<
+ StopTransactionRequest,
+ StopTransactionResponse
+ >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
+ transactionId: payload.transactionId,
+ meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
+ payload.transactionId
+ ),
+ idTag: this.chargingStation.getTransactionIdTag(payload.transactionId),
+ reason: StopTransactionReason.NONE,
+ });
+ break;
+ case BroadcastChannelProcedureName.START_CHARGING_STATION:
+ this.chargingStation.start();
+ break;
+ case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
+ await this.chargingStation.stop();
+ break;
+ }
+ }
+}
return {
hashId: chargingStation.hashId,
stationInfo: chargingStation.stationInfo,
+ stopped: chargingStation.stopped,
connectors: Array.from(chargingStation.connectors.values()),
};
}
ProtocolRequestHandler,
ProtocolResponse,
ProtocolVersion,
+ RequestPayload,
ResponsePayload,
ResponseStatus,
} from '../../../types/UIProtocol';
public async messageHandler(request: RawData): Promise<void> {
let messageId: string;
let command: ProcedureName;
- let requestPayload: JsonType;
+ let requestPayload: RequestPayload;
let responsePayload: ResponsePayload;
try {
[messageId, command, requestPayload] = this.dataValidation(request);
if (this.messageHandlers.has(command) === false) {
- // Throw exception
throw new BaseError(
`${command} is not implemented to handle message payload ${JSON.stringify(
requestPayload,
)}`
);
}
+
// Call the message handler to build the response payload
- responsePayload = (await this.messageHandlers.get(command)(
- requestPayload
- )) as ResponsePayload;
+ responsePayload = await this.messageHandlers.get(command)(requestPayload);
} catch (error) {
// Log
logger.error(
return data as ProtocolRequest;
}
- private handleListChargingStations(): JsonType {
+ private handleListChargingStations(): ResponsePayload {
+ // TODO: remove cast to unknown
return {
status: ResponseStatus.SUCCESS,
...Array.from(this.uiServer.chargingStations.values()),
- } as JsonType;
+ } as unknown as ResponsePayload;
}
private async handleStartSimulator(): Promise<ResponsePayload> {
-import { JsonType } from '../../../types/JsonType';
import {
ProcedureName,
ProtocolRequestHandler,
ProtocolVersion,
+ RequestPayload,
ResponsePayload,
ResponseStatus,
} from '../../../types/UIProtocol';
+import { BroadcastChannelProcedureName } from '../../../types/WorkerBroadcastChannel';
+import Utils from '../../../utils/Utils';
import { AbstractUIServer } from '../AbstractUIServer';
import AbstractUIService from './AbstractUIService';
ProcedureName.STOP_TRANSACTION,
this.handleStopTransaction.bind(this) as ProtocolRequestHandler
);
+ this.messageHandlers.set(
+ ProcedureName.START_CHARGING_STATION,
+ this.handleStartChargingStation.bind(this) as ProtocolRequestHandler
+ );
+ this.messageHandlers.set(
+ ProcedureName.STOP_CHARGING_STATION,
+ this.handleStopChargingStation.bind(this) as ProtocolRequestHandler
+ );
+ }
+
+ private handleStartTransaction(payload: RequestPayload): ResponsePayload {
+ this.workerBroadcastChannel.postMessage([
+ Utils.generateUUID(),
+ BroadcastChannelProcedureName.START_TRANSACTION,
+ payload,
+ ]);
+ return { status: ResponseStatus.SUCCESS };
+ }
+
+ private handleStopTransaction(payload: RequestPayload): ResponsePayload {
+ this.workerBroadcastChannel.postMessage([
+ Utils.generateUUID(),
+ BroadcastChannelProcedureName.STOP_TRANSACTION,
+ payload,
+ ]);
+ return { status: ResponseStatus.SUCCESS };
}
- private handleStartTransaction(payload: JsonType): ResponsePayload {
- this.workerBroadcastChannel.postMessage([ProcedureName.START_TRANSACTION, payload]);
+ private handleStartChargingStation(payload: RequestPayload): ResponsePayload {
+ this.workerBroadcastChannel.postMessage([
+ Utils.generateUUID(),
+ BroadcastChannelProcedureName.START_CHARGING_STATION,
+ payload,
+ ]);
return { status: ResponseStatus.SUCCESS };
}
- private handleStopTransaction(payload: JsonType): ResponsePayload {
- this.workerBroadcastChannel.postMessage([ProcedureName.STOP_TRANSACTION, payload]);
+ private handleStopChargingStation(payload: RequestPayload): ResponsePayload {
+ this.workerBroadcastChannel.postMessage([
+ Utils.generateUUID(),
+ BroadcastChannelProcedureName.STOP_CHARGING_STATION,
+ payload,
+ ]);
return { status: ResponseStatus.SUCCESS };
}
}
export interface ChargingStationData extends WorkerData {
hashId: string;
stationInfo: ChargingStationInfo;
+ stopped: boolean;
connectors: ConnectorStatus[];
}
-import { JsonObject, JsonType } from './JsonType';
+import { JsonObject } from './JsonType';
export enum Protocol {
UI = 'ui',
'0.0.1' = '0.0.1',
}
+export type ProtocolRequest = [string, ProcedureName, RequestPayload];
+export type ProtocolResponse = [string, ResponsePayload];
+
+export type ProtocolRequestHandler = (
+ payload: RequestPayload
+) => ResponsePayload | Promise<ResponsePayload>;
+
export enum ProcedureName {
LIST_CHARGING_STATIONS = 'listChargingStations',
+ START_CHARGING_STATION = 'startChargingStation',
+ STOP_CHARGING_STATION = 'stopChargingStation',
START_TRANSACTION = 'startTransaction',
STOP_TRANSACTION = 'stopTransaction',
START_SIMULATOR = 'startSimulator',
STOP_SIMULATOR = 'stopSimulator',
}
+export interface RequestPayload extends JsonObject {
+ hashId?: string;
+}
export enum ResponseStatus {
SUCCESS = 'success',
export interface ResponsePayload extends JsonObject {
status: ResponseStatus;
}
-
-export type ProtocolRequest = [string, ProcedureName, JsonType];
-export type ProtocolResponse = [string, ResponsePayload];
-
-export type ProtocolRequestHandler = (
- payload: JsonType
-) => void | Promise<void> | ResponsePayload | Promise<ResponsePayload>;
-import { ChargingStationData } from './ChargingStationWorker';
+import { JsonObject } from './JsonType';
-// TODO: use a base payload type and extends it per procedure name
-export interface WorkerBroadcastChannelData extends ChargingStationData {
+export type BroadcastChannelRequest = [string, BroadcastChannelProcedureName, RequestPayload];
+export type BroadcastChannelResponse = [string, ResponsePayload];
+
+export enum BroadcastChannelProcedureName {
+ START_CHARGING_STATION = 'startChargingStation',
+ STOP_CHARGING_STATION = 'stopChargingStation',
+ START_TRANSACTION = 'startTransaction',
+ STOP_TRANSACTION = 'stopTransaction',
+}
+
+interface BasePayload extends JsonObject {
+ hashId: string;
+}
+
+export interface RequestPayload extends BasePayload {
connectorId?: number;
transactionId?: number;
idTag?: string;
}
+
+export type ResponsePayload = BasePayload;