From fc529777e42874152e2d3f966a96525afa9577cd Mon Sep 17 00:00:00 2001 From: mlobstein Date: Tue, 6 Dec 2022 08:37:54 -0600 Subject: [PATCH] [vizio] Vizio TV binding - Initial contribution (#13309) Signed-off-by: Michael Lobstein --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.vizio/NOTICE | 13 + bundles/org.openhab.binding.vizio/README.md | 181 ++++++ bundles/org.openhab.binding.vizio/pom.xml | 17 + .../src/main/feature/feature.xml | 10 + .../vizio/internal/VizioBindingConstants.java | 54 ++ .../vizio/internal/VizioConfiguration.java | 30 + .../vizio/internal/VizioException.java | 29 + .../vizio/internal/VizioHandlerFactory.java | 71 +++ .../VizioStateDescriptionOptionProvider.java | 43 ++ .../internal/appdb/VizioAppDbService.java | 57 ++ .../communication/VizioCommunicator.java | 293 +++++++++ .../VizioTlsTrustManagerProvider.java | 59 ++ .../console/VizioCommandExtension.java | 183 ++++++ .../discovery/VizioDiscoveryParticipant.java | 132 ++++ .../binding/vizio/internal/dto/Item.java | 93 +++ .../vizio/internal/dto/Parameters.java | 53 ++ .../vizio/internal/dto/PutResponse.java | 53 ++ .../binding/vizio/internal/dto/Status.java | 43 ++ .../binding/vizio/internal/dto/Value.java | 43 ++ .../vizio/internal/dto/app/CurrentApp.java | 56 ++ .../vizio/internal/dto/app/ItemApp.java | 44 ++ .../vizio/internal/dto/app/ItemAppValue.java | 53 ++ .../vizio/internal/dto/applist/VizioApp.java | 43 ++ .../internal/dto/applist/VizioAppConfig.java | 53 ++ .../vizio/internal/dto/applist/VizioApps.java | 36 ++ .../vizio/internal/dto/audio/Audio.java | 120 ++++ .../vizio/internal/dto/audio/ItemAudio.java | 93 +++ .../internal/dto/input/CurrentInput.java | 80 +++ .../vizio/internal/dto/input/ItemInput.java | 93 +++ .../internal/dto/inputlist/InputList.java | 121 ++++ .../internal/dto/pairing/ItemAuthToken.java | 33 + .../internal/dto/pairing/ItemPairing.java | 43 ++ .../internal/dto/pairing/PairingComplete.java | 34 + .../internal/dto/pairing/PairingStart.java | 34 + .../vizio/internal/dto/power/ItemPower.java | 63 ++ .../vizio/internal/dto/power/PowerMode.java | 59 ++ .../vizio/internal/enums/KeyCommand.java | 70 +++ .../vizio/internal/handler/VizioHandler.java | 584 ++++++++++++++++++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../resources/OH-INF/i18n/vizio.properties | 81 +++ .../src/main/resources/OH-INF/thing/vizio.xml | 117 ++++ .../src/main/resources/db/apps.json | 228 +++++++ bundles/pom.xml | 1 + 45 files changed, 3611 insertions(+) create mode 100644 bundles/org.openhab.binding.vizio/NOTICE create mode 100644 bundles/org.openhab.binding.vizio/README.md create mode 100644 bundles/org.openhab.binding.vizio/pom.xml create mode 100644 bundles/org.openhab.binding.vizio/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioBindingConstants.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioConfiguration.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioException.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioHandlerFactory.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioStateDescriptionOptionProvider.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/appdb/VizioAppDbService.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioCommunicator.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioTlsTrustManagerProvider.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/console/VizioCommandExtension.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/discovery/VizioDiscoveryParticipant.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Item.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Parameters.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/PutResponse.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Status.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Value.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/CurrentApp.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemApp.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemAppValue.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApp.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioAppConfig.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApps.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/Audio.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/ItemAudio.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/CurrentInput.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/ItemInput.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/inputlist/InputList.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemAuthToken.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemPairing.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingComplete.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingStart.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/ItemPower.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/PowerMode.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/enums/KeyCommand.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/handler/VizioHandler.java create mode 100644 bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/i18n/vizio.properties create mode 100644 bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/thing/vizio.xml create mode 100644 bundles/org.openhab.binding.vizio/src/main/resources/db/apps.json diff --git a/CODEOWNERS b/CODEOWNERS index 2714540d6da..a275a0b71cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -358,6 +358,7 @@ /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 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 53d0bec852d..52b327901c7 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1781,6 +1781,11 @@ org.openhab.binding.vitotronic ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.vizio + ${project.version} + org.openhab.addons.bundles org.openhab.binding.volvooncall diff --git a/bundles/org.openhab.binding.vizio/NOTICE b/bundles/org.openhab.binding.vizio/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/NOTICE @@ -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 index 00000000000..7c72f7b0ffa --- /dev/null +++ b/bundles/org.openhab.binding.vizio/README.md @@ -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 start_pairing +``` + +Substitute `` with thing's id, ie: `vizio_tv:00bc3e711660` +Substitute `` the desired device name that will appear in the TV's settings, under Mobile Devices, ie: `Vizio-openHAB` + +Submit Pairing Code: + +``` +openhab:vizio submit_code +``` + +Substitute `` with the same thing id used above +Substitute `` 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` & `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 index 00000000000..34c5fbd89a6 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.4.0-SNAPSHOT + + + org.openhab.binding.vizio + + openHAB Add-ons :: Bundles :: Vizio Binding + + 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 index 00000000000..83ec41e2489 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/feature/feature.xml @@ -0,0 +1,10 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-mdns + mvn:org.openhab.addons.bundles/org.openhab.binding.vizio/${project.version} + + 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 index 00000000000..3198cd7c1e6 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioBindingConstants.java @@ -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 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 index 00000000000..ab95e298cda --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioConfiguration.java @@ -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 index 00000000000..21ccb4df918 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioException.java @@ -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 index 00000000000..9c7be002e3f --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioHandlerFactory.java @@ -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 index 00000000000..5a51cbccf2c --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/VizioStateDescriptionOptionProvider.java @@ -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 index 00000000000..a1694d0216b --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/appdb/VizioAppDbService.java @@ -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 index 00000000000..d7095938c8b --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioCommunicator.java @@ -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 fromJson(String json, Class 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 index 00000000000..67f35616e01 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/communication/VizioTlsTrustManagerProvider.java @@ -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 index 00000000000..e2ae8609533 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/console/VizioCommandExtension.java @@ -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 + " "); + } 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 + + " "); + 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 getUsages() { + return List.of(new String[] { + buildCommandUsage(" " + START_PAIRING + " ", "start pairing process"), + buildCommandUsage(" " + SUBMIT_CODE + " ", "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 index 00000000000..f45f8d4bb4f --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/discovery/VizioDiscoveryParticipant.java @@ -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 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 index 00000000000..f7bb76fafe2 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Item.java @@ -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 index 00000000000..dfcdcc6b2b0 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Parameters.java @@ -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 index 00000000000..76b6b746b77 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/PutResponse.java @@ -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 index 00000000000..b0befb8cbb0 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Status.java @@ -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 index 00000000000..1e65781c875 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/Value.java @@ -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 index 00000000000..81b5c9ccce5 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/CurrentApp.java @@ -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 index 00000000000..2728a0f722e --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemApp.java @@ -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 index 00000000000..d5fdda94f4c --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/app/ItemAppValue.java @@ -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 index 00000000000..04a77113475 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApp.java @@ -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 index 00000000000..b936219d34c --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioAppConfig.java @@ -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 index 00000000000..8ed45ebed28 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/applist/VizioApps.java @@ -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 apps = new ArrayList(); + + public List getApps() { + return apps; + } + + public void setApps(List 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 index 00000000000..d42ee1656ba --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/Audio.java @@ -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 hashlist = new ArrayList(); + @SerializedName("GROUP") + private String group; + @SerializedName("NAME") + private String name; + @SerializedName("PARAMETERS") + private Parameters parameters; + @SerializedName("ITEMS") + private List items = new ArrayList(); + @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 getHashlist() { + return hashlist; + } + + public void setHashlist(List 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 getItems() { + return items; + } + + public void setItems(List 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 index 00000000000..cc0d094d575 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/audio/ItemAudio.java @@ -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 index 00000000000..5ca0aa9b6f0 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/CurrentInput.java @@ -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 items = new ArrayList(); + @SerializedName("HASHLIST") + private List hashlist = new ArrayList(); + @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 getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public List getHashlist() { + return hashlist; + } + + public void setHashlist(List 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 index 00000000000..d4e0ed4f2ec --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/input/ItemInput.java @@ -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 index 00000000000..5b2014e18e8 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/inputlist/InputList.java @@ -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 hashlist = new ArrayList(); + @SerializedName("GROUP") + private String group; + @SerializedName("NAME") + private String name; + @SerializedName("PARAMETERS") + private Parameters parameters; + @SerializedName("ITEMS") + private List items = new ArrayList(); + @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 getHashlist() { + return hashlist; + } + + public void setHashlist(List 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 getItems() { + return items; + } + + public void setItems(List 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 index 00000000000..1216394db06 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemAuthToken.java @@ -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 index 00000000000..0b198212a57 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/ItemPairing.java @@ -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 index 00000000000..eb1ec052f17 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingComplete.java @@ -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 index 00000000000..a59fc66c004 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/pairing/PairingStart.java @@ -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 index 00000000000..2c5054a6f47 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/ItemPower.java @@ -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 index 00000000000..6e5fae41c4c --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/dto/power/PowerMode.java @@ -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 items = new ArrayList(); + @SerializedName("URI") + private String uri; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public List getItems() { + return items; + } + + public void setItems(List 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 index 00000000000..f52e9cec656 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/enums/KeyCommand.java @@ -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 index 00000000000..b3e7131b5e3 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/java/org/openhab/binding/vizio/internal/handler/VizioHandler.java @@ -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 userConfigApps = new ArrayList(); + 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 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 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 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 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 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 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 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 index 00000000000..d0e8c2e6007 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + Vizio Binding + Controls Vizio TVs w/SmartCast API (2016+ Models) + + 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 index 00000000000..c26e2a261f2 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/i18n/vizio.properties @@ -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 index 00000000000..6f7d16fea52 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/resources/OH-INF/thing/vizio.xml @@ -0,0 +1,117 @@ + + + + + + + + A Vizio SmartCast TV + + + + + + + + + + + + + + unknown + + + uuid + + + + network-address + + Host Name or IP Address of the Vizio TV + + + + Port for the Vizio TV + 7345 + true + + + + + + + + Auth Token that is obtained via the pairing process; See documentation for details + + + script + + The JSON configuration string for the list of apps available in the activeApp channel drop down + + + + + + String + + Select the Source Input for the TV + + + + String + + The currently running App on the TV + + + + Player + + Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind + Player + + + + String + + A Remote Button press to send to the TV + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 00000000000..5d3b38db3e3 --- /dev/null +++ b/bundles/org.openhab.binding.vizio/src/main/resources/db/apps.json @@ -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 diff --git a/bundles/pom.xml b/bundles/pom.xml index 633af03dcf8..bce1e7d769b 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -390,6 +390,7 @@ org.openhab.binding.vesync org.openhab.binding.vigicrues org.openhab.binding.vitotronic + org.openhab.binding.vizio org.openhab.binding.volvooncall org.openhab.binding.warmup org.openhab.binding.weathercompany