README.md: formatting fixes
[e-mobility-charging-stations-simulator.git] / src / 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';
6import { v4 as uuidv4 } from 'uuid';
7
8type ResponseHandler = {
1f7fa4de 9 procedureName: ProcedureName;
32de5a57
LM
10 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void;
11 reject: (reason?: any) => void;
32de5a57
LM
12};
13
14export default class UIClient {
15 private static _instance: UIClient | null = null;
16
5a010bf0 17 private _ws!: WebSocket;
32de5a57
LM
18 private _responseHandlers: Map<string, ResponseHandler>;
19
20 private constructor() {
5a010bf0 21 this.openWS();
32de5a57 22 this._responseHandlers = new Map<string, ResponseHandler>();
32de5a57
LM
23 }
24
f27eb751 25 public static getInstance() {
32de5a57
LM
26 if (UIClient._instance === null) {
27 UIClient._instance = new UIClient();
28 }
29 return UIClient._instance;
30 }
31
17bfa1b6 32 public registerWSonOpenListener(listener: (event: Event) => void) {
32de5a57
LM
33 this._ws.addEventListener('open', listener);
34 }
35
5a010bf0
JB
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 }
32de5a57 43
5a010bf0 44 public async listChargingStations(): Promise<ResponsePayload> {
32de5a57
LM
45 return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {});
46 }
47
8fc2e5cc 48 public async startChargingStation(hashId: string): Promise<ResponsePayload> {
757b2ecf 49 return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashIds: [hashId] });
8fc2e5cc
JB
50 }
51
52 public async stopChargingStation(hashId: string): Promise<ResponsePayload> {
757b2ecf 53 return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashIds: [hashId] });
8fc2e5cc
JB
54 }
55
56 public async openConnection(hashId: string): Promise<ResponsePayload> {
57 return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
757b2ecf 58 hashIds: [hashId],
8fc2e5cc
JB
59 });
60 }
61
62 public async closeConnection(hashId: string): Promise<ResponsePayload> {
63 return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
757b2ecf 64 hashIds: [hashId],
8fc2e5cc
JB
65 });
66 }
67
32de5a57
LM
68 public async startTransaction(
69 hashId: string,
70 connectorId: number,
5a010bf0 71 idTag: string | undefined
32de5a57 72 ): Promise<ResponsePayload> {
32de5a57 73 return this.sendRequest(ProcedureName.START_TRANSACTION, {
757b2ecf 74 hashIds: [hashId],
32de5a57
LM
75 connectorId,
76 idTag,
77 });
78 }
79
5a010bf0
JB
80 public async stopTransaction(
81 hashId: string,
82 transactionId: number | undefined
83 ): Promise<ResponsePayload> {
32de5a57 84 return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
757b2ecf 85 hashIds: [hashId],
32de5a57
LM
86 transactionId,
87 });
88 }
89
757b2ecf
JB
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
5a010bf0
JB
110 private openWS(): void {
111 this._ws = new WebSocket(
6a619cce
JB
112 `ws://${config.uiServer.host}:${config.uiServer.port}`,
113 config.uiServer.protocol
5a010bf0
JB
114 );
115 this._ws.onmessage = this.responseHandler.bind(this);
6a619cce
JB
116 this._ws.onerror = (errorEvent) => {
117 console.error('WebSocket error: ', errorEvent);
118 };
119 this._ws.onclose = (closeEvent) => {
120 console.info('WebSocket close: ', closeEvent);
a745e412 121 };
5a010bf0
JB
122 }
123
32de5a57
LM
124 private setResponseHandler(
125 id: string,
1f7fa4de 126 procedureName: ProcedureName,
32de5a57 127 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void,
1f7fa4de 128 reject: (reason?: any) => void
32de5a57 129 ): void {
5cf9050d 130 this._responseHandlers.set(id, { procedureName, resolve, reject });
32de5a57
LM
131 }
132
133 private getResponseHandler(id: string): ResponseHandler | undefined {
134 return this._responseHandlers.get(id);
135 }
136
1f7fa4de
JB
137 private deleteResponseHandler(id: string): boolean {
138 return this._responseHandlers.delete(id);
139 }
140
757b2ecf
JB
141 private async sendRequest(
142 command: ProcedureName,
143 data: RequestPayload
144 ): Promise<ResponsePayload> {
32de5a57
LM
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
5a010bf0
JB
151 if (this._ws.readyState !== WebSocket.OPEN) {
152 this.openWS();
153 }
154 if (this._ws.readyState === WebSocket.OPEN) {
32de5a57
LM
155 this._ws.send(msg);
156 } else {
157 throw new Error(`Send request ${command} message: connection not opened`);
158 }
159
1f7fa4de 160 this.setResponseHandler(uuid, command, resolve, reject);
32de5a57
LM
161 }),
162 60 * 1000,
163 Error(`Send request ${command} message timeout`),
164 () => {
165 this._responseHandlers.delete(uuid);
166 }
167 );
168 }
169
97f0a1a5 170 private responseHandler(messageEvent: MessageEvent<string>): void {
32de5a57
LM
171 const data = JSON.parse(messageEvent.data) as ProtocolResponse;
172
53e5fd67
JB
173 if (Array.isArray(data) === false) {
174 throw new Error('Response not an array: ' + JSON.stringify(data, null, 2));
32de5a57
LM
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:
a745e412 188 console.error(`Response status not supported: ${response.status}`);
32de5a57 189 }
1f7fa4de 190 this.deleteResponseHandler(uuid);
32de5a57
LM
191 } else {
192 throw new Error('Not a response to a request: ' + JSON.stringify(data, null, 2));
193 }
194 }
195}