UI server: logging and code refinements
[e-mobility-charging-stations-simulator.git] / src / ui / web / src / composable / UIClient.ts
CommitLineData
32de5a57
LM
1import { JsonType } from '@/type/JsonType';
2import {
3 ProcedureName,
4 ProtocolResponse,
5 ResponsePayload,
6 ResponseStatus,
7} from '@/type/UIProtocol';
8import Utils from './Utils';
9import config from '@/assets/config';
10import { v4 as uuidv4 } from 'uuid';
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
29 public static get instance() {
30 if (UIClient._instance === null) {
31 UIClient._instance = new UIClient();
32 }
33 return UIClient._instance;
34 }
35
16fe3949 36 public registerWSonOpenListener(listener: (this: WebSocket, ev: 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
JB
52 public async startChargingStation(hashId: string): Promise<ResponsePayload> {
53 return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashId });
54 }
55
56 public async stopChargingStation(hashId: string): Promise<ResponsePayload> {
57 return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashId });
58 }
59
60 public async openConnection(hashId: string): Promise<ResponsePayload> {
61 return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
62 hashId,
63 });
64 }
65
66 public async closeConnection(hashId: string): Promise<ResponsePayload> {
67 return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
68 hashId,
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
LM
77 return this.sendRequest(ProcedureName.START_TRANSACTION, {
78 hashId,
79 connectorId,
80 idTag,
81 });
82 }
83
5a010bf0
JB
84 public async stopTransaction(
85 hashId: string,
86 transactionId: number | undefined
87 ): Promise<ResponsePayload> {
32de5a57
LM
88 return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
89 hashId,
90 transactionId,
91 });
92 }
93
5a010bf0
JB
94 private openWS(): void {
95 this._ws = new WebSocket(
96 `ws://${config.emobility.host}:${config.emobility.port}`,
97 config.emobility.protocol
98 );
99 this._ws.onmessage = this.responseHandler.bind(this);
a745e412
JB
100 this._ws.onerror = (error) => {
101 console.error('WebSocket error: ', error);
102 };
5a010bf0
JB
103 }
104
32de5a57
LM
105 private setResponseHandler(
106 id: string,
1f7fa4de 107 procedureName: ProcedureName,
32de5a57 108 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void,
1f7fa4de 109 reject: (reason?: any) => void
32de5a57
LM
110 ): void {
111 this._responseHandlers.set(id, { resolve, reject, procedureName });
112 }
113
114 private getResponseHandler(id: string): ResponseHandler | undefined {
115 return this._responseHandlers.get(id);
116 }
117
1f7fa4de
JB
118 private deleteResponseHandler(id: string): boolean {
119 return this._responseHandlers.delete(id);
120 }
121
32de5a57
LM
122 private async sendRequest(command: ProcedureName, data: JsonType): Promise<ResponsePayload> {
123 let uuid: string;
124 return Utils.promiseWithTimeout(
125 new Promise((resolve, reject) => {
126 uuid = uuidv4();
127 const msg = JSON.stringify([uuid, command, data]);
128
5a010bf0
JB
129 if (this._ws.readyState !== WebSocket.OPEN) {
130 this.openWS();
131 }
132 if (this._ws.readyState === WebSocket.OPEN) {
32de5a57
LM
133 this._ws.send(msg);
134 } else {
135 throw new Error(`Send request ${command} message: connection not opened`);
136 }
137
1f7fa4de 138 this.setResponseHandler(uuid, command, resolve, reject);
32de5a57
LM
139 }),
140 60 * 1000,
141 Error(`Send request ${command} message timeout`),
142 () => {
143 this._responseHandlers.delete(uuid);
144 }
145 );
146 }
147
97f0a1a5 148 private responseHandler(messageEvent: MessageEvent<string>): void {
32de5a57
LM
149 const data = JSON.parse(messageEvent.data) as ProtocolResponse;
150
53e5fd67
JB
151 if (Array.isArray(data) === false) {
152 throw new Error('Response not an array: ' + JSON.stringify(data, null, 2));
32de5a57
LM
153 }
154
155 const [uuid, response] = data;
156
157 if (this._responseHandlers.has(uuid) === true) {
158 switch (response.status) {
159 case ResponseStatus.SUCCESS:
160 this.getResponseHandler(uuid)?.resolve(response);
161 break;
162 case ResponseStatus.FAILURE:
163 this.getResponseHandler(uuid)?.reject(response);
164 break;
165 default:
a745e412 166 console.error(`Response status not supported: ${response.status}`);
32de5a57 167 }
1f7fa4de 168 this.deleteResponseHandler(uuid);
32de5a57
LM
169 } else {
170 throw new Error('Not a response to a request: ' + JSON.stringify(data, null, 2));
171 }
172 }
173}