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