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