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