]> git.basschouten.com Git - openhab-addons.git/blob
cca3aabba69d83e9e9f29dc89979d2f88227f207
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.vizio.internal.communication;
14
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jetty.client.HttpClient;
21 import org.eclipse.jetty.client.api.ContentResponse;
22 import org.eclipse.jetty.client.api.Request;
23 import org.eclipse.jetty.client.util.StringContentProvider;
24 import org.eclipse.jetty.http.HttpHeader;
25 import org.eclipse.jetty.http.HttpMethod;
26 import org.openhab.binding.vizio.internal.VizioException;
27 import org.openhab.binding.vizio.internal.dto.PutResponse;
28 import org.openhab.binding.vizio.internal.dto.app.CurrentApp;
29 import org.openhab.binding.vizio.internal.dto.applist.VizioAppConfig;
30 import org.openhab.binding.vizio.internal.dto.audio.Audio;
31 import org.openhab.binding.vizio.internal.dto.input.CurrentInput;
32 import org.openhab.binding.vizio.internal.dto.inputlist.InputList;
33 import org.openhab.binding.vizio.internal.dto.pairing.PairingComplete;
34 import org.openhab.binding.vizio.internal.dto.pairing.PairingStart;
35 import org.openhab.binding.vizio.internal.dto.power.PowerMode;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import com.google.gson.Gson;
40 import com.google.gson.GsonBuilder;
41 import com.google.gson.JsonSyntaxException;
42
43 /**
44  * The {@link VizioCommunicator} class contains methods for accessing the HTTP interface of Vizio TVs
45  *
46  * @author Michael Lobstein - Initial contribution
47  */
48 @NonNullByDefault
49 public class VizioCommunicator {
50     private final Logger logger = LoggerFactory.getLogger(VizioCommunicator.class);
51
52     private static final String AUTH_HEADER = "AUTH";
53     private static final String JSON_CONTENT_TYPE = "application/json";
54     private static final String JSON_VALUE = "{\"VALUE\": %s}";
55     private static final int REQUEST_TIMEOUT_MS = 10_000;
56
57     private final HttpClient httpClient;
58     private final Gson gson = new GsonBuilder().serializeNulls().create();
59
60     private final String authToken;
61     private final String urlPowerMode;
62     private final String urlCurrentAudio;
63     private final String urlCurrentInput;
64     private final String urlInputList;
65     private final String urlChangeVolume;
66     private final String urlCurrentApp;
67     private final String urlLaunchApp;
68     private final String urlKeyPress;
69     private final String urlStartPairing;
70     private final String urlSubmitPairingCode;
71
72     public VizioCommunicator(HttpClient httpClient, String host, int port, String authToken) {
73         this.httpClient = httpClient;
74         this.authToken = authToken;
75
76         final String baseUrl = "https://" + host + ":" + port;
77         urlPowerMode = baseUrl + "/state/device/power_mode";
78         urlCurrentAudio = baseUrl + "/menu_native/dynamic/tv_settings/audio";
79         urlChangeVolume = baseUrl + "/menu_native/dynamic/tv_settings/audio/volume";
80         urlCurrentInput = baseUrl + "/menu_native/dynamic/tv_settings/devices/current_input";
81         urlInputList = baseUrl + "/menu_native/dynamic/tv_settings/devices/name_input";
82         urlCurrentApp = baseUrl + "/app/current";
83         urlLaunchApp = baseUrl + "/app/launch";
84         urlKeyPress = baseUrl + "/key_command/";
85         urlStartPairing = baseUrl + "/pairing/start";
86         urlSubmitPairingCode = baseUrl + "/pairing/pair";
87     }
88
89     /**
90      * Get the current power state of the Vizio TV
91      *
92      * @return A PowerMode response object
93      * @throws VizioException
94      *
95      */
96     public PowerMode getPowerMode() throws VizioException {
97         return fromJson(getCommand(urlPowerMode), PowerMode.class);
98     }
99
100     /**
101      * Get the current audio settings of the Vizio TV
102      *
103      * @return An Audio response object
104      * @throws VizioException
105      *
106      */
107     public Audio getCurrentAudioSettings() throws VizioException {
108         return fromJson(getCommand(urlCurrentAudio), Audio.class);
109     }
110
111     /**
112      * Change the volume of the Vizio TV
113      *
114      * @param commandJSON the command JSON for the desired volue
115      * @return A PutResponse response object
116      * @throws VizioException
117      *
118      */
119     public PutResponse changeVolume(String commandJSON) throws VizioException {
120         return fromJson(putCommand(urlChangeVolume, commandJSON), PutResponse.class);
121     }
122
123     /**
124      * Get the currently selected input of the Vizio TV
125      *
126      * @return A CurrentInput response object
127      * @throws VizioException
128      *
129      */
130     public CurrentInput getCurrentInput() throws VizioException {
131         return fromJson(getCommand(urlCurrentInput), CurrentInput.class);
132     }
133
134     /**
135      * Change the currently selected input of the Vizio TV
136      *
137      * @param commandJSON the command JSON for the selected input
138      * @return A PutResponse response object
139      * @throws VizioException
140      *
141      */
142     public PutResponse changeInput(String commandJSON) throws VizioException {
143         return fromJson(putCommand(urlCurrentInput, commandJSON), PutResponse.class);
144     }
145
146     /**
147      * Get the list of available source inputs from the Vizio TV
148      *
149      * @return An InputList response object
150      * @throws VizioException
151      *
152      */
153     public InputList getSourceInputList() throws VizioException {
154         return fromJson(getCommand(urlInputList), InputList.class);
155     }
156
157     /**
158      * Get the id of the app currently running on the Vizio TV
159      *
160      * @return A CurrentApp response object
161      * @throws VizioException
162      *
163      */
164     public CurrentApp getCurrentApp() throws VizioException {
165         return fromJson(getCommand(urlCurrentApp), CurrentApp.class);
166     }
167
168     /**
169      * Launch a given streaming app on the Vizio TV
170      *
171      * @param appConfig the VizioAppConfig data for the app to launch
172      * @return A PutResponse response object
173      * @throws VizioException
174      *
175      */
176     public PutResponse launchApp(VizioAppConfig appConfig) throws VizioException {
177         return fromJson(putCommand(urlLaunchApp, String.format(JSON_VALUE, gson.toJson(appConfig))), PutResponse.class);
178     }
179
180     /**
181      * Send a key press command to the Vizio TV
182      *
183      * @param commandJSON the command JSON for the key press
184      * @return A PutResponse response object
185      * @throws VizioException
186      *
187      */
188     public PutResponse sendKeyPress(String commandJSON) throws VizioException {
189         return fromJson(putCommand(urlKeyPress, commandJSON), PutResponse.class);
190     }
191
192     /**
193      * Start the pairing process to obtain an auth token from the TV
194      *
195      * @param deviceName the deviceName that is displayed in the TV settings after the device is registered
196      * @param deviceId the deviceId a unique number that identifies this pairing request
197      * @return A PairingStart response object
198      * @throws VizioException
199      *
200      */
201     public PairingStart startPairing(String deviceName, int deviceId) throws VizioException {
202         return fromJson(
203                 putCommand(urlStartPairing,
204                         String.format("{ \"DEVICE_NAME\": \"%s\", \"DEVICE_ID\": \"%d\" }", deviceName, deviceId)),
205                 PairingStart.class);
206     }
207
208     /**
209      * Finish the pairing process by submitting the code that was displayed on the TV to obtain the auth token
210      *
211      * @param deviceId the same deviceId that was used by startPairing()
212      * @param pairingCode the pairingCode that was displayed on the TV
213      * @param pairingToken the pairingToken returned by startPairing()
214      * @return A PairingComplete response object
215      * @throws VizioException
216      *
217      */
218     public PairingComplete submitPairingCode(int deviceId, String pairingCode, int pairingToken) throws VizioException {
219         return fromJson(putCommand(urlSubmitPairingCode, String.format(
220                 "{\"DEVICE_ID\": \"%d\",\"CHALLENGE_TYPE\": 1,\"RESPONSE_VALUE\": \"%s\",\"PAIRING_REQ_TOKEN\": %d}",
221                 deviceId, pairingCode, pairingToken)), PairingComplete.class);
222     }
223
224     /**
225      * Sends a GET request to the Vizio TV
226      *
227      * @param url The url used to retrieve status information from the Vizio TV
228      * @return The response content of the http request
229      * @throws VizioException
230      *
231      */
232     private String getCommand(String url) throws VizioException {
233         try {
234             final Request request = httpClient.newRequest(url).method(HttpMethod.GET);
235             request.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
236             request.header(AUTH_HEADER, authToken);
237             request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
238
239             final ContentResponse response = request.send();
240
241             logger.trace("GET url: {}, response: {}", url, response.getContentAsString());
242             return response.getContentAsString();
243         } catch (InterruptedException | TimeoutException | ExecutionException e) {
244             throw new VizioException("Error executing vizio GET command, URL: " + url + " " + e.getMessage());
245         }
246     }
247
248     /**
249      * Sends a PUT request to the Vizio TV
250      *
251      * @param url The url used to send a command to the Vizio TV
252      * @param commandJSON The JSON data needed to execute the command
253      * @return The response content of the http request
254      * @throws VizioException
255      *
256      */
257     private String putCommand(String url, String commandJSON) throws VizioException {
258         try {
259             final Request request = httpClient.newRequest(url).method(HttpMethod.PUT);
260             request.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
261             if (!url.contains("pairing")) {
262                 request.header(AUTH_HEADER, authToken);
263             }
264             request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
265
266             final ContentResponse response = request.send();
267
268             logger.trace("PUT url: {}, response: {}", url, response.getContentAsString());
269             return response.getContentAsString();
270         } catch (InterruptedException | TimeoutException | ExecutionException e) {
271             throw new VizioException("Error executing vizio PUT command, URL: " + url + e.getMessage());
272         }
273     }
274
275     /**
276      * Wrapper for the Gson fromJson() method that encapsulates exception handling
277      *
278      * @param json The JSON string to be deserialized
279      * @param classOfT The type of class to be returned
280      * @return the deserialized object
281      * @throws VizioException
282      *
283      */
284     private <T> T fromJson(String json, Class<T> classOfT) throws VizioException {
285         Object obj = null;
286         try {
287             obj = gson.fromJson(json, classOfT);
288         } catch (JsonSyntaxException e) {
289             throw new VizioException("Error Parsing JSON string: " + json + ", Exception: " + e.getMessage());
290         }
291         if (obj != null) {
292             return classOfT.cast(obj);
293         } else {
294             throw new VizioException("Error creating " + classOfT.getSimpleName() + " object for JSON string: " + json);
295         }
296     }
297 }