]> git.basschouten.com Git - openhab-addons.git/commitdiff
[vizio] Vizio TV binding - Initial contribution (#13309)
authormlobstein <michael.lobstein@gmail.com>
Tue, 6 Dec 2022 14:37:54 +0000 (08:37 -0600)
committerGitHub <noreply@github.com>
Tue, 6 Dec 2022 14:37:54 +0000 (15:37 +0100)
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
45 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.vizio/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.vizio/README.md [new file with mode: 0644]
bundles/org.openhab.binding.vizio/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioException.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioStateDescriptionOptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/appdb/VizioAppDbService.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioCommunicator.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioTlsTrustManagerProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/console/VizioCommandExtension.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/discovery/VizioDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Item.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Parameters.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/PutResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Status.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Value.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/CurrentApp.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemApp.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemAppValue.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApp.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioAppConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApps.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/Audio.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/ItemAudio.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/CurrentInput.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/ItemInput.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/inputlist/InputList.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemAuthToken.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemPairing.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingComplete.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingStart.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/ItemPower.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/PowerMode.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/enums/KeyCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/handler/VizioHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/i18n/vizio.properties [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/thing/vizio.xml [new file with mode: 0644]
bundles/org.openhab.binding.vizio/src/main/resources/db/apps.json [new file with mode: 0644]
bundles/pom.xml

index 2714540d6da90ca24543352f6fee5aa31b3f8b0b..a275a0b71cc90695fd37d5926c1d80d28ab3f212 100644 (file)
 /bundles/org.openhab.binding.vesync/ @dag81
 /bundles/org.openhab.binding.vigicrues/ @clinique
 /bundles/org.openhab.binding.vitotronic/ @steand
+/bundles/org.openhab.binding.vizio/ @mlobstein
 /bundles/org.openhab.binding.volvooncall/ @clinique @Jamstah
 /bundles/org.openhab.binding.warmup/ @jamesmelville
 /bundles/org.openhab.binding.weathercompany/ @mhilbush
index 53d0bec852dfc48bc66267cc368dcce61b9e46eb..52b327901c7c62169f3271e3432ad86f91d56f4d 100644 (file)
       <artifactId>org.openhab.binding.vitotronic</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.vizio</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.volvooncall</artifactId>
diff --git a/bundles/org.openhab.binding.vizio/NOTICE b/bundles/org.openhab.binding.vizio/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.vizio/README.md b/bundles/org.openhab.binding.vizio/README.md
new file mode 100644 (file)
index 0000000..7c72f7b
--- /dev/null
@@ -0,0 +1,181 @@
+# Vizio Binding
+
+This binding connects Vizio TVs to openHAB.  
+The TV must support the Vizio SmartCast API that is found on 2016 and later models.
+
+## Supported Things
+
+There is currently only one supported thing type, which represents a Vizio TV using the `vizio_tv` id.
+Multiple Things can be added if more than one Vizio TV is to be controlled.
+
+## Discovery
+
+Auto-discovery is supported if the Vizio TV can be located on the local network using mDNS.
+Otherwise the thing must be manually added.
+When the TV is discovered, a pairing process to obtain an authentication token from the TV must be completed using the openHAB console. See below for details.
+
+## Thing Configuration
+
+The thing has a few configuration parameters:
+
+| Parameter   | Description                                                                                                                          |
+|-------------|--------------------------------------------------------------------------------------------------------------------------------------|
+| hostName    | The host name or IP address of the Vizio TV. Mandatory.                                                                              |
+| port        | The port on the Vizio TV that listens for https connections. Default 7345; Use 9000 for older model TVs.                             |
+| authToken   | The token that is used to authenticate all commands sent to the TV. See below for instructions to obtain via the openHAB console.    |
+| appListJson | A JSON string that defines the apps that are available in the `activeApp` channel drop down. See below for instructions for editing. |
+
+### Console Commands for Pairing:
+
+To obtain an authorization token that enables openHAB to authenticate with the TV, the following console commands must be used while the TV is turned on.
+The first command will send a pairing start request to the TV. This triggers the TV to display a 4-digit pairing code on screen that must be sent with the second command.
+
+Start Pairing:
+
+```
+openhab:vizio <thingUID> start_pairing <deviceName>
+```
+
+Substitute `<thingUID>` with thing's id, ie: `vizio_tv:00bc3e711660`  
+Substitute `<deviceName>` the desired device name that will appear in the TV's settings, under Mobile Devices, ie: `Vizio-openHAB`  
+
+Submit Pairing Code:
+
+```
+openhab:vizio <thingUID> submit_code <pairingCode>
+```
+
+Substitute `<thingUID>` with the same thing id used above  
+Substitute `<pairingCode>` with the 4-digit pairing code displayed on the TV, ie: `1234`  
+
+The console should then indicate that pairing was successful (token will be displayed) and that the token was saved to the thing configuration.
+If using file-based provisioning of the thing, the authorization token must be added to the thing configuration manually.
+With an authorization token in place, the binding can now control the TV.  
+
+The authorization token text can be re-used in the event that it becomes necessary to setup the binding again.
+By simply adding the token that is already recognized by the TV to the thing configuration, the pairing process can be bypassed.
+
+## Channels
+
+The following channels are available:
+
+| Channel ID  | Item Type | Description                                                                                                                                                                                 |
+|-------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| power       | Switch    | Turn the power on the TV on or off. Note: TV may not turn on if power is switched off and the TV is configured for Eco mode.                                                                |
+| volume      | Dimmer    | Control the volume on the TV (0-100%).                                                                                                                                                      |
+| mute        | Switch    | Mute or unmute the volume on the TV.                                                                                                                                                        |
+| source      | String    | Select the source input on the TV. The dropdown list is automatically populated from the TV.                                                                                                |
+| activeApp   | String    | A dropdown containing a list of streaming apps defined by the `appListJson` config option that can be launched by the binding. An app started via remote control is automatically selected. |
+| control     | Player    | Control Playback e.g. Play/Pause/Next/Previous/FForward/Rewind                                                                                                                              |
+| button      | String    | Sends a remote control command the TV. See list of available commands below.                                                                                                                |
+
+### List of available button commands for Vizio TVs:
+
+PowerOn  
+PowerOff  
+PowerToggle  
+VolumeUp  
+VolumeDown  
+MuteOn **(may only work as a toggle)**  
+MuteOff **(may only work as a toggle)**  
+MuteToggle  
+ChannelUp  
+ChannelDown  
+PreviousCh  
+InputToggle  
+SeekFwd  
+SeekBack  
+Play  
+Pause  
+Up  
+Down  
+Left  
+Right  
+Ok  
+Back  
+Info  
+Menu  
+Home  
+Exit  
+Smartcast  
+ccToggle  
+PictureMode  
+WideMode  
+WideToggle
+
+### App List Configuration:
+
+The Vizio API to launch and identify currently running apps on the TV is very complex.
+To handle this, the binding maintains a JSON database of applications and their associated metadata in order to populate the `activeApp` dropdown with available apps.  
+
+When the thing is started for the first time, this JSON database is saved into the `appListJson` configuration parameter.
+This list of apps can be edited via the script editor on the thing configuration.
+By editing the JSON, apps that are not desired can be removed from the `activeApp` dropdown and newly discovered apps can be added.
+
+An entry for an application has a `name` element and a `config` element containing `APP_ID`, `NAME_SPACE` and `MESSAGE` (null for most apps):
+
+```
+{
+   "name": "Crackle",
+   "config": {
+      "APP_ID": "5",
+      "NAME_SPACE": 4,
+      "MESSAGE": null
+   }
+},
+
+```
+
+If an app is running that is not currently recognized by the binding, the `activeApp` channel will display a message that contains the `APP_ID` and `NAME_SPACE` which can be used to create the missing record for that app in the JSON.
+
+If an app that is in the JSON database fails to start when selected, try adjusting the `NAME_SPACE` value for that app.
+`NAME_SPACE` seems to be a version number and adjusting the number up or down may correct the mismatch between the TV and the binding. 
+
+A current list of `APP_ID`'s can be found at http://hometest.buddytv.netdna-cdn.com/appservice/vizio_apps_prod.json
+and `NAME_SPACE` &amp; `MESSAGE` values needed can be found at http://hometest.buddytv.netdna-cdn.com/appservice/app_availability_prod.json
+
+If there is an error in the user supplied `appListJson`, the thing will fail to start and display a CONFIGURATION_PENDING message.
+If all text in `appListJson` is removed (set to null) and the thing configuration saved, the binding will restore `appListJson` from the binding's JSON db.
+
+## Full Example
+
+vizio.things:
+
+```
+// Vizio TV
+vizio:vizio_tv:mytv1 "My Vizio TV" [ hostName="192.168.10.1", port=7345, authToken="idspisp0pd" ]
+
+```
+
+vizio.items:
+
+```
+// Vizio TV items:
+
+Switch TV_Power       "Power"              { channel="vizio:vizio_tv:mytv1:power" }
+Dimmer TV_Volume      "Volume [%d %%]"     { channel="vizio:vizio_tv:mytv1:volume" }
+Switch TV_Mute        "Mute"               { channel="vizio:vizio_tv:mytv1:mute" }
+String TV_Source      "Source Input [%s]"  { channel="vizio:vizio_tv:mytv1:source" }
+String TV_ActiveApp   "Current App: [%s]"  { channel="vizio:vizio_tv:mytv1:activeApp" }
+Player TV_Control     "Playback Control"   { channel="vizio:vizio_tv:mytv1:control" }
+String TV_Button      "Send Command to TV" { channel="vizio:vizio_tv:mytv1:button" }
+
+```
+
+vizio.sitemap:
+
+```
+sitemap vizio label="Vizio" {
+    Frame label="My Vizio TV" {
+        Switch item=TV_Power
+        // Volume can be a Setpoint also
+        Slider item=TV_Volume minValue=0 maxValue=100 step=1 icon="soundvolume"
+        Switch item=TV_Mute icon="soundvolume_mute"
+        Selection item=TV_Source icon="screen"
+        Selection item=TV_ActiveApp icon="screen"
+        Default item=TV_Control
+        Selection item=TV_Button
+    }
+}
+
+```
diff --git a/bundles/org.openhab.binding.vizio/pom.xml b/bundles/org.openhab.binding.vizio/pom.xml
new file mode 100644 (file)
index 0000000..34c5fbd
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>3.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.vizio</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Vizio Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.vizio/src/main/feature/feature.xml b/bundles/org.openhab.binding.vizio/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..83ec41e
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.vizio-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-vizio" description="Vizio Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <feature>openhab-transport-mdns</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.vizio/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioBindingConstants.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioBindingConstants.java
new file mode 100644 (file)
index 0000000..3198cd7
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link VizioBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class VizioBindingConstants {
+    public static final String BINDING_ID = "vizio";
+    public static final String PROPERTY_UUID = "uuid";
+    public static final String PROPERTY_HOST_NAME = "hostName";
+    public static final String PROPERTY_PORT = "port";
+    public static final String PROPERTY_AUTH_TOKEN = "authToken";
+    public static final String PROPERTY_APP_LIST_JSON = "appListJson";
+    public static final String EMPTY = "";
+    public static final String MODIFY_STRING_SETTING_JSON = "{\"REQUEST\": \"MODIFY\",\"VALUE\": \"%s\",\"HASHVAL\": %d}";
+    public static final String MODIFY_INT_SETTING_JSON = "{\"REQUEST\": \"MODIFY\",\"VALUE\": %d,\"HASHVAL\": %d}";
+    public static final String UNKNOWN_APP_STR = "Unknown app_id: %d, name_space: %d";
+    public static final String ON = "ON";
+    public static final String OFF = "OFF";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_VIZIO_TV = new ThingTypeUID(BINDING_ID, "vizio_tv");
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_VIZIO_TV);
+
+    // List of all Channel id's
+    public static final String POWER = "power";
+    public static final String VOLUME = "volume";
+    public static final String MUTE = "mute";
+    public static final String SOURCE = "source";
+    public static final String ACTIVE_APP = "activeApp";
+    public static final String CONTROL = "control";
+    public static final String BUTTON = "button";
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioConfiguration.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioConfiguration.java
new file mode 100644 (file)
index 0000000..ab95e29
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link VizioConfiguration} is the class used to match the
+ * thing configuration.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class VizioConfiguration {
+    public @Nullable String hostName;
+    public Integer port = 7345;
+    public @Nullable String authToken;
+    public @Nullable String appListJson;
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioException.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioException.java
new file mode 100644 (file)
index 0000000..21ccb4d
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link VizioException} extends Exception
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class VizioException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public VizioException(String errorMessage) {
+        super(errorMessage);
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioHandlerFactory.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioHandlerFactory.java
new file mode 100644 (file)
index 0000000..9c7be00
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal;
+
+import static org.openhab.binding.vizio.internal.VizioBindingConstants.SUPPORTED_THING_TYPES_UIDS;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.vizio.internal.appdb.VizioAppDbService;
+import org.openhab.binding.vizio.internal.handler.VizioHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link VizioHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.vizio")
+public class VizioHandlerFactory extends BaseThingHandlerFactory {
+
+    private final HttpClient httpClient;
+    private final VizioStateDescriptionOptionProvider stateDescriptionProvider;
+    private final String vizioAppsJson;
+
+    @Activate
+    public VizioHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
+            final @Reference VizioStateDescriptionOptionProvider provider,
+            final @Reference VizioAppDbService vizioAppDbService) {
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+        this.stateDescriptionProvider = provider;
+        this.vizioAppsJson = vizioAppDbService.getVizioAppsJson();
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
+            VizioHandler handler = new VizioHandler(thing, httpClient, stateDescriptionProvider, vizioAppsJson);
+            return handler;
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioStateDescriptionOptionProvider.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioStateDescriptionOptionProvider.java
new file mode 100644 (file)
index 0000000..5a51cbc
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link VizioStateDescriptionOptionProvider} is a Dynamic provider of state options while leaving other state
+ * description fields as original.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, VizioStateDescriptionOptionProvider.class })
+@NonNullByDefault
+public class VizioStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
+
+    @Activate
+    public VizioStateDescriptionOptionProvider(final @Reference EventPublisher eventPublisher,
+            final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
+            final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+        this.eventPublisher = eventPublisher;
+        this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+        this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/appdb/VizioAppDbService.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/appdb/VizioAppDbService.java
new file mode 100644 (file)
index 0000000..a1694d0
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.appdb;
+
+import static org.openhab.binding.vizio.internal.VizioBindingConstants.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link VizioAppDbService} class makes available a JSON list of known apps on Vizio TVs.
+ *
+ * @author Michael Lobstein - Initial Contribution
+ */
+
+@Component(service = VizioAppDbService.class)
+@NonNullByDefault
+public class VizioAppDbService {
+    private final Logger logger = LoggerFactory.getLogger(VizioAppDbService.class);
+    private String vizioAppsJson;
+
+    @Activate
+    public VizioAppDbService() {
+        try {
+            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/db/apps.json");
+            if (is != null) {
+                vizioAppsJson = new String(is.readAllBytes(), StandardCharsets.UTF_8);
+            } else {
+                vizioAppsJson = EMPTY;
+            }
+        } catch (IOException e) {
+            logger.warn("Unable to load Vizio app list : {}", e.getMessage());
+            vizioAppsJson = EMPTY;
+        }
+    }
+
+    public String getVizioAppsJson() {
+        return vizioAppsJson;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioCommunicator.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioCommunicator.java
new file mode 100644 (file)
index 0000000..d709593
--- /dev/null
@@ -0,0 +1,293 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.communication;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.vizio.internal.VizioException;
+import org.openhab.binding.vizio.internal.dto.PutResponse;
+import org.openhab.binding.vizio.internal.dto.app.CurrentApp;
+import org.openhab.binding.vizio.internal.dto.applist.VizioAppConfig;
+import org.openhab.binding.vizio.internal.dto.audio.Audio;
+import org.openhab.binding.vizio.internal.dto.input.CurrentInput;
+import org.openhab.binding.vizio.internal.dto.inputlist.InputList;
+import org.openhab.binding.vizio.internal.dto.pairing.PairingComplete;
+import org.openhab.binding.vizio.internal.dto.pairing.PairingStart;
+import org.openhab.binding.vizio.internal.dto.power.PowerMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link VizioCommunicator} class contains methods for accessing the HTTP interface of Vizio TVs
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class VizioCommunicator {
+    private final Logger logger = LoggerFactory.getLogger(VizioCommunicator.class);
+
+    private static final String AUTH_HEADER = "AUTH";
+    private static final String JSON_CONTENT_TYPE = "application/json";
+    private static final String JSON_VALUE = "{\"VALUE\": %s}";
+
+    private final HttpClient httpClient;
+    private final Gson gson = new GsonBuilder().serializeNulls().create();
+
+    private final String authToken;
+    private final String urlPowerMode;
+    private final String urlCurrentAudio;
+    private final String urlCurrentInput;
+    private final String urlInputList;
+    private final String urlChangeVolume;
+    private final String urlCurrentApp;
+    private final String urlLaunchApp;
+    private final String urlKeyPress;
+    private final String urlStartPairing;
+    private final String urlSubmitPairingCode;
+
+    public VizioCommunicator(HttpClient httpClient, String host, int port, String authToken) {
+        this.httpClient = httpClient;
+        this.authToken = authToken;
+
+        final String baseUrl = "https://" + host + ":" + port;
+        urlPowerMode = baseUrl + "/state/device/power_mode";
+        urlCurrentAudio = baseUrl + "/menu_native/dynamic/tv_settings/audio";
+        urlChangeVolume = baseUrl + "/menu_native/dynamic/tv_settings/audio/volume";
+        urlCurrentInput = baseUrl + "/menu_native/dynamic/tv_settings/devices/current_input";
+        urlInputList = baseUrl + "/menu_native/dynamic/tv_settings/devices/name_input";
+        urlCurrentApp = baseUrl + "/app/current";
+        urlLaunchApp = baseUrl + "/app/launch";
+        urlKeyPress = baseUrl + "/key_command/";
+        urlStartPairing = baseUrl + "/pairing/start";
+        urlSubmitPairingCode = baseUrl + "/pairing/pair";
+    }
+
+    /**
+     * Get the current power state of the Vizio TV
+     *
+     * @return A PowerMode response object
+     * @throws VizioException
+     *
+     */
+    public PowerMode getPowerMode() throws VizioException {
+        return fromJson(getCommand(urlPowerMode), PowerMode.class);
+    }
+
+    /**
+     * Get the current audio settings of the Vizio TV
+     *
+     * @return An Audio response object
+     * @throws VizioException
+     *
+     */
+    public Audio getCurrentAudioSettings() throws VizioException {
+        return fromJson(getCommand(urlCurrentAudio), Audio.class);
+    }
+
+    /**
+     * Change the volume of the Vizio TV
+     *
+     * @param the command JSON for the desired volue
+     * @return A PutResponse response object
+     * @throws VizioException
+     *
+     */
+    public PutResponse changeVolume(String commandJSON) throws VizioException {
+        return fromJson(putCommand(urlChangeVolume, commandJSON), PutResponse.class);
+    }
+
+    /**
+     * Get the currently selected input of the Vizio TV
+     *
+     * @return A CurrentInput response object
+     * @throws VizioException
+     *
+     */
+    public CurrentInput getCurrentInput() throws VizioException {
+        return fromJson(getCommand(urlCurrentInput), CurrentInput.class);
+    }
+
+    /**
+     * Change the currently selected input of the Vizio TV
+     *
+     * @param the command JSON for the selected input
+     * @return A PutResponse response object
+     * @throws VizioException
+     *
+     */
+    public PutResponse changeInput(String commandJSON) throws VizioException {
+        return fromJson(putCommand(urlCurrentInput, commandJSON), PutResponse.class);
+    }
+
+    /**
+     * Get the list of available source inputs from the Vizio TV
+     *
+     * @return An InputList response object
+     * @throws VizioException
+     *
+     */
+    public InputList getSourceInputList() throws VizioException {
+        return fromJson(getCommand(urlInputList), InputList.class);
+    }
+
+    /**
+     * Get the id of the app currently running on the Vizio TV
+     *
+     * @return A CurrentApp response object
+     * @throws VizioException
+     *
+     */
+    public CurrentApp getCurrentApp() throws VizioException {
+        return fromJson(getCommand(urlCurrentApp), CurrentApp.class);
+    }
+
+    /**
+     * Launch a given streaming app on the Vizio TV
+     *
+     * @param the VizioAppConfig data for the app to launch
+     * @return A PutResponse response object
+     * @throws VizioException
+     *
+     */
+    public PutResponse launchApp(VizioAppConfig appConfig) throws VizioException {
+        return fromJson(putCommand(urlLaunchApp, String.format(JSON_VALUE, gson.toJson(appConfig))), PutResponse.class);
+    }
+
+    /**
+     * Send a key press command to the Vizio TV
+     *
+     * @param the command JSON for the key press
+     * @return A PutResponse response object
+     * @throws VizioException
+     *
+     */
+    public PutResponse sendKeyPress(String commandJSON) throws VizioException {
+        return fromJson(putCommand(urlKeyPress, commandJSON), PutResponse.class);
+    }
+
+    /**
+     * Start the pairing process to obtain an auth token from the TV
+     *
+     * @param the deviceName that is displayed in the TV settings after the device is registered
+     * @param the deviceId a unique number that identifies this pairing request
+     * @return A PairingStart response object
+     * @throws VizioException
+     *
+     */
+    public PairingStart starPairing(String deviceName, int deviceId) throws VizioException {
+        return fromJson(
+                putCommand(urlStartPairing,
+                        String.format("{ \"DEVICE_NAME\": \"%s\", \"DEVICE_ID\": \"%d\" }", deviceName, deviceId)),
+                PairingStart.class);
+    }
+
+    /**
+     * Finish the pairing process by submitting the code that was displayed on the TV to obtain the auth token
+     *
+     * @param the same deviceId that was used by startPairing()
+     * @param the pairingCode that was displayed on the TV
+     * @param the pairingToken returned by startPairing()
+     * @return A PairingComplete response object
+     * @throws VizioException
+     *
+     */
+    public PairingComplete submitPairingCode(int deviceId, String pairingCode, int pairingToken) throws VizioException {
+        return fromJson(putCommand(urlSubmitPairingCode, String.format(
+                "{\"DEVICE_ID\": \"%d\",\"CHALLENGE_TYPE\": 1,\"RESPONSE_VALUE\": \"%s\",\"PAIRING_REQ_TOKEN\": %d}",
+                deviceId, pairingCode, pairingToken)), PairingComplete.class);
+    }
+
+    /**
+     * Sends a GET request to the Vizio TV
+     *
+     * @param url The url used to retrieve status information from the Vizio TV
+     * @return The response content of the http request
+     * @throws VizioException
+     *
+     */
+    private String getCommand(String url) throws VizioException {
+        try {
+            final Request request = httpClient.newRequest(url).method(HttpMethod.GET);
+            request.header(AUTH_HEADER, authToken);
+            request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
+
+            final ContentResponse response = request.send();
+
+            logger.trace("GET url: {}, response: {}", url, response.getContentAsString());
+            return response.getContentAsString();
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            throw new VizioException("Error executing vizio GET command, URL: " + url + " " + e.getMessage());
+        }
+    }
+
+    /**
+     * Sends a PUT request to the Vizio TV
+     *
+     * @param url The url used to send a command to the Vizio TV
+     * @param commandJSON The JSON data needed to execute the command
+     * @return The response content of the http request
+     * @throws VizioException
+     *
+     */
+    private String putCommand(String url, String commandJSON) throws VizioException {
+        try {
+            final Request request = httpClient.newRequest(url).method(HttpMethod.PUT);
+            if (!url.contains("pairing")) {
+                request.header(AUTH_HEADER, authToken);
+            }
+            request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
+
+            final ContentResponse response = request.send();
+
+            logger.trace("PUT url: {}, response: {}", url, response.getContentAsString());
+            return response.getContentAsString();
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            throw new VizioException("Error executing vizio PUT command, URL: " + url + e.getMessage());
+        }
+    }
+
+    /**
+     * Wrapper for the Gson fromJson() method that encapsulates exception handling
+     *
+     * @param json The JSON string to be deserialized
+     * @param classOfT The type of class to be returned
+     * @return the deserialized object
+     * @throws VizioException
+     *
+     */
+    private <T> T fromJson(String json, Class<T> classOfT) throws VizioException {
+        Object obj = null;
+        try {
+            obj = gson.fromJson(json, classOfT);
+        } catch (JsonSyntaxException e) {
+            throw new VizioException("Error Parsing JSON string: " + json + ", Exception: " + e.getMessage());
+        }
+        if (obj != null) {
+            return classOfT.cast(obj);
+        } else {
+            throw new VizioException("Error creating " + classOfT.getSimpleName() + " object for JSON string: " + json);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioTlsTrustManagerProvider.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioTlsTrustManagerProvider.java
new file mode 100644 (file)
index 0000000..67f3561
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.communication;
+
+import java.net.MalformedURLException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.PEMTrustManager;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
+import org.openhab.core.io.net.http.TrustAllTrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides a {@link PEMTrustManager} to allow secure connections to a Vizio TV that uses self signed
+ * certificates.
+ *
+ * @author Christoph Weitkamp - Initial Contribution
+ * @author Michael Lobstein - Adapted for Vizio binding
+ */
+@NonNullByDefault
+public class VizioTlsTrustManagerProvider implements TlsTrustManagerProvider {
+    private final String hostname;
+
+    private final Logger logger = LoggerFactory.getLogger(VizioTlsTrustManagerProvider.class);
+
+    public VizioTlsTrustManagerProvider(String hostname) {
+        this.hostname = hostname;
+    }
+
+    @Override
+    public String getHostName() {
+        return hostname;
+    }
+
+    @Override
+    public X509ExtendedTrustManager getTrustManager() {
+        try {
+            logger.trace("Use self-signed certificate downloaded from Vizio TV.");
+            return PEMTrustManager.getInstanceFromServer("https://" + getHostName());
+        } catch (CertificateException | MalformedURLException e) {
+            logger.debug("An unexpected exception occurred - returning a TrustAllTrustManager: {}", e.getMessage(), e);
+        }
+        return TrustAllTrustManager.getInstance();
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/console/VizioCommandExtension.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/console/VizioCommandExtension.java
new file mode 100644 (file)
index 0000000..e2ae860
--- /dev/null
@@ -0,0 +1,183 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.console;
+
+import static org.openhab.binding.vizio.internal.VizioBindingConstants.*;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Random;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.vizio.internal.VizioException;
+import org.openhab.binding.vizio.internal.communication.VizioCommunicator;
+import org.openhab.binding.vizio.internal.dto.pairing.PairingComplete;
+import org.openhab.binding.vizio.internal.handler.VizioHandler;
+import org.openhab.core.io.console.Console;
+import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
+import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingRegistry;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link VizioCommandExtension} is responsible for handling console commands
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+
+@NonNullByDefault
+@Component(service = ConsoleCommandExtension.class)
+public class VizioCommandExtension extends AbstractConsoleCommandExtension {
+    private static final String START_PAIRING = "start_pairing";
+    private static final String SUBMIT_CODE = "submit_code";
+
+    private final ThingRegistry thingRegistry;
+    private final HttpClient httpClient;
+
+    @Activate
+    public VizioCommandExtension(final @Reference ThingRegistry thingRegistry,
+            final @Reference HttpClientFactory httpClientFactory) {
+        super("vizio", "Interact with the Vizio binding to get an authentication token from the TV.");
+        this.thingRegistry = thingRegistry;
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+    }
+
+    @Override
+    public void execute(String[] args, Console console) {
+        if (args.length == 3) {
+            Thing thing = null;
+            try {
+                ThingUID thingUID = new ThingUID(args[0]);
+                thing = thingRegistry.get(thingUID);
+            } catch (IllegalArgumentException e) {
+                thing = null;
+            }
+            ThingHandler thingHandler = null;
+            VizioHandler handler = null;
+            if (thing != null) {
+                thingHandler = thing.getHandler();
+                if (thingHandler instanceof VizioHandler) {
+                    handler = (VizioHandler) thingHandler;
+                }
+            }
+            if (thing == null) {
+                console.println("Bad thing id '" + args[0] + "'");
+                printUsage(console);
+            } else if (thingHandler == null) {
+                console.println("No handler initialized for the thing id '" + args[0] + "'");
+                printUsage(console);
+            } else if (handler == null) {
+                console.println("'" + args[0] + "' is not a Vizio thing id");
+                printUsage(console);
+            } else {
+                String host = (String) thing.getConfiguration().get(PROPERTY_HOST_NAME);
+                BigDecimal port = (BigDecimal) thing.getConfiguration().get(PROPERTY_PORT);
+
+                if (host == null || host.isEmpty() || port.signum() < 1) {
+                    console.println(
+                            "Error! Host Name and Port must be specified in thing configuration before paring.");
+                    return;
+                } else if (host.contains(":")) {
+                    // format for ipv6
+                    host = "[" + host + "]";
+                }
+
+                VizioCommunicator communicator = new VizioCommunicator(httpClient, host, port.intValue(), EMPTY);
+
+                switch (args[1]) {
+                    case START_PAIRING:
+                        try {
+                            Random rng = new Random();
+
+                            int pairingDeviceId = rng.nextInt(100000);
+                            int pairingToken = communicator.starPairing(args[2], pairingDeviceId).getItem()
+                                    .getPairingReqToken();
+                            if (pairingToken != -1) {
+                                handler.setPairingDeviceId(pairingDeviceId);
+                                handler.setPairingToken(pairingToken);
+
+                                console.println("Pairing has been started!");
+                                console.println(
+                                        "Please note the 4 digit code displayed on the TV and substitute it into the following console command:");
+                                console.println(
+                                        "openhab:vizio " + handler.getThing().getUID() + " " + SUBMIT_CODE + " <NNNN>");
+                            } else {
+                                console.println("Unable to obtain pairing token!");
+                            }
+                        } catch (VizioException e) {
+                            console.println("Error! Unable to start pairing process.");
+                            console.println("Exception was: " + e.getMessage());
+                        }
+                        break;
+                    case SUBMIT_CODE:
+                        try {
+                            int pairingDeviceId = handler.getPairingDeviceId();
+                            int pairingToken = handler.getPairingToken();
+
+                            if (pairingDeviceId < 0 || pairingToken < 0) {
+                                console.println("Error! '" + START_PAIRING + "' command must be completed first.");
+                                console.println(
+                                        "Please issue the following command and substitute the desired device name.");
+                                console.println("openhab:vizio " + handler.getThing().getUID() + " " + START_PAIRING
+                                        + " <deviceName>");
+                                break;
+                            }
+
+                            Integer.valueOf(args[2]);
+                            PairingComplete authTokenResp = communicator.submitPairingCode(pairingDeviceId, args[2],
+                                    pairingToken);
+                            if (authTokenResp.getItem().getAuthToken() != EMPTY) {
+                                console.println("Pairing complete!");
+                                console.println("The auth token: " + authTokenResp.getItem().getAuthToken()
+                                        + " was received and will be added to the thing configuration.");
+                                console.println(
+                                        "If the thing is provisioned via a file, the token must be manually added to the thing configuration.");
+
+                                handler.setPairingDeviceId(-1);
+                                handler.setPairingToken(-1);
+                                handler.saveAuthToken(authTokenResp.getItem().getAuthToken());
+                            } else {
+                                console.println("Unable to obtain auth token!");
+                            }
+                        } catch (NumberFormatException nfe) {
+                            console.println(
+                                    "Error! Pairing code must be numeric. Check console command and try again.");
+                        } catch (VizioException e) {
+                            console.println("Error! Unable to complete pairing process.");
+                            console.println("Exception was: " + e.getMessage());
+                        }
+                        break;
+                    default:
+                        printUsage(console);
+                        break;
+                }
+            }
+        } else {
+            printUsage(console);
+        }
+    }
+
+    @Override
+    public List<String> getUsages() {
+        return List.of(new String[] {
+                buildCommandUsage("<thingUID> " + START_PAIRING + " <deviceName>", "start pairing process"),
+                buildCommandUsage("<thingUID> " + SUBMIT_CODE + " <pairingCode>", "submit pairing code") });
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/discovery/VizioDiscoveryParticipant.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/discovery/VizioDiscoveryParticipant.java
new file mode 100644 (file)
index 0000000..f45f8d4
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.discovery;
+
+import static org.openhab.binding.vizio.internal.VizioBindingConstants.*;
+
+import java.net.InetAddress;
+import java.util.Set;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link VizioDiscoveryParticipant} is responsible processing the
+ * results of searches for mDNS services of type _viziocast._tcp.local.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "discovery.vizio")
+public class VizioDiscoveryParticipant implements MDNSDiscoveryParticipant {
+    private final Logger logger = LoggerFactory.getLogger(VizioDiscoveryParticipant.class);
+
+    @Override
+    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+        return SUPPORTED_THING_TYPES_UIDS;
+    }
+
+    @Override
+    public String getServiceType() {
+        return "_viziocast._tcp.local.";
+    }
+
+    @Override
+    public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+        DiscoveryResult result = null;
+
+        ThingUID thingUid = getThingUID(service);
+        if (thingUid != null) {
+            InetAddress ip = getIpAddress(service);
+            if (ip == null) {
+                return null;
+            }
+            String inetAddress = ip.toString().substring(1); // trim leading slash
+            String label = service.getName();
+            int port = service.getPort();
+
+            result = DiscoveryResultBuilder.create(thingUid).withLabel(label).withRepresentationProperty(PROPERTY_UUID)
+                    .withProperty(PROPERTY_UUID, thingUid.getId())
+                    .withProperty(Thing.PROPERTY_MODEL_ID, service.getPropertyString("mdl"))
+                    .withProperty(PROPERTY_HOST_NAME, inetAddress).withProperty(PROPERTY_PORT, port).build();
+            logger.debug("Created {} for Vizio TV at {}, name: '{}'", result, inetAddress, label);
+        }
+        return result;
+    }
+
+    /**
+     * @see org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant#getThingUID(javax.jmdns.ServiceInfo)
+     */
+    @Override
+    public @Nullable ThingUID getThingUID(ServiceInfo service) {
+        if (service.getType() != null && service.getType().equals(getServiceType())) {
+            String uidName = getUIDName(service);
+            return uidName != null ? new ThingUID(THING_TYPE_VIZIO_TV, uidName) : null;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the UID name from the mdns record txt info (mac address), fall back with IP address
+     *
+     * @param service the mdns service
+     * @return the UID name
+     */
+    private @Nullable String getUIDName(ServiceInfo service) {
+        String uid = service.getPropertyString("eth");
+
+        if (uid == null || uid.endsWith("000") || uid.length() < 12) {
+            uid = service.getPropertyString("wifi");
+        }
+
+        if (uid == null || uid.endsWith("000") || uid.length() < 12) {
+            InetAddress ip = getIpAddress(service);
+            if (ip == null) {
+                return null;
+            } else {
+                uid = ip.toString();
+            }
+        }
+        return uid.replaceAll("[^A-Za-z0-9_]", "_");
+    }
+
+    /**
+     * {@link InetAddress} gets the IP address of the device in v4 or v6 format.
+     *
+     * @param ServiceInfo service
+     * @return InetAddress the IP address
+     *
+     */
+    private @Nullable InetAddress getIpAddress(ServiceInfo service) {
+        InetAddress address = null;
+        for (InetAddress addr : service.getInet4Addresses()) {
+            return addr;
+        }
+        // Fall back for Inet6addresses
+        for (InetAddress addr : service.getInet6Addresses()) {
+            return addr;
+        }
+        return address;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Item.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Item.java
new file mode 100644 (file)
index 0000000..f7bb76f
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Item} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class Item {
+    @SerializedName("HASHVAL")
+    private Long hashval;
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("NAME")
+    private String name = "";
+    @SerializedName("TYPE")
+    private String type;
+    @SerializedName("ENABLED")
+    private String enabled;
+    @SerializedName("READONLY")
+    private String readonly;
+    @SerializedName("VALUE")
+    private Value value = new Value();
+
+    public Long getHashval() {
+        return hashval;
+    }
+
+    public void setHashval(Long hashval) {
+        this.hashval = hashval;
+    }
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(String enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getReadonly() {
+        return readonly;
+    }
+
+    public void setReadonly(String readonly) {
+        this.readonly = readonly;
+    }
+
+    public Value getValue() {
+        return value;
+    }
+
+    public void setValue(Value value) {
+        this.value = value;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Parameters.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Parameters.java
new file mode 100644 (file)
index 0000000..dfcdcc6
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Parameters} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class Parameters {
+    @SerializedName("FLAT")
+    private String flat;
+    @SerializedName("HELPTEXT")
+    private String helptext;
+    @SerializedName("HASHONLY")
+    private String hashonly;
+
+    public String getFlat() {
+        return flat;
+    }
+
+    public void setFlat(String flat) {
+        this.flat = flat;
+    }
+
+    public String getHelptext() {
+        return helptext;
+    }
+
+    public void setHelptext(String helptext) {
+        this.helptext = helptext;
+    }
+
+    public String getHashonly() {
+        return hashonly;
+    }
+
+    public void setHashonly(String hashonly) {
+        this.hashonly = hashonly;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/PutResponse.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/PutResponse.java
new file mode 100644 (file)
index 0000000..76b6b74
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link PutResponse} class maps the JSON data response from several Vizio TV endpoints
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class PutResponse {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("URI")
+    private String uri;
+    @SerializedName("TIME")
+    private String time;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
+    public String getTime() {
+        return time;
+    }
+
+    public void setTime(String time) {
+        this.time = time;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Status.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Status.java
new file mode 100644 (file)
index 0000000..b0befb8
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Status} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class Status {
+    @SerializedName("RESULT")
+    private String result;
+    @SerializedName("DETAIL")
+    private String detail;
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public String getDetail() {
+        return detail;
+    }
+
+    public void setDetail(String detail) {
+        this.detail = detail;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Value.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Value.java
new file mode 100644 (file)
index 0000000..1e65781
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Value} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class Value {
+    @SerializedName("NAME")
+    private String name = "";
+    @SerializedName("METADATA")
+    private String metadata;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(String metadata) {
+        this.metadata = metadata;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/CurrentApp.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/CurrentApp.java
new file mode 100644 (file)
index 0000000..81b5c9c
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.app;
+
+import org.openhab.binding.vizio.internal.dto.Status;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link CurrentApp} class maps the JSON data response from the Vizio TV endpoint:
+ * '/app/current'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class CurrentApp {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("ITEM")
+    private ItemApp item = new ItemApp();
+    @SerializedName("URI")
+    private String uri;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public ItemApp getItem() {
+        return item;
+    }
+
+    public void setItem(ItemApp item) {
+        this.item = item;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemApp.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemApp.java
new file mode 100644 (file)
index 0000000..2728a0f
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.app;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemApp} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemApp {
+    @SerializedName("TYPE")
+    private String type;
+
+    @SerializedName("VALUE")
+    private ItemAppValue value = new ItemAppValue();
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public ItemAppValue getValue() {
+        return value;
+    }
+
+    public void setValue(ItemAppValue value) {
+        this.value = value;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemAppValue.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemAppValue.java
new file mode 100644 (file)
index 0000000..d5fdda9
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.app;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemAppValue} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemAppValue {
+    @SerializedName("MESSAGE")
+    private String message;
+    @SerializedName("NAME_SPACE")
+    private Integer nameSpace = -1;
+    @SerializedName("APP_ID")
+    private String appId = "";
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Integer getNameSpace() {
+        return nameSpace;
+    }
+
+    public void setNameSpace(Integer nameSpace) {
+        this.nameSpace = nameSpace;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApp.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApp.java
new file mode 100644 (file)
index 0000000..04a7711
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.applist;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link VizioApp} class contains the name and config data for an app that runs on a Vizio TV
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class VizioApp {
+    @SerializedName("name")
+    private String name = "";
+    @SerializedName("config")
+    private VizioAppConfig config = new VizioAppConfig();
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public VizioAppConfig getConfig() {
+        return config;
+    }
+
+    public void setConfig(VizioAppConfig config) {
+        this.config = config;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioAppConfig.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioAppConfig.java
new file mode 100644 (file)
index 0000000..b936219
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.applist;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link VizioAppConfig} class maps the JSON data needed to launch an app on a Vizio TV
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class VizioAppConfig {
+    @SerializedName("NAME_SPACE")
+    private Integer nameSpace;
+    @SerializedName("APP_ID")
+    private String appId;
+    @SerializedName("MESSAGE")
+    private String message;
+
+    public Integer getNameSpace() {
+        return nameSpace;
+    }
+
+    public void setNameSpace(Integer nameSpace) {
+        this.nameSpace = nameSpace;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApps.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApps.java
new file mode 100644 (file)
index 0000000..8ed45eb
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.applist;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link VizioApps} class contains a list of VizioApp objects
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class VizioApps {
+    @SerializedName("Apps")
+    private List<VizioApp> apps = new ArrayList<VizioApp>();
+
+    public List<VizioApp> getApps() {
+        return apps;
+    }
+
+    public void setApps(List<VizioApp> apps) {
+        this.apps = apps;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/Audio.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/Audio.java
new file mode 100644 (file)
index 0000000..d42ee16
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.audio;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openhab.binding.vizio.internal.dto.Parameters;
+import org.openhab.binding.vizio.internal.dto.Status;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Audio} class maps the JSON data response from the Vizio TV endpoint:
+ * '/menu_native/dynamic/tv_settings/audio'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class Audio {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("HASHLIST")
+    private List<Long> hashlist = new ArrayList<Long>();
+    @SerializedName("GROUP")
+    private String group;
+    @SerializedName("NAME")
+    private String name;
+    @SerializedName("PARAMETERS")
+    private Parameters parameters;
+    @SerializedName("ITEMS")
+    private List<ItemAudio> items = new ArrayList<ItemAudio>();
+    @SerializedName("URI")
+    private String uri;
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("TYPE")
+    private String type;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public List<Long> getHashlist() {
+        return hashlist;
+    }
+
+    public void setHashlist(List<Long> hashlist) {
+        this.hashlist = hashlist;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Parameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public List<ItemAudio> getItems() {
+        return items;
+    }
+
+    public void setItems(List<ItemAudio> items) {
+        this.items = items;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/ItemAudio.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/ItemAudio.java
new file mode 100644 (file)
index 0000000..cc0d094
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.audio;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemAudio} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemAudio {
+    @SerializedName("HASHVAL")
+    private Long hashval = 0L;
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("NAME")
+    private String name;
+    @SerializedName("TYPE")
+    private String type;
+    @SerializedName("ENABLED")
+    private String enabled;
+    @SerializedName("READONLY")
+    private String readonly;
+    @SerializedName("VALUE")
+    private String value = "";
+
+    public Long getHashval() {
+        return hashval;
+    }
+
+    public void setHashval(Long hashval) {
+        this.hashval = hashval;
+    }
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(String enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getReadonly() {
+        return readonly;
+    }
+
+    public void setReadonly(String readonly) {
+        this.readonly = readonly;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/CurrentInput.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/CurrentInput.java
new file mode 100644 (file)
index 0000000..5ca0aa9
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openhab.binding.vizio.internal.dto.Parameters;
+import org.openhab.binding.vizio.internal.dto.Status;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link CurrentInput} class maps the JSON data response from the Vizio TV endpoint:
+ * '/menu_native/dynamic/tv_settings/devices/current_input'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class CurrentInput {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("ITEMS")
+    private List<ItemInput> items = new ArrayList<ItemInput>();
+    @SerializedName("HASHLIST")
+    private List<Long> hashlist = new ArrayList<Long>();
+    @SerializedName("URI")
+    private String uri;
+    @SerializedName("PARAMETERS")
+    private Parameters parameters;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public List<ItemInput> getItems() {
+        return items;
+    }
+
+    public void setItems(List<ItemInput> items) {
+        this.items = items;
+    }
+
+    public List<Long> getHashlist() {
+        return hashlist;
+    }
+
+    public void setHashlist(List<Long> hashlist) {
+        this.hashlist = hashlist;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Parameters parameters) {
+        this.parameters = parameters;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/ItemInput.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/ItemInput.java
new file mode 100644 (file)
index 0000000..d4e0ed4
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.input;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemInput} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemInput {
+    @SerializedName("HASHVAL")
+    private Long hashval = 0L;
+    @SerializedName("NAME")
+    private String name;
+    @SerializedName("ENABLED")
+    private String enabled;
+    @SerializedName("VALUE")
+    private String value = "";
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("HIDDEN")
+    private String hidden;
+    @SerializedName("TYPE")
+    private String type;
+
+    public Long getHashval() {
+        return hashval;
+    }
+
+    public void setHashval(Long hashval) {
+        this.hashval = hashval;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(String enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getHidden() {
+        return hidden;
+    }
+
+    public void setHidden(String hidden) {
+        this.hidden = hidden;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/inputlist/InputList.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/inputlist/InputList.java
new file mode 100644 (file)
index 0000000..5b2014e
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.inputlist;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openhab.binding.vizio.internal.dto.Item;
+import org.openhab.binding.vizio.internal.dto.Parameters;
+import org.openhab.binding.vizio.internal.dto.Status;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link InputList} class maps the JSON data response from the Vizio TV endpoint:
+ * '/menu_native/dynamic/tv_settings/devices/name_input'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class InputList {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("HASHLIST")
+    private List<Long> hashlist = new ArrayList<Long>();
+    @SerializedName("GROUP")
+    private String group;
+    @SerializedName("NAME")
+    private String name;
+    @SerializedName("PARAMETERS")
+    private Parameters parameters;
+    @SerializedName("ITEMS")
+    private List<Item> items = new ArrayList<Item>();
+    @SerializedName("URI")
+    private String uri;
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("TYPE")
+    private String type;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public List<Long> getHashlist() {
+        return hashlist;
+    }
+
+    public void setHashlist(List<Long> hashlist) {
+        this.hashlist = hashlist;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Parameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public List<Item> getItems() {
+        return items;
+    }
+
+    public void setItems(List<Item> items) {
+        this.items = items;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemAuthToken.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemAuthToken.java
new file mode 100644 (file)
index 0000000..1216394
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.pairing;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemAuthToken} class contains data from the Vizio TV in response to completing the pairing process
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemAuthToken {
+    @SerializedName("AUTH_TOKEN")
+    private String authToken = "";
+
+    public String getAuthToken() {
+        return authToken;
+    }
+
+    public void setAuthToken(String authToken) {
+        this.authToken = authToken;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemPairing.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemPairing.java
new file mode 100644 (file)
index 0000000..0b19821
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.pairing;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemPairing} class contains data from the Vizio TV in response to starting the pairing process
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemPairing {
+    @SerializedName("PAIRING_REQ_TOKEN")
+    private Integer pairingReqToken = -1;
+    @SerializedName("CHALLENGE_TYPE")
+    private Integer challengeType = -1;
+
+    public Integer getPairingReqToken() {
+        return pairingReqToken;
+    }
+
+    public void setPairingReqToken(Integer pairingReqToken) {
+        this.pairingReqToken = pairingReqToken;
+    }
+
+    public Integer getChallengeType() {
+        return challengeType;
+    }
+
+    public void setChallengeType(Integer challengeType) {
+        this.challengeType = challengeType;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingComplete.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingComplete.java
new file mode 100644 (file)
index 0000000..eb1ec05
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.pairing;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link PairingComplete} class maps the JSON data response from the Vizio TV endpoint:
+ * '/pairing/pair'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class PairingComplete {
+    @SerializedName("ITEM")
+    private ItemAuthToken item = new ItemAuthToken();
+
+    public ItemAuthToken getItem() {
+        return item;
+    }
+
+    public void setItem(ItemAuthToken item) {
+        this.item = item;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingStart.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingStart.java
new file mode 100644 (file)
index 0000000..a59fc66
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.pairing;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link PairingStart} class maps the JSON data response from the Vizio TV endpoint:
+ * '/pairing/start'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class PairingStart {
+    @SerializedName("ITEM")
+    private ItemPairing item = new ItemPairing();
+
+    public ItemPairing getItem() {
+        return item;
+    }
+
+    public void setItem(ItemPairing item) {
+        this.item = item;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/ItemPower.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/ItemPower.java
new file mode 100644 (file)
index 0000000..2c5054a
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.power;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ItemPower} class contains data from the Vizio TV JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class ItemPower {
+    @SerializedName("CNAME")
+    private String cname;
+    @SerializedName("TYPE")
+    private String type;
+    @SerializedName("NAME")
+    private String name;
+    @SerializedName("VALUE")
+    private int value;
+
+    public String getCname() {
+        return cname;
+    }
+
+    public void setCname(String cname) {
+        this.cname = cname;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public void setValue(int value) {
+        this.value = value;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/PowerMode.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/PowerMode.java
new file mode 100644 (file)
index 0000000..6e5fae4
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.dto.power;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openhab.binding.vizio.internal.dto.Status;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link PowerMode} class maps the JSON data response from the Vizio TV endpoint:
+ * '/state/device/power_mode'
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class PowerMode {
+    @SerializedName("STATUS")
+    private Status status;
+    @SerializedName("ITEMS")
+    private List<ItemPower> items = new ArrayList<ItemPower>();
+    @SerializedName("URI")
+    private String uri;
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public List<ItemPower> getItems() {
+        return items;
+    }
+
+    public void setItems(List<ItemPower> items) {
+        this.items = items;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/enums/KeyCommand.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/enums/KeyCommand.java
new file mode 100644 (file)
index 0000000..f52e9ce
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link KeyCommand} class provides enum values for remote control button press commands.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public enum KeyCommand {
+    SEEKFWD(2, 0),
+    SEEKBACK(2, 1),
+    PAUSE(2, 2),
+    PLAY(2, 3),
+    DOWN(3, 0),
+    LEFT(3, 1),
+    OK(3, 2),
+    LEFT2(3, 4),
+    RIGHT(3, 7),
+    UP(3, 8),
+    BACK(4, 0),
+    SMARTCAST(4, 3),
+    CCTOGGLE(4, 4),
+    INFO(4, 6),
+    MENU(4, 8),
+    HOME(4, 15),
+    VOLUMEDOWN(5, 0),
+    VOLUMEUP(5, 1),
+    MUTEOFF(5, 2),
+    MUTEON(5, 3),
+    MUTETOGGLE(5, 4),
+    PICTUREMODE(6, 0),
+    WIDEMODE(6, 1),
+    WIDETOGGLE(6, 2),
+    INPUTTOGGLE(7, 1),
+    CHANNELDOWN(8, 0),
+    CHANNELUP(8, 1),
+    PREVIOUSCH(8, 2),
+    EXIT(9, 0),
+    POWEROFF(11, 0),
+    POWERON(11, 1),
+    POWERTOGGLE(11, 2);
+
+    private static final String KEY_COMMAND_STR = "{\"KEYLIST\": [{\"CODESET\": %d,\"CODE\": %d,\"ACTION\":\"KEYPRESS\"}]}";
+
+    private final int codeSet;
+    private final int code;
+
+    KeyCommand(int codeSet, int code) {
+        this.codeSet = codeSet;
+        this.code = code;
+    }
+
+    public String getJson() {
+        return String.format(KEY_COMMAND_STR, codeSet, code);
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/handler/VizioHandler.java b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/handler/VizioHandler.java
new file mode 100644 (file)
index 0000000..b3e7131
--- /dev/null
@@ -0,0 +1,584 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.vizio.internal.handler;
+
+import static org.openhab.binding.vizio.internal.VizioBindingConstants.*;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.vizio.internal.VizioConfiguration;
+import org.openhab.binding.vizio.internal.VizioException;
+import org.openhab.binding.vizio.internal.VizioStateDescriptionOptionProvider;
+import org.openhab.binding.vizio.internal.communication.VizioCommunicator;
+import org.openhab.binding.vizio.internal.communication.VizioTlsTrustManagerProvider;
+import org.openhab.binding.vizio.internal.dto.app.CurrentApp;
+import org.openhab.binding.vizio.internal.dto.applist.VizioApp;
+import org.openhab.binding.vizio.internal.dto.applist.VizioApps;
+import org.openhab.binding.vizio.internal.dto.audio.Audio;
+import org.openhab.binding.vizio.internal.dto.audio.ItemAudio;
+import org.openhab.binding.vizio.internal.dto.input.CurrentInput;
+import org.openhab.binding.vizio.internal.dto.inputlist.InputList;
+import org.openhab.binding.vizio.internal.dto.power.PowerMode;
+import org.openhab.binding.vizio.internal.enums.KeyCommand;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
+import org.openhab.core.library.types.NextPreviousType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.PlayPauseType;
+import org.openhab.core.library.types.RewindFastforwardType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.StateOption;
+import org.openhab.core.types.UnDefType;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link VizioHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class VizioHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(VizioHandler.class);
+    private final HttpClient httpClient;
+    private final VizioStateDescriptionOptionProvider stateDescriptionProvider;
+    private final String dbAppsJson;
+
+    private @Nullable ServiceRegistration<?> serviceRegistration;
+    private @Nullable ScheduledFuture<?> refreshJob;
+    private @Nullable ScheduledFuture<?> metadataRefreshJob;
+
+    private VizioCommunicator communicator;
+    private List<VizioApp> userConfigApps = new ArrayList<VizioApp>();
+    private Object sequenceLock = new Object();
+
+    private int pairingDeviceId = -1;
+    private int pairingToken = -1;
+    private Long currentInputHash = 0L;
+    private Long currentVolumeHash = 0L;
+    private String currentApp = EMPTY;
+    private String currentInput = EMPTY;
+    private boolean currentMute = false;
+    private int currentVolume = -1;
+    private boolean powerOn = false;
+    private boolean debounce = true;
+
+    public VizioHandler(Thing thing, HttpClient httpClient,
+            VizioStateDescriptionOptionProvider stateDescriptionProvider, String vizioAppsJson) {
+        super(thing);
+        this.httpClient = httpClient;
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.dbAppsJson = vizioAppsJson;
+        this.communicator = new VizioCommunicator(httpClient, EMPTY, -1, EMPTY);
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("Initializing Vizio handler");
+        final Gson gson = new Gson();
+        VizioConfiguration config = getConfigAs(VizioConfiguration.class);
+
+        @Nullable
+        String host = config.hostName;
+        final @Nullable String authToken = config.authToken;
+        @Nullable
+        String appListJson = config.appListJson;
+
+        if (host == null || host.isEmpty()) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.configuration-error-hostname");
+            return;
+        } else if (host.contains(":")) {
+            // format for ipv6
+            host = "[" + host + "]";
+        }
+
+        this.communicator = new VizioCommunicator(httpClient, host, config.port, authToken != null ? authToken : EMPTY);
+
+        // register trustmanager service to allow httpClient to accept self signed cert from the Vizio TV
+        VizioTlsTrustManagerProvider tlsTrustManagerProvider = new VizioTlsTrustManagerProvider(
+                host + ":" + config.port);
+        serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext()
+                .registerService(TlsTrustManagerProvider.class.getName(), tlsTrustManagerProvider, null);
+
+        if (authToken == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                    "@text/offline.configuration-error-authtoken");
+            return;
+        }
+
+        // if app list is not supplied in thing configuration, populate it from the json db
+        if (appListJson == null) {
+            appListJson = dbAppsJson;
+
+            // Update thing configuration (persistent) - store app list from db into thing so the user can update it
+            Configuration configuration = this.getConfig();
+            configuration.put(PROPERTY_APP_LIST_JSON, appListJson);
+            this.updateConfiguration(configuration);
+        }
+
+        try {
+            VizioApps appsFromJson = gson.fromJson(appListJson, VizioApps.class);
+            if (appsFromJson != null && !appsFromJson.getApps().isEmpty()) {
+                userConfigApps = appsFromJson.getApps();
+
+                List<StateOption> appListOptions = new ArrayList<>();
+                userConfigApps.forEach(app -> {
+                    appListOptions.add(new StateOption(app.getName(), app.getName()));
+                });
+
+                stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), ACTIVE_APP),
+                        appListOptions);
+            }
+        } catch (JsonSyntaxException e) {
+            logger.debug("Invalid App List Configuration in thing configuration. Exception: {}", e.getMessage(), e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.configuration-error-applist");
+            return;
+        }
+
+        updateStatus(ThingStatus.UNKNOWN);
+
+        startVizioStateRefresh();
+        startPeriodicRefresh();
+    }
+
+    /**
+     * Start the job that queries the Vizio TV every 10 seconds to get its current status
+     */
+    private void startVizioStateRefresh() {
+        ScheduledFuture<?> refreshJob = this.refreshJob;
+        if (refreshJob == null || refreshJob.isCancelled()) {
+            this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshVizioState, 5, 10, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Get current status from the Vizio TV and update the channels
+     */
+    private void refreshVizioState() {
+        synchronized (sequenceLock) {
+            try {
+                PowerMode polledPowerMode = communicator.getPowerMode();
+
+                if (debounce && !polledPowerMode.getItems().isEmpty()) {
+                    int powerMode = polledPowerMode.getItems().get(0).getValue();
+                    if (powerMode == 1) {
+                        powerOn = true;
+                        updateState(POWER, OnOffType.ON);
+                    } else if (powerMode == 0) {
+                        powerOn = false;
+                        updateState(POWER, OnOffType.OFF);
+                    } else {
+                        logger.debug("Unknown power mode {}, for response object: {}", powerMode, polledPowerMode);
+                    }
+                }
+                updateStatus(ThingStatus.ONLINE);
+            } catch (VizioException e) {
+                logger.debug("Unable to retrieve Vizio TV power mode info. Exception: {}", e.getMessage(), e);
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "@text/offline.communication-error-get-power");
+            }
+
+            if (powerOn && (isLinked(VOLUME) || isLinked(MUTE))) {
+                try {
+                    Audio audioSettings = communicator.getCurrentAudioSettings();
+
+                    Optional<ItemAudio> volumeItem = audioSettings.getItems().stream()
+                            .filter(i -> VOLUME.equals(i.getCname())).findFirst();
+                    if (debounce && volumeItem.isPresent()) {
+                        currentVolumeHash = volumeItem.get().getHashval();
+
+                        try {
+                            int polledVolume = Integer.parseInt(volumeItem.get().getValue());
+                            if (polledVolume != currentVolume) {
+                                currentVolume = polledVolume;
+                                updateState(VOLUME, new PercentType(BigDecimal.valueOf(currentVolume)));
+                            }
+                        } catch (NumberFormatException e) {
+                            logger.debug("Unable to parse volume value {} as int", volumeItem.get().getValue());
+                        }
+                    }
+
+                    Optional<ItemAudio> muteItem = audioSettings.getItems().stream()
+                            .filter(i -> MUTE.equals(i.getCname())).findFirst();
+                    if (debounce && muteItem.isPresent()) {
+                        String polledMute = muteItem.get().getValue().toUpperCase(Locale.ENGLISH);
+
+                        if (ON.equals(polledMute) || OFF.equals(polledMute)) {
+                            if (ON.equals(polledMute) && !currentMute) {
+                                updateState(MUTE, OnOffType.ON);
+                                currentMute = true;
+                            } else if (OFF.equals(polledMute) && currentMute) {
+                                updateState(MUTE, OnOffType.OFF);
+                                currentMute = false;
+                            }
+                        } else {
+                            logger.debug("Unknown mute mode {}, for response object: {}", polledMute, audioSettings);
+                        }
+                    }
+                } catch (VizioException e) {
+                    logger.debug("Unable to retrieve Vizio TV current audio settings. Exception: {}", e.getMessage(),
+                            e);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                            "@text/offline.communication-error-get-audio");
+                }
+            }
+
+            if (powerOn && isLinked(SOURCE)) {
+                try {
+                    CurrentInput polledInputState = communicator.getCurrentInput();
+
+                    if (debounce && !polledInputState.getItems().isEmpty()
+                            && !currentInput.equals(polledInputState.getItems().get(0).getValue())) {
+                        currentInput = polledInputState.getItems().get(0).getValue();
+                        currentInputHash = polledInputState.getItems().get(0).getHashval();
+                        updateState(SOURCE, new StringType(currentInput));
+                    }
+                } catch (VizioException e) {
+                    logger.debug("Unable to retrieve Vizio TV current input. Exception: {}", e.getMessage(), e);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                            "@text/offline.communication-error-get-input");
+                }
+            }
+
+            if (powerOn && isLinked(ACTIVE_APP)) {
+                try {
+                    if (debounce) {
+                        CurrentApp polledApp = communicator.getCurrentApp();
+                        Optional<VizioApp> currentAppData = userConfigApps.stream()
+                                .filter(a -> a.getConfig().getAppId().equals(polledApp.getItem().getValue().getAppId())
+                                        && a.getConfig().getNameSpace()
+                                                .equals(polledApp.getItem().getValue().getNameSpace()))
+                                .findFirst();
+
+                        if (currentAppData.isPresent()) {
+                            if (!currentApp.equals(currentAppData.get().getName())) {
+                                currentApp = currentAppData.get().getName();
+                                updateState(ACTIVE_APP, new StringType(currentApp));
+                            }
+                        } else {
+                            currentApp = EMPTY;
+                            try {
+                                int appId = Integer.parseInt(polledApp.getItem().getValue().getAppId());
+                                updateState(ACTIVE_APP, new StringType(String.format(UNKNOWN_APP_STR, appId,
+                                        polledApp.getItem().getValue().getNameSpace())));
+                            } catch (NumberFormatException nfe) {
+                                // Non-numeric appId received, eg: hdmi1
+                                updateState(ACTIVE_APP, UnDefType.UNDEF);
+                            }
+
+                            logger.debug("Unknown app_id: {}, name_space: {}",
+                                    polledApp.getItem().getValue().getAppId(),
+                                    polledApp.getItem().getValue().getNameSpace());
+                        }
+                    }
+                } catch (VizioException e) {
+                    logger.debug("Unable to retrieve Vizio TV current running app. Exception: {}", e.getMessage(), e);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                            "@text/offline.communication-error-get-app");
+                }
+            }
+        }
+        debounce = true;
+    }
+
+    /**
+     * Start the job to periodically retrieve various metadata from the Vizio TV every 10 minutes
+     */
+    private void startPeriodicRefresh() {
+        ScheduledFuture<?> metadataRefreshJob = this.metadataRefreshJob;
+        if (metadataRefreshJob == null || metadataRefreshJob.isCancelled()) {
+            this.metadataRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshVizioMetadata, 1, 600,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Update source list (hashes) and other metadata from the Vizio TV
+     */
+    private void refreshVizioMetadata() {
+        synchronized (sequenceLock) {
+            try {
+                InputList inputList = communicator.getSourceInputList();
+
+                List<StateOption> sourceListOptions = new ArrayList<>();
+                inputList.getItems().forEach(source -> {
+                    sourceListOptions.add(new StateOption(source.getName(), source.getValue().getName()));
+                });
+
+                stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), SOURCE),
+                        sourceListOptions);
+            } catch (VizioException e) {
+                logger.debug("Unable to retrieve the Vizio TV input list. Exception: {}", e.getMessage(), e);
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> refreshJob = this.refreshJob;
+        if (refreshJob != null) {
+            refreshJob.cancel(true);
+            this.refreshJob = null;
+        }
+
+        ScheduledFuture<?> metadataRefreshJob = this.metadataRefreshJob;
+        if (metadataRefreshJob != null) {
+            metadataRefreshJob.cancel(true);
+            this.metadataRefreshJob = null;
+        }
+
+        ServiceRegistration<?> localServiceRegistration = serviceRegistration;
+        if (localServiceRegistration != null) {
+            // remove trustmanager service
+            localServiceRegistration.unregister();
+            serviceRegistration = null;
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            logger.debug("Unsupported refresh command: {}", command);
+        } else {
+            switch (channelUID.getId()) {
+                case POWER:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            if (command == OnOffType.ON) {
+                                communicator.sendKeyPress(KeyCommand.POWERON.getJson());
+                                powerOn = true;
+                            } else {
+                                communicator.sendKeyPress(KeyCommand.POWEROFF.getJson());
+                                powerOn = false;
+                            }
+                        } catch (VizioException e) {
+                            logger.debug("Unable to send power {} command to the Vizio TV, Exception: {}", command,
+                                    e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-set-power");
+                        }
+                    }
+                    break;
+                case VOLUME:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            int volume = Integer.parseInt(command.toString());
+
+                            // volume changed again before polling has run, get current volume hash from the TV first
+                            if (currentVolumeHash.equals(0L)) {
+                                Audio audioSettings = communicator.getCurrentAudioSettings();
+
+                                Optional<ItemAudio> volumeItem = audioSettings.getItems().stream()
+                                        .filter(i -> VOLUME.equals(i.getCname())).findFirst();
+                                if (volumeItem.isPresent()) {
+                                    currentVolumeHash = volumeItem.get().getHashval();
+                                } else {
+                                    logger.debug("Unable to get current volume hash on the Vizio TV");
+                                }
+                            }
+                            communicator
+                                    .changeVolume(String.format(MODIFY_INT_SETTING_JSON, volume, currentVolumeHash));
+                            currentVolumeHash = 0L;
+                        } catch (VizioException e) {
+                            logger.debug("Unable to set volume on the Vizio TV, command volume: {}, Exception: {}",
+                                    command, e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-set-volume");
+                        } catch (NumberFormatException e) {
+                            logger.debug("Unable to parse command volume value {} as int", command);
+                        }
+                    }
+                    break;
+                case MUTE:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            if (command == OnOffType.ON && !currentMute) {
+                                communicator.sendKeyPress(KeyCommand.MUTETOGGLE.getJson());
+                                currentMute = true;
+                            } else if (command == OnOffType.OFF && currentMute) {
+                                communicator.sendKeyPress(KeyCommand.MUTETOGGLE.getJson());
+                                currentMute = false;
+                            }
+                        } catch (VizioException e) {
+                            logger.debug("Unable to send mute {} command to the Vizio TV, Exception: {}", command,
+                                    e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-set-mute");
+                        }
+                    }
+                    break;
+                case SOURCE:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            // if input changed again before polling has run, get current input hash from the TV
+                            // first
+                            if (currentInputHash.equals(0L)) {
+                                CurrentInput polledInput = communicator.getCurrentInput();
+                                if (!polledInput.getItems().isEmpty()) {
+                                    currentInputHash = polledInput.getItems().get(0).getHashval();
+                                }
+                            }
+                            communicator
+                                    .changeInput(String.format(MODIFY_STRING_SETTING_JSON, command, currentInputHash));
+                            currentInputHash = 0L;
+                        } catch (VizioException e) {
+                            logger.debug("Unable to set current source on the Vizio TV, source: {}, Exception: {}",
+                                    command, e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-set-source");
+                        }
+                    }
+                    break;
+                case ACTIVE_APP:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            Optional<VizioApp> selectedApp = userConfigApps.stream()
+                                    .filter(a -> command.toString().equals(a.getName())).findFirst();
+
+                            if (selectedApp.isPresent()) {
+                                communicator.launchApp(selectedApp.get().getConfig());
+                            } else {
+                                logger.debug("Unknown app name: '{}', check that it exists in App List configuration",
+                                        command);
+                            }
+                        } catch (VizioException e) {
+                            logger.debug("Unable to launch app name: '{}' on the Vizio TV, Exception: {}", command,
+                                    e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-launch-app");
+                        }
+                    }
+                    break;
+                case CONTROL:
+                    debounce = false;
+                    synchronized (sequenceLock) {
+                        try {
+                            handleControlCommand(command);
+                        } catch (VizioException e) {
+                            logger.debug("Unable to send control command: '{}' to the Vizio TV, Exception: {}", command,
+                                    e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-send-cmd");
+                        }
+                    }
+                    break;
+                case BUTTON:
+                    synchronized (sequenceLock) {
+                        try {
+                            KeyCommand keyCommand = KeyCommand.valueOf(command.toString().toUpperCase(Locale.ENGLISH));
+                            communicator.sendKeyPress(keyCommand.getJson());
+                        } catch (IllegalArgumentException | VizioException e) {
+                            logger.debug("Unable to send keypress to the Vizio TV, key: {}, Exception: {}", command,
+                                    e.getMessage());
+                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                    "@text/offline.communication-error-send-key");
+                        }
+                    }
+                    break;
+                default:
+                    logger.warn("Unknown channel: '{}'", channelUID.getId());
+                    break;
+            }
+        }
+    }
+
+    private void handleControlCommand(Command command) throws VizioException {
+        if (command instanceof PlayPauseType) {
+            if (command == PlayPauseType.PLAY) {
+                communicator.sendKeyPress(KeyCommand.PLAY.getJson());
+            } else if (command == PlayPauseType.PAUSE) {
+                communicator.sendKeyPress(KeyCommand.PAUSE.getJson());
+            }
+        } else if (command instanceof NextPreviousType) {
+            if (command == NextPreviousType.NEXT) {
+                communicator.sendKeyPress(KeyCommand.RIGHT.getJson());
+            } else if (command == NextPreviousType.PREVIOUS) {
+                communicator.sendKeyPress(KeyCommand.LEFT.getJson());
+            }
+        } else if (command instanceof RewindFastforwardType) {
+            if (command == RewindFastforwardType.FASTFORWARD) {
+                communicator.sendKeyPress(KeyCommand.SEEKFWD.getJson());
+            } else if (command == RewindFastforwardType.REWIND) {
+                communicator.sendKeyPress(KeyCommand.SEEKBACK.getJson());
+            }
+        } else {
+            logger.warn("Unknown control command: {}", command);
+        }
+    }
+
+    @Override
+    public boolean isLinked(String channelName) {
+        Channel channel = this.thing.getChannel(channelName);
+        if (channel != null) {
+            return isLinked(channel.getUID());
+        } else {
+            return false;
+        }
+    }
+
+    // The remaining methods are used by the console when obtaining the auth token from the TV.
+    public void saveAuthToken(String authToken) {
+        // Store the auth token in the configuration and restart the thing
+        Configuration configuration = this.getConfig();
+        configuration.put(PROPERTY_AUTH_TOKEN, authToken);
+        this.updateConfiguration(configuration);
+        this.thingUpdated(this.getThing());
+    }
+
+    public int getPairingDeviceId() {
+        return pairingDeviceId;
+    }
+
+    public void setPairingDeviceId(int pairingDeviceId) {
+        this.pairingDeviceId = pairingDeviceId;
+    }
+
+    public int getPairingToken() {
+        return pairingToken;
+    }
+
+    public void setPairingToken(int pairingToken) {
+        this.pairingToken = pairingToken;
+    }
+}
diff --git a/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..d0e8c2e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="vizio" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+       <name>Vizio Binding</name>
+       <description>Controls Vizio TVs w/SmartCast API (2016+ Models)</description>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/i18n/vizio.properties b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/i18n/vizio.properties
new file mode 100644 (file)
index 0000000..c26e2a2
--- /dev/null
@@ -0,0 +1,81 @@
+# binding
+
+binding.vizio.name = Vizio Binding
+binding.vizio.description = Controls Vizio TVs w/SmartCast API (2016+ Models)
+
+# thing types
+
+thing-type.vizio.vizio_tv.label = Vizio TV
+thing-type.vizio.vizio_tv.description = A Vizio SmartCast TV
+
+# thing types config
+
+thing-type.config.vizio.vizio_tv.appListJson.label = App List Configuration
+thing-type.config.vizio.vizio_tv.appListJson.description = The JSON configuration string for the list of apps available in the activeApp channel drop down
+thing-type.config.vizio.vizio_tv.authToken.label = Auth Token
+thing-type.config.vizio.vizio_tv.authToken.description = Auth Token that is obtained via the pairing process; See documentation for details
+thing-type.config.vizio.vizio_tv.hostName.label = Host Name/IP Address
+thing-type.config.vizio.vizio_tv.hostName.description = Host Name or IP Address of the Vizio TV
+thing-type.config.vizio.vizio_tv.port.label = Port
+thing-type.config.vizio.vizio_tv.port.description = Port for the Vizio TV
+thing-type.config.vizio.vizio_tv.port.option.7345 = 7345 (Newer Models)
+thing-type.config.vizio.vizio_tv.port.option.9000 = 9000 (Older Models)
+
+# channel types
+
+channel-type.vizio.activeApp.label = Active App
+channel-type.vizio.activeApp.description = The currently running App on the TV
+channel-type.vizio.buttonTv.label = Remote Button
+channel-type.vizio.buttonTv.description = A Remote Button press to send to the TV
+channel-type.vizio.buttonTv.state.option.PowerOn = Power On
+channel-type.vizio.buttonTv.state.option.PowerOff = Power Off
+channel-type.vizio.buttonTv.state.option.PowerToggle = Power Toggle
+channel-type.vizio.buttonTv.state.option.VolumeUp = Volume Up
+channel-type.vizio.buttonTv.state.option.VolumeDown = Volume Down
+channel-type.vizio.buttonTv.state.option.MuteOn = Mute On
+channel-type.vizio.buttonTv.state.option.MuteOff = Mute Off
+channel-type.vizio.buttonTv.state.option.MuteToggle = Mute Toggle
+channel-type.vizio.buttonTv.state.option.ChannelUp = Channel Up
+channel-type.vizio.buttonTv.state.option.ChannelDown = Channel Down
+channel-type.vizio.buttonTv.state.option.PreviousCh = Previous Channel
+channel-type.vizio.buttonTv.state.option.InputToggle = Input Toggle
+channel-type.vizio.buttonTv.state.option.SeekFwd = Seek Fwd
+channel-type.vizio.buttonTv.state.option.SeekBack = Seek Back
+channel-type.vizio.buttonTv.state.option.Play = Play
+channel-type.vizio.buttonTv.state.option.Pause = Pause
+channel-type.vizio.buttonTv.state.option.Up = Up
+channel-type.vizio.buttonTv.state.option.Down = Down
+channel-type.vizio.buttonTv.state.option.Left = Left
+channel-type.vizio.buttonTv.state.option.Right = Right
+channel-type.vizio.buttonTv.state.option.Ok = Ok
+channel-type.vizio.buttonTv.state.option.Back = Back
+channel-type.vizio.buttonTv.state.option.Info = Info
+channel-type.vizio.buttonTv.state.option.Menu = Menu
+channel-type.vizio.buttonTv.state.option.Home = Home
+channel-type.vizio.buttonTv.state.option.Exit = Exit
+channel-type.vizio.buttonTv.state.option.Smartcast = Smartcast
+channel-type.vizio.buttonTv.state.option.ccToggle = CC Toggle
+channel-type.vizio.buttonTv.state.option.PictureMode = Picture Mode
+channel-type.vizio.buttonTv.state.option.WideMode = Wide Mode
+channel-type.vizio.buttonTv.state.option.WideToggle = Wide Toggle
+channel-type.vizio.control.label = Control
+channel-type.vizio.control.description = Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind
+channel-type.vizio.source.label = Source Input
+channel-type.vizio.source.description = Select the Source Input for the TV
+
+# message strings
+
+offline.configuration-error-hostname = Host Name must be specified
+offline.configuration-error-authtoken = Auth Token must be specified, see documentation for details
+offline.configuration-error-applist = Invalid App List Configuration in thing configuration
+offline.communication-error-get-power = Unable to retrieve power mode info from TV
+offline.communication-error-get-audio = Unable to retrieve current audio settings from TV
+offline.communication-error-get-input = Unable to retrieve current input from TV
+offline.communication-error-get-app = Unable to retrieve current running app from TV
+offline.communication-error-set-power = Unable to send power command to the TV
+offline.communication-error-set-volume = Unable to set volume on the TV
+offline.communication-error-set-mute = Unable to send mute command to the TV
+offline.communication-error-set-source = Unable to set current source on the TV
+offline.communication-error-launch-app = Unable to launch app on the TV
+offline.communication-error-send-cmd = Unable to send control command to the TV
+offline.communication-error-send-key = Unable to send keypress to the TV
diff --git a/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/thing/vizio.xml b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/thing/vizio.xml
new file mode 100644 (file)
index 0000000..6f7d16f
--- /dev/null
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="vizio"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <!-- Vizio TV Thing -->
+       <thing-type id="vizio_tv">
+               <label>Vizio TV</label>
+               <description>
+                       A Vizio SmartCast TV
+               </description>
+
+               <channels>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="source" typeId="source"/>
+                       <channel id="activeApp" typeId="activeApp"/>
+                       <channel id="control" typeId="control"/>
+                       <channel id="button" typeId="buttonTv"/>
+               </channels>
+
+               <properties>
+                       <property name="modelId">unknown</property>
+               </properties>
+
+               <representation-property>uuid</representation-property>
+
+               <config-description>
+                       <parameter name="hostName" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Host Name/IP Address</label>
+                               <description>Host Name or IP Address of the Vizio TV</description>
+                       </parameter>
+                       <parameter name="port" type="integer" min="1" max="65535" required="true">
+                               <label>Port</label>
+                               <description>Port for the Vizio TV</description>
+                               <default>7345</default>
+                               <limitToOptions>true</limitToOptions>
+                               <options>
+                                       <option value="7345">7345 (Newer Models)</option>
+                                       <option value="9000">9000 (Older Models)</option>
+                               </options>
+                       </parameter>
+                       <parameter name="authToken" type="text" required="false">
+                               <label>Auth Token</label>
+                               <description>Auth Token that is obtained via the pairing process; See documentation for details</description>
+                       </parameter>
+                       <parameter name="appListJson" type="text" required="false">
+                               <context>script</context>
+                               <label>App List Configuration</label>
+                               <description>The JSON configuration string for the list of apps available in the activeApp channel drop down</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-type id="source">
+               <item-type>String</item-type>
+               <label>Source Input</label>
+               <description>Select the Source Input for the TV</description>
+       </channel-type>
+
+       <channel-type id="activeApp">
+               <item-type>String</item-type>
+               <label>Active App</label>
+               <description>The currently running App on the TV</description>
+       </channel-type>
+
+       <channel-type id="control">
+               <item-type>Player</item-type>
+               <label>Control</label>
+               <description>Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind</description>
+               <category>Player</category>
+       </channel-type>
+
+       <channel-type id="buttonTv">
+               <item-type>String</item-type>
+               <label>Remote Button</label>
+               <description>A Remote Button press to send to the TV</description>
+               <state>
+                       <options>
+                               <option value="PowerOn">Power On</option>
+                               <option value="PowerOff">Power Off</option>
+                               <option value="PowerToggle">Power Toggle</option>
+                               <option value="VolumeUp">Volume Up</option>
+                               <option value="VolumeDown">Volume Down</option>
+                               <option value="MuteOn">Mute On</option>
+                               <option value="MuteOff">Mute Off</option>
+                               <option value="MuteToggle">Mute Toggle</option>
+                               <option value="ChannelUp">Channel Up</option>
+                               <option value="ChannelDown">Channel Down</option>
+                               <option value="PreviousCh">Previous Channel</option>
+                               <option value="InputToggle">Input Toggle</option>
+                               <option value="SeekFwd">Seek Fwd</option>
+                               <option value="SeekBack">Seek Back</option>
+                               <option value="Play">Play</option>
+                               <option value="Pause">Pause</option>
+                               <option value="Up">Up</option>
+                               <option value="Down">Down</option>
+                               <option value="Left">Left</option>
+                               <option value="Right">Right</option>
+                               <option value="Ok">Ok</option>
+                               <option value="Back">Back</option>
+                               <option value="Info">Info</option>
+                               <option value="Menu">Menu</option>
+                               <option value="Home">Home</option>
+                               <option value="Exit">Exit</option>
+                               <option value="Smartcast">Smartcast</option>
+                               <option value="ccToggle">CC Toggle</option>
+                               <option value="PictureMode">Picture Mode</option>
+                               <option value="WideMode">Wide Mode</option>
+                               <option value="WideToggle">Wide Toggle</option>
+                       </options>
+               </state>
+       </channel-type>
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.vizio/src/main/resources/db/apps.json b/bundles/org.openhab.binding.vizio/src/main/resources/db/apps.json
new file mode 100644 (file)
index 0000000..5d3b38d
--- /dev/null
@@ -0,0 +1,228 @@
+{
+   "Apps": [
+      {
+         "name": "SmartCast Home",
+         "config": {
+            "APP_ID": "1",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Apple TV+",
+         "config": {
+            "APP_ID": "4",
+            "NAME_SPACE": 3,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "CBS News",
+         "config": {
+            "APP_ID": "42",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Crackle",
+         "config": {
+            "APP_ID": "5",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "discovery+",
+         "config": {
+            "APP_ID": "130",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Disney+",
+         "config": {
+            "APP_ID": "75",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "FilmRise",
+         "config": {
+            "APP_ID": "24",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Haystack News",
+         "config": {
+            "APP_ID": "60",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "HBO Max",
+         "config": {
+            "APP_ID": "128",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Hulu",
+         "config": {
+            "APP_ID": "3",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "iHeartRadio",
+         "config": {
+            "APP_ID": "6",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Movies Anywhere",
+         "config": {
+            "APP_ID": "38",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "NBC",
+         "config": {
+            "APP_ID": "10",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Netflix",
+         "config": {
+            "APP_ID": "1",
+            "NAME_SPACE": 3,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Newsy",
+         "config": {
+            "APP_ID": "15",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Paramount+",
+         "config": {
+            "APP_ID": "37",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Peacock",
+         "config": {
+            "APP_ID": "88",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Plex",
+         "config": {
+            "APP_ID": "9",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Pluto TV",
+         "config": {
+            "APP_ID": "E6F74C01",
+            "NAME_SPACE": 0,
+            "MESSAGE": "{\"CAST_NAMESPACE\": \"urn:x-cast:tv.pluto\",\"CAST_MESSAGE\": {\"command\": \"initializePlayback\",\"channel\": \"\",\"episode\": \"\",\"time\": 0}}"
+         }
+      },
+      {
+         "name": "Prime Video",
+         "config": {
+            "APP_ID": "3",
+            "NAME_SPACE": 3,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Redbox",
+         "config": {
+            "APP_ID": "41",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Starz",
+         "config": {
+            "APP_ID": "151",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "Vudu",
+         "config": {
+            "APP_ID": "31",
+            "NAME_SPACE": 4,
+            "MESSAGE": "https://my.vudu.com/castReceiver/index.html?launch-source=app-icon"
+         }
+      },
+      {
+         "name": "XUMO",
+         "config": {
+            "APP_ID": "62",
+            "NAME_SPACE": 4,
+            "MESSAGE": "{\"CAST_NAMESPACE\": \"urn:x-cast:com.google.cast.media\",\"CAST_MESSAGE\": {\"type\": \"LOAD\",\"media\": {},\"autoplay\": true,\"currentTime\": 0,\"customData\": {}}}"
+         }
+      },
+      {
+         "name": "YouTube",
+         "config": {
+            "APP_ID": "1",
+            "NAME_SPACE": 5,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "YouTubeTV",
+         "config": {
+            "APP_ID": "3",
+            "NAME_SPACE": 5,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "WatchFree Plus",
+         "config": {
+            "APP_ID": "3014",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      },
+      {
+         "name": "WatchFree+ AVOD",
+         "config": {
+            "APP_ID": "145",
+            "NAME_SPACE": 4,
+            "MESSAGE": null
+         }
+      }
+   ]
+}
\ No newline at end of file
index 633af03dcf84106aa64b7d3edd77fd0d6a15b064..bce1e7d769bb941e372bf7dc47b3fd55f983a207 100644 (file)
     <module>org.openhab.binding.vesync</module>
     <module>org.openhab.binding.vigicrues</module>
     <module>org.openhab.binding.vitotronic</module>
+    <module>org.openhab.binding.vizio</module>
     <module>org.openhab.binding.volvooncall</module>
     <module>org.openhab.binding.warmup</module>
     <module>org.openhab.binding.weathercompany</module>