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