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