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