README.md: formatting fixes
[e-mobility-charging-stations-simulator.git] / src / ui / web / src / composables / UIClient.ts
1 import { ProcedureName, ResponseStatus, type RequestPayload } from '@/types/UIProtocol';
2 import type { ProtocolResponse, ResponsePayload } from '@/types/UIProtocol';
3
4 import Utils from './Utils';
5 import config from '@/assets/config';
6 import { v4 as uuidv4 } from 'uuid';
7
8 type ResponseHandler = {
9 procedureName: ProcedureName;
10 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void;
11 reject: (reason?: any) => void;
12 };
13
14 export default class UIClient {
15 private static _instance: UIClient | null = null;
16
17 private _ws!: WebSocket;
18 private _responseHandlers: Map<string, ResponseHandler>;
19
20 private constructor() {
21 this.openWS();
22 this._responseHandlers = new Map<string, ResponseHandler>();
23 }
24
25 public static getInstance() {
26 if (UIClient._instance === null) {
27 UIClient._instance = new UIClient();
28 }
29 return UIClient._instance;
30 }
31
32 public registerWSonOpenListener(listener: (event: Event) => void) {
33 this._ws.addEventListener('open', listener);
34 }
35
36 public async startSimulator(): Promise<ResponsePayload> {
37 return this.sendRequest(ProcedureName.START_SIMULATOR, {});
38 }
39
40 public async stopSimulator(): Promise<ResponsePayload> {
41 return this.sendRequest(ProcedureName.STOP_SIMULATOR, {});
42 }
43
44 public async listChargingStations(): Promise<ResponsePayload> {
45 return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {});
46 }
47
48 public async startChargingStation(hashId: string): Promise<ResponsePayload> {
49 return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashIds: [hashId] });
50 }
51
52 public async stopChargingStation(hashId: string): Promise<ResponsePayload> {
53 return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashIds: [hashId] });
54 }
55
56 public async openConnection(hashId: string): Promise<ResponsePayload> {
57 return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
58 hashIds: [hashId],
59 });
60 }
61
62 public async closeConnection(hashId: string): Promise<ResponsePayload> {
63 return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
64 hashIds: [hashId],
65 });
66 }
67
68 public async startTransaction(
69 hashId: string,
70 connectorId: number,
71 idTag: string | undefined
72 ): Promise<ResponsePayload> {
73 return this.sendRequest(ProcedureName.START_TRANSACTION, {
74 hashIds: [hashId],
75 connectorId,
76 idTag,
77 });
78 }
79
80 public async stopTransaction(
81 hashId: string,
82 transactionId: number | undefined
83 ): Promise<ResponsePayload> {
84 return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
85 hashIds: [hashId],
86 transactionId,
87 });
88 }
89
90 public async startAutomaticTransactionGenerator(
91 hashId: string,
92 connectorId: number
93 ): Promise<ResponsePayload> {
94 return this.sendRequest(ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, {
95 hashIds: [hashId],
96 connectorIds: [connectorId],
97 });
98 }
99
100 public async stopAutomaticTransactionGenerator(
101 hashId: string,
102 connectorId: number
103 ): Promise<ResponsePayload> {
104 return this.sendRequest(ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, {
105 hashIds: [hashId],
106 connectorIds: [connectorId],
107 });
108 }
109
110 private openWS(): void {
111 this._ws = new WebSocket(
112 `ws://${config.uiServer.host}:${config.uiServer.port}`,
113 config.uiServer.protocol
114 );
115 this._ws.onmessage = this.responseHandler.bind(this);
116 this._ws.onerror = (errorEvent) => {
117 console.error('WebSocket error: ', errorEvent);
118 };
119 this._ws.onclose = (closeEvent) => {
120 console.info('WebSocket close: ', closeEvent);
121 };
122 }
123
124 private setResponseHandler(
125 id: string,
126 procedureName: ProcedureName,
127 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void,
128 reject: (reason?: any) => void
129 ): void {
130 this._responseHandlers.set(id, { procedureName, resolve, reject });
131 }
132
133 private getResponseHandler(id: string): ResponseHandler | undefined {
134 return this._responseHandlers.get(id);
135 }
136
137 private deleteResponseHandler(id: string): boolean {
138 return this._responseHandlers.delete(id);
139 }
140
141 private async sendRequest(
142 command: ProcedureName,
143 data: RequestPayload
144 ): Promise<ResponsePayload> {
145 let uuid: string;
146 return Utils.promiseWithTimeout(
147 new Promise((resolve, reject) => {
148 uuid = uuidv4();
149 const msg = JSON.stringify([uuid, command, data]);
150
151 if (this._ws.readyState !== WebSocket.OPEN) {
152 this.openWS();
153 }
154 if (this._ws.readyState === WebSocket.OPEN) {
155 this._ws.send(msg);
156 } else {
157 throw new Error(`Send request ${command} message: connection not opened`);
158 }
159
160 this.setResponseHandler(uuid, command, resolve, reject);
161 }),
162 60 * 1000,
163 Error(`Send request ${command} message timeout`),
164 () => {
165 this._responseHandlers.delete(uuid);
166 }
167 );
168 }
169
170 private responseHandler(messageEvent: MessageEvent<string>): void {
171 const data = JSON.parse(messageEvent.data) as ProtocolResponse;
172
173 if (Array.isArray(data) === false) {
174 throw new Error('Response not an array: ' + JSON.stringify(data, null, 2));
175 }
176
177 const [uuid, response] = data;
178
179 if (this._responseHandlers.has(uuid) === true) {
180 switch (response.status) {
181 case ResponseStatus.SUCCESS:
182 this.getResponseHandler(uuid)?.resolve(response);
183 break;
184 case ResponseStatus.FAILURE:
185 this.getResponseHandler(uuid)?.reject(response);
186 break;
187 default:
188 console.error(`Response status not supported: ${response.status}`);
189 }
190 this.deleteResponseHandler(uuid);
191 } else {
192 throw new Error('Not a response to a request: ' + JSON.stringify(data, null, 2));
193 }
194 }
195 }