mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 06:45:57 +01:00
[unifiedremote] Initial contribution (#8546)
Signed-off-by: GiviMAD <miguelwork92@gmail.com>
This commit is contained in:
parent
cb5d8711b8
commit
8b8b79cf04
@ -246,6 +246,7 @@
|
|||||||
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
|
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
|
||||||
/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer
|
/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer
|
||||||
/bundles/org.openhab.binding.unifi/ @mgbowman
|
/bundles/org.openhab.binding.unifi/ @mgbowman
|
||||||
|
/bundles/org.openhab.binding.unifiedremote/ @GiviMAD
|
||||||
/bundles/org.openhab.binding.upb/ @marcusb
|
/bundles/org.openhab.binding.upb/ @marcusb
|
||||||
/bundles/org.openhab.binding.upnpcontrol/ @mherwege
|
/bundles/org.openhab.binding.upnpcontrol/ @mherwege
|
||||||
/bundles/org.openhab.binding.urtsi/ @OLibutzki
|
/bundles/org.openhab.binding.urtsi/ @OLibutzki
|
||||||
|
@ -1216,6 +1216,11 @@
|
|||||||
<artifactId>org.openhab.binding.unifi</artifactId>
|
<artifactId>org.openhab.binding.unifi</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.unifiedremote</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.upb</artifactId>
|
<artifactId>org.openhab.binding.upb</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.unifiedremote/NOTICE
Normal file
13
bundles/org.openhab.binding.unifiedremote/NOTICE
Normal file
@ -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
|
49
bundles/org.openhab.binding.unifiedremote/README.md
Normal file
49
bundles/org.openhab.binding.unifiedremote/README.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# UnifiedRemote Binding
|
||||||
|
|
||||||
|
This binding integrates the [Unified Remote Server](https://www.unifiedremote.com/).
|
||||||
|
|
||||||
|
<b>Known Limitations: It needs the web interface to be enabled on the server settings to work.</b>
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Discovery works on the default discovery UDP port 9511.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
Only supported thing is 'Unified Remote Server Thing' which requires the Hostname to be correctly configured in order to work.
|
||||||
|
|
||||||
|
| ThinTypeID | description |
|
||||||
|
|----------|------------------------------|
|
||||||
|
| server | Unified Remote Server Thing |
|
||||||
|
|
||||||
|
|
||||||
|
| Config | Type | description |
|
||||||
|
|----------|----------|------------------------------|
|
||||||
|
| host | String | Unified Remote Server IP |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
|
||||||
|
| channel | type | description |
|
||||||
|
|----------|--------|------------------------------|
|
||||||
|
| mouse-move | String | Relative mouse move in pixels. Expect number JSON array [x,y] ("[10,10]"). |
|
||||||
|
| send-key | String | Use server key. Supported keys are: LEFT_CLICK, RIGHT_CLICK, LOCK, UNLOCK, SLEEP, SHUTDOWN, RESTART, LOGOFF, PLAY, PLAY, PAUSE, NEXT, PREVIOUS, STOP, VOLUME_MUTE, VOLUME_UP, VOLUME_DOWN, BRIGHTNESS_UP, BRIGHTNESS_DOWN, MONITOR_OFF, MONITOR_ON, ESCAPE, SPACE, BACK, LWIN, CONTROL, TAB, MENU, RETURN, UP, DOWN, LEFT, RIGHT |
|
||||||
|
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
### Sample Thing
|
||||||
|
|
||||||
|
```
|
||||||
|
Thing unifiedremote:server:xx-xx-xx-xx-xx-xx [ host="192.168.1.10" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample Items
|
||||||
|
|
||||||
|
```
|
||||||
|
Group pcRemote "Living room PC"
|
||||||
|
String PC_SendKey "Send Key" (pcRemote) { channel="unifiedremote:server:xx-xx-xx-xx-xx-xx:send-key" }
|
||||||
|
String PC_MouseMove "Mouse Move" (pcRemote) { channel="samsungtv:tv:livingroom:mouse-move" }
|
||||||
|
```
|
17
bundles/org.openhab.binding.unifiedremote/pom.xml
Normal file
17
bundles/org.openhab.binding.unifiedremote/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>3.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.unifiedremote</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: UnifiedRemote Binding</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Copyright (c) 2010-2020 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
|
||||||
|
|
||||||
|
-->
|
||||||
|
<features name="org.openhab.binding.unifiedremote-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||||
|
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||||
|
|
||||||
|
<feature name="openhab-binding-unifiedremote" description="UnifiedRemote Binding" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.unifiedremote/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UnifiedRemoteBindingConstants} class defines common constants, which are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Miguel Álvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UnifiedRemoteBindingConstants {
|
||||||
|
|
||||||
|
private static final String BINDING_ID = "unifiedremote";
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_UNIFIED_REMOTE_SERVER = new ThingTypeUID(BINDING_ID, "server");
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
|
||||||
|
.singleton(THING_TYPE_UNIFIED_REMOTE_SERVER);
|
||||||
|
|
||||||
|
// List of all Channel ids
|
||||||
|
public static final String MOUSE_CHANNEL = "mouse-move";
|
||||||
|
public static final String SEND_KEY_CHANNEL = "send-key";
|
||||||
|
|
||||||
|
// List of all Parameters
|
||||||
|
public static final String PARAMETER_MAC_ADDRESS = "macAddress";
|
||||||
|
public static final String PARAMETER_HOSTNAME = "host";
|
||||||
|
public static final String PARAMETER_TCP_PORT = "udpPort";
|
||||||
|
public static final String PARAMETER_UDP_PORT = "tcpPort";
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UnifiedRemoteConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Miguel Álvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UnifiedRemoteConfiguration {
|
||||||
|
public String host = "";
|
||||||
|
public int tcpPort;
|
||||||
|
public int udpPort;
|
||||||
|
}
|
@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
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.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UnifiedRemoteConnection} Handles Remote Server Communications
|
||||||
|
*
|
||||||
|
* @author Miguel Alvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UnifiedRemoteConnection {
|
||||||
|
|
||||||
|
private static final int WEB_CLIENT_PORT = 9510;
|
||||||
|
private static final int TIMEOUT_SEC = 10;
|
||||||
|
private static final String CONNECTION_ID_HEADER = "UR-Connection-ID";
|
||||||
|
private static final String MOUSE_REMOTE = "Relmtech.Basic Input";
|
||||||
|
private static final String NAVIGATION_REMOTE = "Unified.Navigation";
|
||||||
|
private static final String POWER_REMOTE = "Unified.Power";
|
||||||
|
private static final String MEDIA_REMOTE = "Unified.Media";
|
||||||
|
private static final String MONITOR_REMOTE = "Unified.Monitor";
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(UnifiedRemoteConnection.class);
|
||||||
|
private final String url;
|
||||||
|
private final JsonParser jsonParser = new JsonParser();
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private @Nullable String connectionID;
|
||||||
|
private @Nullable String connectionGUID;
|
||||||
|
|
||||||
|
public UnifiedRemoteConnection(HttpClient httpClient, String host) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
url = "http://" + host + ":" + WEB_CLIENT_PORT + "/client/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticate() throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
ContentResponse response = null;
|
||||||
|
connectionGUID = "web-" + UUID.randomUUID().toString();
|
||||||
|
response = httpClient.newRequest(getPath("connect")).method(HttpMethod.GET)
|
||||||
|
.timeout(TIMEOUT_SEC, TimeUnit.SECONDS).send();
|
||||||
|
JsonObject responseBody = jsonParser.parse(response.getContentAsString()).getAsJsonObject();
|
||||||
|
connectionID = responseBody.get("id").getAsString();
|
||||||
|
|
||||||
|
String password = UUID.randomUUID().toString();
|
||||||
|
JsonObject authPayload = new JsonObject();
|
||||||
|
authPayload.addProperty("Action", 0);
|
||||||
|
authPayload.addProperty("Request", 0);
|
||||||
|
authPayload.addProperty("Version", 10);
|
||||||
|
authPayload.addProperty("Password", password);
|
||||||
|
authPayload.addProperty("Platform", "web");
|
||||||
|
authPayload.addProperty("Source", connectionGUID);
|
||||||
|
request(authPayload);
|
||||||
|
|
||||||
|
JsonObject capabilitiesPayload = new JsonObject();
|
||||||
|
JsonObject capabilitiesInnerPayload = new JsonObject();
|
||||||
|
capabilitiesInnerPayload.addProperty("Actions", true);
|
||||||
|
capabilitiesInnerPayload.addProperty("Sync", true);
|
||||||
|
capabilitiesInnerPayload.addProperty("Grid", true);
|
||||||
|
capabilitiesInnerPayload.addProperty("Fast", false);
|
||||||
|
capabilitiesInnerPayload.addProperty("Loading", true);
|
||||||
|
capabilitiesInnerPayload.addProperty("Encryption2", true);
|
||||||
|
capabilitiesPayload.add("Capabilities", capabilitiesInnerPayload);
|
||||||
|
capabilitiesPayload.addProperty("Action", 1);
|
||||||
|
capabilitiesPayload.addProperty("Request", 1);
|
||||||
|
capabilitiesPayload.addProperty("Source", connectionGUID);
|
||||||
|
request(capabilitiesPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentResponse mouseMove(String jsonIntArray)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
JsonArray cordinates = jsonParser.parse(jsonIntArray).getAsJsonArray();
|
||||||
|
int x = cordinates.get(0).getAsInt();
|
||||||
|
int y = cordinates.get(1).getAsInt();
|
||||||
|
return this.execRemoteAction("Relmtech.Basic Input", "delta",
|
||||||
|
wrapValues(new String[] { "0", Integer.toString(x), Integer.toString(y) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentResponse sendKey(String key) throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
String remoteID = "";
|
||||||
|
String actionName = "";
|
||||||
|
String value = null;
|
||||||
|
switch (key) {
|
||||||
|
case "LEFT_CLICK":
|
||||||
|
remoteID = MOUSE_REMOTE;
|
||||||
|
actionName = "left";
|
||||||
|
break;
|
||||||
|
case "RIGHT_CLICK":
|
||||||
|
remoteID = MOUSE_REMOTE;
|
||||||
|
actionName = "right";
|
||||||
|
break;
|
||||||
|
case "LOCK":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "lock";
|
||||||
|
break;
|
||||||
|
case "UNLOCK":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "unlock";
|
||||||
|
break;
|
||||||
|
case "SLEEP":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "sleep";
|
||||||
|
break;
|
||||||
|
case "SHUTDOWN":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "shutdown";
|
||||||
|
break;
|
||||||
|
case "RESTART":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "restart";
|
||||||
|
break;
|
||||||
|
case "LOGOFF":
|
||||||
|
remoteID = POWER_REMOTE;
|
||||||
|
actionName = "logoff";
|
||||||
|
break;
|
||||||
|
case "PLAY/PAUSE":
|
||||||
|
case "PLAY":
|
||||||
|
case "PAUSE":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "play_pause";
|
||||||
|
break;
|
||||||
|
case "NEXT":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "next";
|
||||||
|
break;
|
||||||
|
case "PREVIOUS":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "previous";
|
||||||
|
break;
|
||||||
|
case "STOP":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "stop";
|
||||||
|
break;
|
||||||
|
case "VOLUME_MUTE":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "volume_mute";
|
||||||
|
break;
|
||||||
|
case "VOLUME_UP":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "volume_up";
|
||||||
|
break;
|
||||||
|
case "VOLUME_DOWN":
|
||||||
|
remoteID = MEDIA_REMOTE;
|
||||||
|
actionName = "volume_down";
|
||||||
|
break;
|
||||||
|
case "BRIGHTNESS_UP":
|
||||||
|
remoteID = MONITOR_REMOTE;
|
||||||
|
actionName = "brightness_up";
|
||||||
|
break;
|
||||||
|
case "BRIGHTNESS_DOWN":
|
||||||
|
remoteID = MONITOR_REMOTE;
|
||||||
|
actionName = "brightness_down";
|
||||||
|
break;
|
||||||
|
case "MONITOR_OFF":
|
||||||
|
remoteID = MONITOR_REMOTE;
|
||||||
|
actionName = "turn_off";
|
||||||
|
break;
|
||||||
|
case "MONITOR_ON":
|
||||||
|
remoteID = MONITOR_REMOTE;
|
||||||
|
actionName = "turn_on";
|
||||||
|
break;
|
||||||
|
case "ESCAPE":
|
||||||
|
case "SPACE":
|
||||||
|
case "BACK":
|
||||||
|
case "LWIN":
|
||||||
|
case "CONTROL":
|
||||||
|
case "TAB":
|
||||||
|
case "MENU":
|
||||||
|
case "RETURN":
|
||||||
|
case "UP":
|
||||||
|
case "DOWN":
|
||||||
|
case "LEFT":
|
||||||
|
case "RIGHT":
|
||||||
|
remoteID = NAVIGATION_REMOTE;
|
||||||
|
actionName = "toggle";
|
||||||
|
value = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
JsonArray wrappedValues = null;
|
||||||
|
if (value != null) {
|
||||||
|
wrappedValues = wrapValues(new String[] { value });
|
||||||
|
}
|
||||||
|
return this.execRemoteAction(remoteID, actionName, wrappedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentResponse keepAlive() throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
JsonObject payload = new JsonObject();
|
||||||
|
payload.addProperty("KeepAlive", true);
|
||||||
|
payload.addProperty("Source", connectionGUID);
|
||||||
|
return request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentResponse execRemoteAction(String remoteID, String name, @Nullable JsonElement values)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
JsonObject payload = new JsonObject();
|
||||||
|
|
||||||
|
JsonObject runInnerPayload = new JsonObject();
|
||||||
|
JsonObject extrasInnerPayload = new JsonObject();
|
||||||
|
if (values != null) {
|
||||||
|
extrasInnerPayload.add("Values", values);
|
||||||
|
runInnerPayload.add("Extras", extrasInnerPayload);
|
||||||
|
}
|
||||||
|
runInnerPayload.addProperty("Name", name);
|
||||||
|
payload.addProperty("ID", remoteID);
|
||||||
|
payload.addProperty("Action", 7);
|
||||||
|
payload.addProperty("Request", 7);
|
||||||
|
payload.add("Run", runInnerPayload);
|
||||||
|
payload.addProperty("Source", connectionGUID);
|
||||||
|
return request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentResponse request(JsonObject content)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
Request request = httpClient.newRequest(getPath("request")).method(HttpMethod.POST).timeout(TIMEOUT_SEC,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
request.header(HttpHeader.CONTENT_TYPE, "application/json");
|
||||||
|
if (connectionID != null)
|
||||||
|
request.header(CONNECTION_ID_HEADER, connectionID);
|
||||||
|
String stringContent = content.toString();
|
||||||
|
logger.debug("[Request Payload {} ]", stringContent);
|
||||||
|
request.content(new StringContentProvider(stringContent, "utf-8"));
|
||||||
|
return request.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray wrapValues(String[] commandValues) {
|
||||||
|
JsonArray values = new JsonArray();
|
||||||
|
for (String value : commandValues) {
|
||||||
|
JsonObject valueWrapper = new JsonObject();
|
||||||
|
valueWrapper.addProperty("Value", value);
|
||||||
|
values.add(valueWrapper);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPath(String path) {
|
||||||
|
return url + path;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.unifiedremote.internal.UnifiedRemoteBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.*;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UnifiedRemoteDiscoveryService} discover Unified Remote Server Instances in the network.
|
||||||
|
*
|
||||||
|
* @author Miguel Alvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@Component(service = DiscoveryService.class, configurationPid = "discovery.unifiedremote")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UnifiedRemoteDiscoveryService extends AbstractDiscoveryService {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(UnifiedRemoteDiscoveryService.class);
|
||||||
|
static final int TIMEOUT_MS = 20000;
|
||||||
|
private static final long DISCOVERY_RESULT_TTL_SEC = TimeUnit.MINUTES.toSeconds(5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port used for broadcast and listening.
|
||||||
|
*/
|
||||||
|
public static final int DISCOVERY_PORT = 9511;
|
||||||
|
/**
|
||||||
|
* String the client sends, to disambiguate packets on this port.
|
||||||
|
*/
|
||||||
|
public static final String DISCOVERY_REQUEST = "6N T|-Ar-A6N T|-Ar-A6N T|-Ar-A";
|
||||||
|
/**
|
||||||
|
* String the client sends, to disambiguate packets on this port.
|
||||||
|
*/
|
||||||
|
public static final String DISCOVERY_RESPONSE_PREFIX = ")-b@ h): :)i)-b@ h): :)i)-b@ h): :)";
|
||||||
|
/**
|
||||||
|
* String used to replace non printable characters on service response
|
||||||
|
*/
|
||||||
|
public static final String NON_PRINTABLE_CHARTS_REPLACEMENT = ": :";
|
||||||
|
|
||||||
|
private static final int MAX_PACKET_SIZE = 2048;
|
||||||
|
/**
|
||||||
|
* maximum time to wait for a reply, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static final int SOCKET_TIMEOUT_MS = 3000;
|
||||||
|
|
||||||
|
public UnifiedRemoteDiscoveryService() {
|
||||||
|
super(SUPPORTED_THING_TYPES, TIMEOUT_MS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startScan() {
|
||||||
|
sendBroadcast(this::addNewServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewServer(ServerInfo serverInfo) {
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put(PARAMETER_MAC_ADDRESS, serverInfo.macAddress);
|
||||||
|
properties.put(PARAMETER_HOSTNAME, serverInfo.host);
|
||||||
|
properties.put(PARAMETER_TCP_PORT, serverInfo.tcpPort);
|
||||||
|
properties.put(PARAMETER_UDP_PORT, serverInfo.udpPort);
|
||||||
|
thingDiscovered(
|
||||||
|
DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_UNIFIED_REMOTE_SERVER, serverInfo.macAddress))
|
||||||
|
.withTTL(DISCOVERY_RESULT_TTL_SEC).withRepresentationProperty(PARAMETER_MAC_ADDRESS)
|
||||||
|
.withProperties(properties).withLabel(serverInfo.name).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a UDP socket on the service discovery broadcast port.
|
||||||
|
*
|
||||||
|
* @return open DatagramSocket if successful
|
||||||
|
* @throws RuntimeException if cannot create the socket
|
||||||
|
*/
|
||||||
|
public DatagramSocket createSocket() throws SocketException {
|
||||||
|
DatagramSocket socket;
|
||||||
|
socket = new DatagramSocket();
|
||||||
|
socket.setBroadcast(true);
|
||||||
|
socket.setSoTimeout(TIMEOUT_MS);
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerInfo tryParseServerDiscovery(DatagramPacket receivePacket) throws ParseException {
|
||||||
|
String host = receivePacket.getAddress().getHostAddress();
|
||||||
|
String reply = new String(receivePacket.getData()).replaceAll("[\\p{C}]", NON_PRINTABLE_CHARTS_REPLACEMENT)
|
||||||
|
.replaceAll("[^\\x00-\\x7F]", NON_PRINTABLE_CHARTS_REPLACEMENT);
|
||||||
|
if (!reply.startsWith(DISCOVERY_RESPONSE_PREFIX))
|
||||||
|
throw new ParseException("Bad discovery response prefix", 0);
|
||||||
|
String[] parts = Arrays
|
||||||
|
.stream(reply.replace(DISCOVERY_RESPONSE_PREFIX, "").split(NON_PRINTABLE_CHARTS_REPLACEMENT))
|
||||||
|
.filter((String e) -> e.length() != 0).toArray(String[]::new);
|
||||||
|
String name = parts[0];
|
||||||
|
int tcpPort = Integer.parseInt(parts[1]);
|
||||||
|
int udpPort = Integer.parseInt(parts[3]);
|
||||||
|
String macAddress = parts[2];
|
||||||
|
return new ServerInfo(host, tcpPort, udpPort, name, macAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send broadcast packets with service request string until a response
|
||||||
|
* is received. Return the response as String (even though it should
|
||||||
|
* contain an internet address).
|
||||||
|
*
|
||||||
|
* @return String received from server. Should be server IP address.
|
||||||
|
* Returns empty string if failed to get valid reply.
|
||||||
|
*/
|
||||||
|
public void sendBroadcast(Consumer<ServerInfo> listener) {
|
||||||
|
byte[] receiveBuffer = new byte[MAX_PACKET_SIZE];
|
||||||
|
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
|
||||||
|
|
||||||
|
DatagramSocket socket = null;
|
||||||
|
try {
|
||||||
|
socket = createSocket();
|
||||||
|
} catch (SocketException e) {
|
||||||
|
logger.debug("Error creating discovery socket: {}", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] packetData = DISCOVERY_REQUEST.getBytes();
|
||||||
|
try {
|
||||||
|
InetAddress broadcastAddress = InetAddress.getByName("255.255.255.255");
|
||||||
|
int servicePort = DISCOVERY_PORT;
|
||||||
|
DatagramPacket packet = new DatagramPacket(packetData, packetData.length, broadcastAddress, servicePort);
|
||||||
|
socket.send(packet);
|
||||||
|
logger.debug("Sent packet to {}:{}", broadcastAddress.getHostAddress(), servicePort);
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
socket.receive(receivePacket);
|
||||||
|
String host = receivePacket.getAddress().getHostAddress();
|
||||||
|
logger.debug("Received reply from {}", host);
|
||||||
|
try {
|
||||||
|
ServerInfo serverInfo = tryParseServerDiscovery(receivePacket);
|
||||||
|
listener.accept(serverInfo);
|
||||||
|
} catch (ParseException ex) {
|
||||||
|
logger.debug("Unable to parse server discovery response from {}: {}", host, ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException ste) {
|
||||||
|
logger.debug("SocketTimeoutException during socket operation: {}", ste.getMessage());
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
logger.debug("IOException during socket operation: {}", ioe.getMessage());
|
||||||
|
} finally {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerInfo {
|
||||||
|
String name;
|
||||||
|
int tcpPort;
|
||||||
|
int udpPort;
|
||||||
|
String host;
|
||||||
|
String macAddress;
|
||||||
|
|
||||||
|
ServerInfo(String host, int tcpPort, int udpPort, String name, String macAddress) {
|
||||||
|
this.name = name;
|
||||||
|
this.tcpPort = tcpPort;
|
||||||
|
this.udpPort = udpPort;
|
||||||
|
this.host = host;
|
||||||
|
this.macAddress = macAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.unifiedremote.internal.UnifiedRemoteBindingConstants.MOUSE_CHANNEL;
|
||||||
|
import static org.openhab.binding.unifiedremote.internal.UnifiedRemoteBindingConstants.SEND_KEY_CHANNEL;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UnifiedRemoteHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Miguel Alvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UnifiedRemoteHandler extends BaseThingHandler {
|
||||||
|
|
||||||
|
private @Nullable UnifiedRemoteConnection connection;
|
||||||
|
private @Nullable ScheduledFuture<?> connectionCheckerSchedule;
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
public UnifiedRemoteHandler(Thing thing, HttpClient httpClient) {
|
||||||
|
super(thing);
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
String channelId = channelUID.getId();
|
||||||
|
if (!isLinked(channelId))
|
||||||
|
return;
|
||||||
|
String stringCommand = command.toFullString();
|
||||||
|
UnifiedRemoteConnection urConnection = connection;
|
||||||
|
try {
|
||||||
|
if (urConnection != null) {
|
||||||
|
ContentResponse response;
|
||||||
|
switch (channelId) {
|
||||||
|
case MOUSE_CHANNEL:
|
||||||
|
response = urConnection.mouseMove(stringCommand);
|
||||||
|
break;
|
||||||
|
case SEND_KEY_CHANNEL:
|
||||||
|
response = urConnection.sendKey(stringCommand);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isErrorResponse(response)) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Session expired");
|
||||||
|
urConnection.authenticate();
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection not initialized");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
|
if (isThingOfflineException(e)) {
|
||||||
|
// we assume thing is offline
|
||||||
|
updateStatus(ThingStatus.OFFLINE);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Unexpected exception: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
connection = getNewConnection();
|
||||||
|
initConnectionChecker();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnifiedRemoteConnection getNewConnection() {
|
||||||
|
UnifiedRemoteConfiguration currentConfiguration = getConfigAs(UnifiedRemoteConfiguration.class);
|
||||||
|
return new UnifiedRemoteConnection(this.httpClient, currentConfiguration.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initConnectionChecker() {
|
||||||
|
stopConnectionChecker();
|
||||||
|
connectionCheckerSchedule = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
|
try {
|
||||||
|
UnifiedRemoteConnection urConnection = connection;
|
||||||
|
if (urConnection == null)
|
||||||
|
return;
|
||||||
|
ThingStatus status = thing.getStatus();
|
||||||
|
if ((status == ThingStatus.OFFLINE || status == ThingStatus.UNKNOWN) && connection != null) {
|
||||||
|
urConnection.authenticate();
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} else if (status == ThingStatus.ONLINE) {
|
||||||
|
if (isErrorResponse(urConnection.keepAlive())) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Keep alive failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
|
if (isThingOfflineException(e)) {
|
||||||
|
// we assume thing is offline
|
||||||
|
updateStatus(ThingStatus.OFFLINE);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Unexpected exception: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 40, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isThingOfflineException(Exception e) {
|
||||||
|
return e instanceof TimeoutException || e.getCause() instanceof ConnectException
|
||||||
|
|| e.getCause() instanceof NoRouteToHostException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopConnectionChecker() {
|
||||||
|
var schedule = connectionCheckerSchedule;
|
||||||
|
if (schedule != null) {
|
||||||
|
schedule.cancel(true);
|
||||||
|
connectionCheckerSchedule = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
stopConnectionChecker();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isErrorResponse(ContentResponse response) {
|
||||||
|
return response.getStatus() != 200 || response.getContentAsString().contains("Not a valid connection");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.unifiedremote.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
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 UnifiedRemoteHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Miguel Álvarez - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.unifiedremote", service = ThingHandlerFactory.class)
|
||||||
|
public class UnifiedRemoteHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public UnifiedRemoteHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||||
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return UnifiedRemoteBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
if (supportsThingType(thingTypeUID)) {
|
||||||
|
return new UnifiedRemoteHandler(thing, httpClient);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="unifiedremote" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||||
|
|
||||||
|
<name>Unified Remote Binding</name>
|
||||||
|
<description>This is the binding for Unified Remote Server (https://www.unifiedremote.com/).</description>
|
||||||
|
<author>Miguel Álvarez</author>
|
||||||
|
|
||||||
|
</binding:binding>
|
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="unifiedremote"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<thing-type id="server">
|
||||||
|
<label>Unified Remote Server</label>
|
||||||
|
<description>Unified Remote Server Thing for Unified Remote Binding</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="mouse-move" typeId="mouse-move-channel"/>
|
||||||
|
<channel id="send-key" typeId="send-key-channel"/>
|
||||||
|
</channels>
|
||||||
|
<representation-property>macAddress</representation-property>
|
||||||
|
<config-description>
|
||||||
|
<parameter name="host" type="text" required="true">
|
||||||
|
<label>Hostname</label>
|
||||||
|
<context>network-address</context>
|
||||||
|
<description>Unified Remote Server Hostname</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="tcpPort" type="integer">
|
||||||
|
<label>TCP Port</label>
|
||||||
|
<description>Unified Remote Server Port TCP</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="udpPort" type="integer">
|
||||||
|
<label>UDP Port</label>
|
||||||
|
<description>Unified Remote Server Port UDP</description>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<channel-type id="mouse-move-channel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Mouse Move Channel</label>
|
||||||
|
<description>Relative mouse control on the server host</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="send-key-channel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Toggle Key Channel</label>
|
||||||
|
<description>Toggle Key</description>
|
||||||
|
<state>
|
||||||
|
<options>
|
||||||
|
<!-- MOUSE -->
|
||||||
|
<option value="LEFT_CLICK">LEFT_CLICK</option>
|
||||||
|
<option value="RIGHT_CLICK">RIGHT_CLICK</option>
|
||||||
|
<!-- SYSTEM -->
|
||||||
|
<option value="LOCK">LOCK</option>
|
||||||
|
<option value="UNLOCK">UNLOCK</option>
|
||||||
|
<option value="SLEEP">SLEEP</option>
|
||||||
|
<option value="SHUTDOWN">SHUTDOWN</option>
|
||||||
|
<option value="RESTART">RESTART</option>
|
||||||
|
<option value="LOGOFF">LOGOFF</option>
|
||||||
|
<!-- Media -->
|
||||||
|
<option value="PLAY/PAUSE">PLAY/PAUSE</option>
|
||||||
|
<option value="NEXT">NEXT</option>
|
||||||
|
<option value="PREVIOUS">PREVIOUS</option>
|
||||||
|
<option value="STOP">STOP</option>
|
||||||
|
<option value="VOLUME_MUTE">VOLUME_MUTE</option>
|
||||||
|
<option value="VOLUME_UP">VOLUME_UP</option>
|
||||||
|
<option value="VOLUME_DOWN">VOLUME_DOWN</option>
|
||||||
|
<option value="BRIGHTNESS_UP">BRIGHTNESS_UP</option>
|
||||||
|
<option value="BRIGHTNESS_DOWN">BRIGHTNESS_DOWN</option>
|
||||||
|
<option value="MONITOR_OFF">MONITOR_OFF</option>
|
||||||
|
<option value="MONITOR_ON">MONITOR_ON</option>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<option value="ESCAPE">ESCAPE</option>
|
||||||
|
<option value="SPACE">SPACE</option>
|
||||||
|
<option value="BACK">BACK</option>
|
||||||
|
<option value="LWIN">LWIN</option>
|
||||||
|
<option value="CONTROL">CONTROL</option>
|
||||||
|
<option value="TAB">TAB</option>
|
||||||
|
<option value="MENU">MENU</option>
|
||||||
|
<option value="RETURN">RETURN</option>
|
||||||
|
<option value="UP">UP</option>
|
||||||
|
<option value="DOWN">DOWN</option>
|
||||||
|
<option value="LEFT">LEFT</option>
|
||||||
|
<option value="RIGHT">RIGHT</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -278,6 +278,7 @@
|
|||||||
<module>org.openhab.binding.tplinksmarthome</module>
|
<module>org.openhab.binding.tplinksmarthome</module>
|
||||||
<module>org.openhab.binding.tradfri</module>
|
<module>org.openhab.binding.tradfri</module>
|
||||||
<module>org.openhab.binding.unifi</module>
|
<module>org.openhab.binding.unifi</module>
|
||||||
|
<module>org.openhab.binding.unifiedremote</module>
|
||||||
<module>org.openhab.binding.upnpcontrol</module>
|
<module>org.openhab.binding.upnpcontrol</module>
|
||||||
<module>org.openhab.binding.upb</module>
|
<module>org.openhab.binding.upb</module>
|
||||||
<module>org.openhab.binding.urtsi</module>
|
<module>org.openhab.binding.urtsi</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user