2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.vizio.internal.communication;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
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;
39 import com.google.gson.Gson;
40 import com.google.gson.GsonBuilder;
41 import com.google.gson.JsonSyntaxException;
44 * The {@link VizioCommunicator} class contains methods for accessing the HTTP interface of Vizio TVs
46 * @author Michael Lobstein - Initial contribution
49 public class VizioCommunicator {
50 private final Logger logger = LoggerFactory.getLogger(VizioCommunicator.class);
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;
57 private final HttpClient httpClient;
58 private final Gson gson = new GsonBuilder().serializeNulls().create();
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;
72 public VizioCommunicator(HttpClient httpClient, String host, int port, String authToken) {
73 this.httpClient = httpClient;
74 this.authToken = authToken;
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";
90 * Get the current power state of the Vizio TV
92 * @return A PowerMode response object
93 * @throws VizioException
96 public PowerMode getPowerMode() throws VizioException {
97 return fromJson(getCommand(urlPowerMode), PowerMode.class);
101 * Get the current audio settings of the Vizio TV
103 * @return An Audio response object
104 * @throws VizioException
107 public Audio getCurrentAudioSettings() throws VizioException {
108 return fromJson(getCommand(urlCurrentAudio), Audio.class);
112 * Change the volume of the Vizio TV
114 * @param commandJSON the command JSON for the desired volue
115 * @return A PutResponse response object
116 * @throws VizioException
119 public PutResponse changeVolume(String commandJSON) throws VizioException {
120 return fromJson(putCommand(urlChangeVolume, commandJSON), PutResponse.class);
124 * Get the currently selected input of the Vizio TV
126 * @return A CurrentInput response object
127 * @throws VizioException
130 public CurrentInput getCurrentInput() throws VizioException {
131 return fromJson(getCommand(urlCurrentInput), CurrentInput.class);
135 * Change the currently selected input of the Vizio TV
137 * @param commandJSON the command JSON for the selected input
138 * @return A PutResponse response object
139 * @throws VizioException
142 public PutResponse changeInput(String commandJSON) throws VizioException {
143 return fromJson(putCommand(urlCurrentInput, commandJSON), PutResponse.class);
147 * Get the list of available source inputs from the Vizio TV
149 * @return An InputList response object
150 * @throws VizioException
153 public InputList getSourceInputList() throws VizioException {
154 return fromJson(getCommand(urlInputList), InputList.class);
158 * Get the id of the app currently running on the Vizio TV
160 * @return A CurrentApp response object
161 * @throws VizioException
164 public CurrentApp getCurrentApp() throws VizioException {
165 return fromJson(getCommand(urlCurrentApp), CurrentApp.class);
169 * Launch a given streaming app on the Vizio TV
171 * @param appConfig the VizioAppConfig data for the app to launch
172 * @return A PutResponse response object
173 * @throws VizioException
176 public PutResponse launchApp(VizioAppConfig appConfig) throws VizioException {
177 return fromJson(putCommand(urlLaunchApp, String.format(JSON_VALUE, gson.toJson(appConfig))), PutResponse.class);
181 * Send a key press command to the Vizio TV
183 * @param commandJSON the command JSON for the key press
184 * @return A PutResponse response object
185 * @throws VizioException
188 public PutResponse sendKeyPress(String commandJSON) throws VizioException {
189 return fromJson(putCommand(urlKeyPress, commandJSON), PutResponse.class);
193 * Start the pairing process to obtain an auth token from the TV
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
201 public PairingStart startPairing(String deviceName, int deviceId) throws VizioException {
203 putCommand(urlStartPairing,
204 String.format("{ \"DEVICE_NAME\": \"%s\", \"DEVICE_ID\": \"%d\" }", deviceName, deviceId)),
209 * Finish the pairing process by submitting the code that was displayed on the TV to obtain the auth token
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
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);
225 * Sends a GET request to the Vizio TV
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
232 private String getCommand(String url) throws VizioException {
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);
239 final ContentResponse response = request.send();
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());
249 * Sends a PUT request to the Vizio TV
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
257 private String putCommand(String url, String commandJSON) throws VizioException {
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);
264 request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
266 final ContentResponse response = request.send();
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());
276 * Wrapper for the Gson fromJson() method that encapsulates exception handling
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
284 private <T> T fromJson(String json, Class<T> classOfT) throws VizioException {
287 obj = gson.fromJson(json, classOfT);
288 } catch (JsonSyntaxException e) {
289 throw new VizioException("Error Parsing JSON string: " + json + ", Exception: " + e.getMessage());
292 return classOfT.cast(obj);
294 throw new VizioException("Error creating " + classOfT.getSimpleName() + " object for JSON string: " + json);