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