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.TimeoutException;
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;
38 import com.google.gson.Gson;
39 import com.google.gson.GsonBuilder;
40 import com.google.gson.JsonSyntaxException;
43 * The {@link VizioCommunicator} class contains methods for accessing the HTTP interface of Vizio TVs
45 * @author Michael Lobstein - Initial contribution
48 public class VizioCommunicator {
49 private final Logger logger = LoggerFactory.getLogger(VizioCommunicator.class);
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}";
55 private final HttpClient httpClient;
56 private final Gson gson = new GsonBuilder().serializeNulls().create();
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;
70 public VizioCommunicator(HttpClient httpClient, String host, int port, String authToken) {
71 this.httpClient = httpClient;
72 this.authToken = authToken;
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";
88 * Get the current power state of the Vizio TV
90 * @return A PowerMode response object
91 * @throws VizioException
94 public PowerMode getPowerMode() throws VizioException {
95 return fromJson(getCommand(urlPowerMode), PowerMode.class);
99 * Get the current audio settings of the Vizio TV
101 * @return An Audio response object
102 * @throws VizioException
105 public Audio getCurrentAudioSettings() throws VizioException {
106 return fromJson(getCommand(urlCurrentAudio), Audio.class);
110 * Change the volume of the Vizio TV
112 * @param commandJSON the command JSON for the desired volue
113 * @return A PutResponse response object
114 * @throws VizioException
117 public PutResponse changeVolume(String commandJSON) throws VizioException {
118 return fromJson(putCommand(urlChangeVolume, commandJSON), PutResponse.class);
122 * Get the currently selected input of the Vizio TV
124 * @return A CurrentInput response object
125 * @throws VizioException
128 public CurrentInput getCurrentInput() throws VizioException {
129 return fromJson(getCommand(urlCurrentInput), CurrentInput.class);
133 * Change the currently selected input of the Vizio TV
135 * @param commandJSON the command JSON for the selected input
136 * @return A PutResponse response object
137 * @throws VizioException
140 public PutResponse changeInput(String commandJSON) throws VizioException {
141 return fromJson(putCommand(urlCurrentInput, commandJSON), PutResponse.class);
145 * Get the list of available source inputs from the Vizio TV
147 * @return An InputList response object
148 * @throws VizioException
151 public InputList getSourceInputList() throws VizioException {
152 return fromJson(getCommand(urlInputList), InputList.class);
156 * Get the id of the app currently running on the Vizio TV
158 * @return A CurrentApp response object
159 * @throws VizioException
162 public CurrentApp getCurrentApp() throws VizioException {
163 return fromJson(getCommand(urlCurrentApp), CurrentApp.class);
167 * Launch a given streaming app on the Vizio TV
169 * @param appConfig the VizioAppConfig data for the app to launch
170 * @return A PutResponse response object
171 * @throws VizioException
174 public PutResponse launchApp(VizioAppConfig appConfig) throws VizioException {
175 return fromJson(putCommand(urlLaunchApp, String.format(JSON_VALUE, gson.toJson(appConfig))), PutResponse.class);
179 * Send a key press command to the Vizio TV
181 * @param commandJSON the command JSON for the key press
182 * @return A PutResponse response object
183 * @throws VizioException
186 public PutResponse sendKeyPress(String commandJSON) throws VizioException {
187 return fromJson(putCommand(urlKeyPress, commandJSON), PutResponse.class);
191 * Start the pairing process to obtain an auth token from the TV
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
199 public PairingStart startPairing(String deviceName, int deviceId) throws VizioException {
201 putCommand(urlStartPairing,
202 String.format("{ \"DEVICE_NAME\": \"%s\", \"DEVICE_ID\": \"%d\" }", deviceName, deviceId)),
207 * Finish the pairing process by submitting the code that was displayed on the TV to obtain the auth token
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
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);
223 * Sends a GET request to the Vizio TV
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
230 private String getCommand(String url) throws VizioException {
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);
236 final ContentResponse response = request.send();
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());
246 * Sends a PUT request to the Vizio TV
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
254 private String putCommand(String url, String commandJSON) throws VizioException {
256 final Request request = httpClient.newRequest(url).method(HttpMethod.PUT);
257 if (!url.contains("pairing")) {
258 request.header(AUTH_HEADER, authToken);
260 request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
262 final ContentResponse response = request.send();
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());
272 * Wrapper for the Gson fromJson() method that encapsulates exception handling
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
280 private <T> T fromJson(String json, Class<T> classOfT) throws VizioException {
283 obj = gson.fromJson(json, classOfT);
284 } catch (JsonSyntaxException e) {
285 throw new VizioException("Error Parsing JSON string: " + json + ", Exception: " + e.getMessage());
288 return classOfT.cast(obj);
290 throw new VizioException("Error creating " + classOfT.getSimpleName() + " object for JSON string: " + json);