Commit | Line | Data |
---|---|---|
757b2ecf | 1 | import { ProcedureName, ResponseStatus, type RequestPayload } from '@/types/UIProtocol'; |
f27eb751 | 2 | import type { ProtocolResponse, ResponsePayload } from '@/types/UIProtocol'; |
6c1761d4 | 3 | |
32de5a57 LM |
4 | import Utils from './Utils'; |
5 | import config from '@/assets/config'; | |
6 | import { v4 as uuidv4 } from 'uuid'; | |
7 | ||
8 | type ResponseHandler = { | |
1f7fa4de | 9 | procedureName: ProcedureName; |
32de5a57 LM |
10 | resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void; |
11 | reject: (reason?: any) => void; | |
32de5a57 LM |
12 | }; |
13 | ||
14 | export 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 | } |