[remoteopenhab] Introduce things for the remote things and add support for HTTPS (#8909)
* [remoteopenhab] Introduce things for the remote things Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Adapt code to change in core Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: scheduler.execute Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Fixed tables in README Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: ThingHandlerService Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Add support for HTTPS communication Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Few changes to satisfy build Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Avoid SSE timeout exception after one minute when no event received Fix #8977 Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Check connection job: do nothing when the thing was ONLINE and the REST API is still reachable Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: super.deactivate Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comments: README Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: buildChannels parameter renamed Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Imrpoved logging when the REST API is failing Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: fasle Signed-off-by: Laurent Garnier <lg.hc@free.fr> * Review comment: deprecated "required" tag for channel parameter Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
d055c057d7
commit
07abbafadc
|
@ -3,11 +3,13 @@
|
|||
The Remote openHAB binding allows to communicate with remote openHAB servers.
|
||||
The communication is bidirectional.
|
||||
The binding on the local server listens to any item state updates on the remote server and updates accordingly the linked channel on the local server.
|
||||
It also transfers any item command from the local server to the remote server.
|
||||
It transfers any item command from the local server to the remote server.
|
||||
It can map any remote thing to a local thing.
|
||||
Through this mapping, in your rules (local server), you can take actions based upon status updates or status changes generated by remote things and you can take actions based upon trigger events generated by the trigger channels defined in the remote thing.
|
||||
|
||||
One first usage is the distribution of your home automation system on a set of openHAB servers.
|
||||
One use of this binding is to distribute your home automation system on multiple openHAB servers.
|
||||
|
||||
A second usage is for users having old openHAB v1 bindings running that were not migrated to openHAB v2 or openHAB v3.
|
||||
Another use is for users to interact with older versions of openHAB that may support old openHAB v1 bindings that were not migrated to openHAB v2 or openHAB v3.
|
||||
They can keep an openHAB v2 server to run their old openHAB v1 bindings and setup a new openHAB v3 server for everything else.
|
||||
The Remote openHAB binding installed on the openHAB v3 server will then allow to use the openHAB v1 bindings through communication with the openHAB v2 server.
|
||||
|
||||
|
@ -15,7 +17,7 @@ A third usage is for users that would like to keep unchanged an existing openHAB
|
|||
|
||||
## Supported Things
|
||||
|
||||
There is one unique supported thing : the `server` bridge thing
|
||||
There is two supported things : the `server` bridge thing representing a remote openHAB server and the `thing` thing representing a thing from the remote openHAB server.
|
||||
|
||||
## Discovery
|
||||
|
||||
|
@ -24,39 +26,75 @@ You will find in the inbox one discovery thing per remote server interface.
|
|||
So if your remote server has one IPv4 address and one IPv6 address, you will discover two things in the inbox.
|
||||
Just choose one of the two things.
|
||||
|
||||
Once a bridge thing representing a remote openHAB server is created, all things from this remote server will be discovered when you scan for new things.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration options, all configuration is done at Thing level.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The thing has the following configuration parameters:
|
||||
The `server` thing has the following configuration parameters:
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| host | yes | The host name or IP address of the remote openHAB server. |
|
||||
| port | yes | The HTTP port to be used to communicate with the remote openHAB server. Default is 8080. |
|
||||
| restPath | yes | The subpath of the REST API on the remote openHAB server. Default is /rest |
|
||||
| token | no | The token to use when the remote openHAB server is setup to require authorization to run its REST API. |
|
||||
| Parameter | Required | Description |
|
||||
|--------------------|----------|-----------------------------------------------------------------------------------------------------------|
|
||||
| host | yes | The host name or IP address of the remote openHAB server. |
|
||||
| useHttps | no | Set to true if you want to use HTTPS to communicate with the remote openHAB server. Default is false. |
|
||||
| port | yes | The HTTP port to use to communicate with the remote openHAB server. Default is 8080. |
|
||||
| trustedCertificate | no | Set to true if you want to use HTTPS even without a valid SSL certificate provided by your remote server. |
|
||||
| restPath | yes | The subpath of the REST API on the remote openHAB server. Default is /rest |
|
||||
| token | no | The token to use when the remote openHAB server is setup to require authorization to run its REST API. |
|
||||
|
||||
The `thing` thing has the following configuration parameters:
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|----------------------|----------|---------------------------------------------|
|
||||
| thingUID | yes | The thing UID in the remote openHAB server. |
|
||||
| buildTriggerChannels | no | If set to true, a trigger channel will be automatically created and linked to each trigger channel from the remote thing. Default is true. |
|
||||
|
||||
Please note that if your remote server is an openHAB v3 server, you will need to define a valid token on your bridge thing to have your things correctly initialized.
|
||||
|
||||
Setting the `buildTriggerChannels` parameter to false is for the main following advanced usages :
|
||||
|
||||
* you don't care about the trigger channels of this remote thing and you don't want the binding to create them locally,
|
||||
* you want to define the trigger channels in your configuration file, and only the channels that you will finally need,
|
||||
* you want to set a specific channel ID rather than using the channel ID created by the binding.
|
||||
|
||||
## Thing Status
|
||||
|
||||
The status of any `thing` thing is a mapping of the remote thing status.
|
||||
A mapping is done only when the `server` bridge is ONLINE (meaning the local server is connected to the remote server).
|
||||
Please note that every remote status other than UNKNOWN, ONLINE and OFFLINE will then be considered as OFFLINE on the local server.
|
||||
|
||||
## Channels
|
||||
|
||||
The channels are built dynamically and automatically by the binding.
|
||||
One channel is created for each item from the remote server.
|
||||
Only basic groups (with no state) are ignored.
|
||||
The channel id of the built channel corresponds to the name of the item on the remote server.
|
||||
Channels are built dynamically and automatically by the binding.
|
||||
|
||||
On the `server` thing, a channel is created automatically for each item defined in the remote server.
|
||||
Only basic groups (with no state) from the remote server are ignored.
|
||||
The channel ID of the created channel corresponds to the name of the item on the remote server.
|
||||
For example, if your remote item is named `MyDate`, the channel UID of the channel created by the binding will be `remoteopenhab:server:xxx:MyDate`.
|
||||
|
||||
On the `thing` thing, if the `buildTriggerChannels` parameter is set to true, a channel is created automatically for each trigger channel defined in the remote thing.
|
||||
For example, if your remote thing provides a trigger channel with this UID `astro:sun:local:night#event`, the channel UID of the channel created by the binding will be `remoteopenhab:thing:xxx:astro_sun_local_night_event`.
|
||||
|
||||
## Limitations
|
||||
|
||||
* The binding will not try to communicate with an openHAB v1 server.
|
||||
* The binding only uses the HTTP protocol for the communications with the remote server (not HTTPS).
|
||||
|
||||
## Example
|
||||
|
||||
### demo.things:
|
||||
|
||||
```
|
||||
Bridge remoteopenhab:server:oh2 "OH2 server" [ host="192.168.0.100" ]
|
||||
Bridge remoteopenhab:server:oh2 "OH2 server" [ host="192.168.0.100", port=8443, useHttps=true, trustedCertificate=true ] {
|
||||
Thing thing tv "TV living room" [ thingUID="lgwebos:WebOSTV:tv" ]
|
||||
Thing thing astroSun "Astro sun" [ thingUID="astro:sun:local", buildTriggerChannels=false ] {
|
||||
Channels:
|
||||
Type trigger : nightEvent "Night Event" [ channelUID="astro:sun:local:night#event" ]
|
||||
}
|
||||
Thing thing astroMoon "Astro moon" [ thingUID="astro:moon:local" ]
|
||||
}
|
||||
```
|
||||
|
||||
### demo.items:
|
||||
|
|
|
@ -31,6 +31,14 @@ public class RemoteopenhabBindingConstants {
|
|||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_TYPE_SERVER = new ThingTypeUID(BINDING_ID, "server");
|
||||
public static final ThingTypeUID THING_TYPE_THING = new ThingTypeUID(BINDING_ID, "thing");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(BRIDGE_TYPE_SERVER);
|
||||
// All supported Bridge types
|
||||
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_TYPES_UIDS = Collections.singleton(BRIDGE_TYPE_SERVER);
|
||||
|
||||
// All supported Thing types
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_THING);
|
||||
|
||||
// List of all channel types
|
||||
public static final String CHANNEL_TYPE_TRIGGER = "trigger";
|
||||
}
|
||||
|
|
|
@ -12,23 +12,42 @@
|
|||
*/
|
||||
package org.openhab.binding.remoteopenhab.internal;
|
||||
|
||||
import static org.openhab.binding.remoteopenhab.internal.RemoteopenhabBindingConstants.*;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.remoteopenhab.internal.handler.RemoteopenhabBridgeHandler;
|
||||
import org.openhab.binding.remoteopenhab.internal.handler.RemoteopenhabThingHandler;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
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.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
|
@ -44,22 +63,103 @@ import com.google.gson.GsonBuilder;
|
|||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.remoteopenhab")
|
||||
public class RemoteopenhabHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.concat(RemoteopenhabBindingConstants.SUPPORTED_BRIDGE_TYPES_UIDS.stream(),
|
||||
RemoteopenhabBindingConstants.SUPPORTED_THING_TYPES_UIDS.stream())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteopenhabHandlerFactory.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final SseEventSourceFactory eventSourceFactory;
|
||||
private final RemoteopenhabChannelTypeProvider channelTypeProvider;
|
||||
private final RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
private final Gson jsonParser;
|
||||
|
||||
private HttpClient httpClientTrustingCert;
|
||||
|
||||
@Activate
|
||||
public RemoteopenhabHandlerFactory(final @Reference ClientBuilder clientBuilder,
|
||||
final @Reference SseEventSourceFactory eventSourceFactory,
|
||||
public RemoteopenhabHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference ClientBuilder clientBuilder, final @Reference SseEventSourceFactory eventSourceFactory,
|
||||
final @Reference RemoteopenhabChannelTypeProvider channelTypeProvider,
|
||||
final @Reference RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.httpClientTrustingCert = httpClientFactory.createHttpClient(RemoteopenhabBindingConstants.BINDING_ID);
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.eventSourceFactory = eventSourceFactory;
|
||||
this.channelTypeProvider = channelTypeProvider;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
jsonParser = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
|
||||
this.jsonParser = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
|
||||
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
|
||||
TrustManager[] trustAllCerts = new TrustManager[] { new X509ExtendedTrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType)
|
||||
throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType)
|
||||
throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate @Nullable [] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
|
||||
@Nullable Socket socket) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
|
||||
@Nullable Socket socket) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
|
||||
@Nullable SSLEngine engine) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
|
||||
@Nullable SSLEngine engine) throws CertificateException {
|
||||
}
|
||||
} };
|
||||
sslContext.init(null, trustAllCerts, null);
|
||||
|
||||
this.httpClientTrustingCert.getSslContextFactory().setSslContext(sslContext);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.warn("An exception occurred while requesting the SSL encryption algorithm : '{}'", e.getMessage(),
|
||||
e);
|
||||
} catch (KeyManagementException e) {
|
||||
logger.warn("An exception occurred while initialising the SSL context : '{}'", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate(ComponentContext componentContext) {
|
||||
super.activate(componentContext);
|
||||
try {
|
||||
httpClientTrustingCert.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to start Jetty HttpClient", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate(ComponentContext componentContext) {
|
||||
try {
|
||||
httpClientTrustingCert.stop();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to stop Jetty HttpClient", e);
|
||||
}
|
||||
super.deactivate(componentContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,16 +170,36 @@ public class RemoteopenhabHandlerFactory extends BaseThingHandlerFactory {
|
|||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||
if (thingTypeUID.equals(RemoteopenhabBindingConstants.BRIDGE_TYPE_SERVER)) {
|
||||
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
||||
} else if (RemoteopenhabBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
ThingUID newThingUID;
|
||||
if (bridgeUID != null && thingUID != null) {
|
||||
newThingUID = new ThingUID(thingTypeUID, bridgeUID, thingUID.getId());
|
||||
} else {
|
||||
newThingUID = thingUID;
|
||||
}
|
||||
return super.createThing(thingTypeUID, configuration, newThingUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"The thing type " + thingTypeUID + " is not supported by the remote openHAB binding.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a handler for the specific thing.
|
||||
*/
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
return BRIDGE_TYPE_SERVER.equals(thingTypeUID)
|
||||
? new RemoteopenhabBridgeHandler((Bridge) thing, clientBuilder, eventSourceFactory, channelTypeProvider,
|
||||
stateDescriptionProvider, jsonParser)
|
||||
: null;
|
||||
if (thingTypeUID.equals(RemoteopenhabBindingConstants.BRIDGE_TYPE_SERVER)) {
|
||||
return new RemoteopenhabBridgeHandler((Bridge) thing, httpClient, httpClientTrustingCert, clientBuilder,
|
||||
eventSourceFactory, channelTypeProvider, stateDescriptionProvider, jsonParser);
|
||||
} else if (RemoteopenhabBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new RemoteopenhabThingHandler(thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,20 +15,22 @@ package org.openhab.binding.remoteopenhab.internal.config;
|
|||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RemoteopenhabInstanceConfiguration} is responsible for holding
|
||||
* The {@link RemoteopenhabServerConfiguration} is responsible for holding
|
||||
* configuration informations associated to a remote openHAB server
|
||||
* thing type
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabInstanceConfiguration {
|
||||
public class RemoteopenhabServerConfiguration {
|
||||
public static final String HOST = "host";
|
||||
public static final String PORT = "port";
|
||||
public static final String REST_PATH = "restPath";
|
||||
|
||||
public String host = "";
|
||||
public boolean useHttps = false;
|
||||
public int port = 8080;
|
||||
public boolean trustedCertificate = false;
|
||||
public String restPath = "/rest";
|
||||
public String token = "";
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RemoteopenhabThingConfiguration} is responsible for holding
|
||||
* configuration informations associated to a remote openHAB thing
|
||||
* thing type
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabThingConfiguration {
|
||||
public static final String THING_UID = "thingUID";
|
||||
|
||||
public String thingUID = "";
|
||||
public boolean buildTriggerChannels = true;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RemoteopenhabTriggerChannelConfiguration} is responsible for holding
|
||||
* configuration informations associated to a remote openHAB trigger channel
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabTriggerChannelConfiguration {
|
||||
public static final String CHANNEL_UID = "channelUID";
|
||||
|
||||
public String channelUID = "";
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.data;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Part of {@link RemoteopenhabThing} containing the channel definition
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabChannel {
|
||||
|
||||
public String uid = "";
|
||||
public String kind = "";
|
||||
public String label = "";
|
||||
public String description = "";
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.data;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Payload from ChannelTriggerEvent events received through the SSE connection.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabChannelTriggerEvent {
|
||||
|
||||
public String channel = "";
|
||||
public @Nullable String event;
|
||||
}
|
|
@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Event {
|
||||
public class RemoteopenhabEvent {
|
||||
|
||||
public String type = "";
|
||||
public String topic = "";
|
|
@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventPayload {
|
||||
public class RemoteopenhabEventPayload {
|
||||
|
||||
public String type = "";
|
||||
public String value = "";
|
|
@ -22,11 +22,11 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Item {
|
||||
public class RemoteopenhabItem {
|
||||
|
||||
public String name = "";
|
||||
public String type = "";
|
||||
public String state = "";
|
||||
public String groupType = "";
|
||||
public @Nullable StateDescription stateDescription;
|
||||
public @Nullable RemoteopenhabStateDescription stateDescription;
|
||||
}
|
|
@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class RestApi {
|
||||
public class RemoteopenhabRestApi {
|
||||
|
||||
public String version;
|
||||
public RestApiEndpoint[] links;
|
||||
public @Nullable RuntimeInfo runtimeInfo;
|
||||
public RemoteopenhabRestApiEndpoint[] links;
|
||||
public @Nullable RemoteopenhabRuntimeInfo runtimeInfo;
|
||||
}
|
|
@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RestApiEndpoint {
|
||||
public class RemoteopenhabRestApiEndpoint {
|
||||
|
||||
public String type = "";
|
||||
public String url = "";
|
|
@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RuntimeInfo {
|
||||
public class RemoteopenhabRuntimeInfo {
|
||||
|
||||
public String version = "";
|
||||
public String buildString = "";
|
|
@ -18,14 +18,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Part of {@link Item} containing the state description
|
||||
* Part of {@link RemoteopenhabItem} containing the state description
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StateDescription {
|
||||
public class RemoteopenhabStateDescription {
|
||||
|
||||
public String pattern = "";
|
||||
public boolean readOnly;
|
||||
public @Nullable List<Option> options;
|
||||
public @Nullable List<RemoteopenhabStateOption> options;
|
||||
}
|
|
@ -15,12 +15,12 @@ package org.openhab.binding.remoteopenhab.internal.data;
|
|||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Part of {@link StateDescription} containing one state option
|
||||
* Part of {@link RemoteopenhabStateDescription} containing one state option
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Option {
|
||||
public class RemoteopenhabStateOption {
|
||||
|
||||
public String value = "";
|
||||
public String label = "";
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.data;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Response to the API GET /rest/things/{uid}/status
|
||||
* Also payload from ThingStatusInfoChangedEvent events received through the SSE connection.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabStatusInfo {
|
||||
|
||||
public String status = "";
|
||||
public String statusDetail = "";
|
||||
public String description = "";
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Response to the API GET /rest/things
|
||||
* Also payload from ThingAddedEvent / ThingRemovedEvent events received through the SSE connection.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabThing {
|
||||
|
||||
public String label = "";
|
||||
@SerializedName("UID")
|
||||
public String uid = "";
|
||||
public @Nullable RemoteopenhabStatusInfo statusInfo;
|
||||
public List<RemoteopenhabChannel> channels = new ArrayList<>();
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
package org.openhab.binding.remoteopenhab.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.remoteopenhab.internal.RemoteopenhabBindingConstants.*;
|
||||
import static org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabInstanceConfiguration.*;
|
||||
import static org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabServerConfiguration.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -50,7 +50,7 @@ public class RemoteopenhabDiscoveryParticipant implements MDNSDiscoveryParticipa
|
|||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
return SUPPORTED_BRIDGE_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -89,7 +89,7 @@ public class RemoteopenhabDiscoveryParticipant implements MDNSDiscoveryParticipa
|
|||
ThingUID thingUID = getThingUID(service);
|
||||
if (thingUID != null && ip != null && restPath != null) {
|
||||
String label = "openHAB server";
|
||||
logger.debug("Created a DiscoveryResult for remote openHAB server {} with IP {}", thingUID, ip);
|
||||
logger.debug("Create a DiscoveryResult for remote openHAB server {} with IP {}", thingUID, ip);
|
||||
Map<String, Object> properties = Map.of(HOST, ip, REST_PATH, restPath);
|
||||
result = DiscoveryResultBuilder.create(thingUID).withProperties(properties).withRepresentationProperty(HOST)
|
||||
.withLabel(label).build();
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabThingConfiguration.THING_UID;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabBindingConstants;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStatusInfo;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabThing;
|
||||
import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
|
||||
import org.openhab.binding.remoteopenhab.internal.handler.RemoteopenhabBridgeHandler;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabThingsDataListener;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RemoteopenhabDiscoveryService} is responsible for discovering all the remote things
|
||||
* available in the remote openHAB server.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabDiscoveryService extends AbstractDiscoveryService
|
||||
implements ThingHandlerService, RemoteopenhabThingsDataListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteopenhabDiscoveryService.class);
|
||||
|
||||
private static final int SEARCH_TIME = 10;
|
||||
|
||||
private @NonNullByDefault({}) RemoteopenhabBridgeHandler bridgeHandler;
|
||||
private @NonNullByDefault({}) RemoteopenhabRestClient restClient;
|
||||
|
||||
public RemoteopenhabDiscoveryService() {
|
||||
super(RemoteopenhabBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof RemoteopenhabBridgeHandler) {
|
||||
this.bridgeHandler = (RemoteopenhabBridgeHandler) handler;
|
||||
this.restClient = bridgeHandler.gestRestClient();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
ThingHandlerService.super.activate();
|
||||
restClient.addThingsDataListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
restClient.removeThingsDataListener(this);
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting discovery scan for remote things");
|
||||
if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
try {
|
||||
List<RemoteopenhabThing> things = restClient.getRemoteThings();
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
for (RemoteopenhabThing thing : things) {
|
||||
createDiscoveryResult(thing, bridgeUID);
|
||||
}
|
||||
} catch (RemoteopenhabException e) {
|
||||
logger.debug("{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan(), bridgeHandler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingStatusUpdated(String thingUID, RemoteopenhabStatusInfo statusInfo) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingAdded(RemoteopenhabThing thing) {
|
||||
createDiscoveryResult(thing, bridgeHandler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingRemoved(RemoteopenhabThing thing) {
|
||||
removeDiscoveryResult(thing, bridgeHandler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelTriggered(String channelUID, @Nullable String event) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
private void createDiscoveryResult(RemoteopenhabThing thing, ThingUID bridgeUID) {
|
||||
ThingUID thingUID = buildThingUID(thing, bridgeUID);
|
||||
logger.debug("Create a DiscoveryResult for remote openHAB thing {} with thingUID setting {}", thingUID,
|
||||
thing.uid);
|
||||
Map<String, Object> properties = Map.of(THING_UID, thing.uid);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
|
||||
.withRepresentationProperty(THING_UID).withBridge(bridgeUID).withLabel(thing.label).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
private void removeDiscoveryResult(RemoteopenhabThing thing, ThingUID bridgeUID) {
|
||||
ThingUID thingUID = buildThingUID(thing, bridgeUID);
|
||||
logger.debug("Remove a DiscoveryResult for remote openHAB thing {} with thingUID setting {}", thingUID,
|
||||
thing.uid);
|
||||
thingRemoved(thingUID);
|
||||
}
|
||||
|
||||
private ThingUID buildThingUID(RemoteopenhabThing thing, ThingUID bridgeUID) {
|
||||
return new ThingUID(RemoteopenhabBindingConstants.THING_TYPE_THING, bridgeUID,
|
||||
thing.uid.replaceAll("[^A-Za-z0-9_]", "_"));
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
|
|||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
@ -30,13 +31,16 @@ import javax.ws.rs.client.ClientBuilder;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabChannelTypeProvider;
|
||||
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabStateDescriptionOptionProvider;
|
||||
import org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabInstanceConfiguration;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.Item;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.Option;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.StateDescription;
|
||||
import org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabServerConfiguration;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateDescription;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateOption;
|
||||
import org.openhab.binding.remoteopenhab.internal.discovery.RemoteopenhabDiscoveryService;
|
||||
import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabItemsDataListener;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabStreamingDataListener;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
|
@ -58,6 +62,7 @@ import org.openhab.core.thing.ChannelUID;
|
|||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
|
@ -85,7 +90,8 @@ import com.google.gson.Gson;
|
|||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements RemoteopenhabStreamingDataListener {
|
||||
public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
|
||||
implements RemoteopenhabStreamingDataListener, RemoteopenhabItemsDataListener {
|
||||
|
||||
private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
private static final DateTimeFormatter FORMATTER_DATE = DateTimeFormatter.ofPattern(DATE_FORMAT_PATTERN);
|
||||
|
@ -95,35 +101,33 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteopenhabBridgeHandler.class);
|
||||
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final SseEventSourceFactory eventSourceFactory;
|
||||
private final HttpClient httpClientTrustingCert;
|
||||
private final RemoteopenhabChannelTypeProvider channelTypeProvider;
|
||||
private final RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
private final Gson jsonParser;
|
||||
|
||||
private final Object updateThingLock = new Object();
|
||||
|
||||
private @NonNullByDefault({}) RemoteopenhabInstanceConfiguration config;
|
||||
private @NonNullByDefault({}) RemoteopenhabServerConfiguration config;
|
||||
|
||||
private @Nullable ScheduledFuture<?> checkConnectionJob;
|
||||
private @Nullable RemoteopenhabRestClient restClient;
|
||||
private RemoteopenhabRestClient restClient;
|
||||
|
||||
public RemoteopenhabBridgeHandler(Bridge bridge, ClientBuilder clientBuilder,
|
||||
SseEventSourceFactory eventSourceFactory, RemoteopenhabChannelTypeProvider channelTypeProvider,
|
||||
public RemoteopenhabBridgeHandler(Bridge bridge, HttpClient httpClient, HttpClient httpClientTrustingCert,
|
||||
ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
|
||||
RemoteopenhabChannelTypeProvider channelTypeProvider,
|
||||
RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider, final Gson jsonParser) {
|
||||
super(bridge);
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.eventSourceFactory = eventSourceFactory;
|
||||
this.httpClientTrustingCert = httpClientTrustingCert;
|
||||
this.channelTypeProvider = channelTypeProvider;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.jsonParser = jsonParser;
|
||||
this.restClient = new RemoteopenhabRestClient(httpClient, clientBuilder, eventSourceFactory, jsonParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing remote openHAB handler for bridge {}", getThing().getUID());
|
||||
|
||||
config = getConfigAs(RemoteopenhabInstanceConfiguration.class);
|
||||
config = getConfigAs(RemoteopenhabServerConfiguration.class);
|
||||
|
||||
String host = config.host.trim();
|
||||
if (host.length() == 0) {
|
||||
|
@ -147,7 +151,7 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
URL url;
|
||||
try {
|
||||
url = new URL("http", host, config.port, path);
|
||||
url = new URL(config.useHttps ? "https" : "http", host, config.port, path);
|
||||
} catch (MalformedURLException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Invalid REST URL built from the settings in the thing configuration");
|
||||
|
@ -160,13 +164,16 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
logger.debug("REST URL = {}", urlStr);
|
||||
|
||||
RemoteopenhabRestClient client = new RemoteopenhabRestClient(clientBuilder, eventSourceFactory, jsonParser,
|
||||
config.token, urlStr);
|
||||
restClient = client;
|
||||
restClient.setRestUrl(urlStr);
|
||||
restClient.setAccessToken(config.token);
|
||||
if (config.useHttps && config.trustedCertificate) {
|
||||
restClient.setHttpClient(httpClientTrustingCert);
|
||||
restClient.setTrustedCertificate(true);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
startCheckConnectionJob(client);
|
||||
startCheckConnectionJob();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -174,7 +181,6 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
logger.debug("Disposing remote openHAB handler for bridge {}", getThing().getUID());
|
||||
stopStreamingUpdates();
|
||||
stopCheckConnectionJob();
|
||||
this.restClient = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -182,17 +188,13 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
RemoteopenhabRestClient client = restClient;
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
String state = client.getRemoteItemState(channelUID.getId());
|
||||
String state = restClient.getRemoteItemState(channelUID.getId());
|
||||
updateChannelState(channelUID.getId(), null, state);
|
||||
} else if (isLinked(channelUID)) {
|
||||
client.sendCommandToRemoteItem(channelUID.getId(), command);
|
||||
restClient.sendCommandToRemoteItem(channelUID.getId(), command);
|
||||
String commandStr = command.toFullString();
|
||||
logger.debug("Sending command {} to remote item {} succeeded",
|
||||
commandStr.length() < MAX_STATE_SIZE_FOR_LOGGING ? commandStr
|
||||
|
@ -204,11 +206,11 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
}
|
||||
|
||||
private void createChannels(List<Item> items, boolean replace) {
|
||||
private void createChannels(List<RemoteopenhabItem> items, boolean replace) {
|
||||
synchronized (updateThingLock) {
|
||||
int nbGroups = 0;
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
for (Item item : items) {
|
||||
for (RemoteopenhabItem item : items) {
|
||||
String itemType = item.type;
|
||||
boolean readOnly = false;
|
||||
if ("Group".equals(itemType)) {
|
||||
|
@ -279,11 +281,11 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
}
|
||||
|
||||
private void removeChannels(List<Item> items) {
|
||||
private void removeChannels(List<RemoteopenhabItem> items) {
|
||||
synchronized (updateThingLock) {
|
||||
int nbRemoved = 0;
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
for (Item item : items) {
|
||||
for (RemoteopenhabItem item : items) {
|
||||
Channel channel = getThing().getChannel(item.name);
|
||||
if (channel != null) {
|
||||
thingBuilder.withoutChannel(channel.getUID());
|
||||
|
@ -298,14 +300,14 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
}
|
||||
|
||||
private void setStateOptions(List<Item> items) {
|
||||
for (Item item : items) {
|
||||
private void setStateOptions(List<RemoteopenhabItem> items) {
|
||||
for (RemoteopenhabItem item : items) {
|
||||
Channel channel = getThing().getChannel(item.name);
|
||||
StateDescription descr = item.stateDescription;
|
||||
List<Option> options = descr == null ? null : descr.options;
|
||||
RemoteopenhabStateDescription descr = item.stateDescription;
|
||||
List<RemoteopenhabStateOption> options = descr == null ? null : descr.options;
|
||||
if (channel != null && options != null && options.size() > 0) {
|
||||
List<StateOption> stateOptions = new ArrayList<>();
|
||||
for (Option option : options) {
|
||||
for (RemoteopenhabStateOption option : options) {
|
||||
stateOptions.add(new StateOption(option.value, option.label));
|
||||
}
|
||||
stateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions);
|
||||
|
@ -314,19 +316,19 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
}
|
||||
|
||||
public void checkConnection(RemoteopenhabRestClient client) {
|
||||
public void checkConnection() {
|
||||
logger.debug("Try the root REST API...");
|
||||
try {
|
||||
client.tryApi();
|
||||
if (client.getRestApiVersion() == null) {
|
||||
restClient.tryApi();
|
||||
if (restClient.getRestApiVersion() == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"OH 1.x server not supported by the binding");
|
||||
} else {
|
||||
List<Item> items = client.getRemoteItems();
|
||||
} else if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
List<RemoteopenhabItem> items = restClient.getRemoteItems();
|
||||
|
||||
createChannels(items, true);
|
||||
setStateOptions(items);
|
||||
for (Item item : items) {
|
||||
for (RemoteopenhabItem item : items) {
|
||||
updateChannelState(item.name, null, item.state);
|
||||
}
|
||||
|
||||
|
@ -335,20 +337,21 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
restartStreamingUpdates();
|
||||
}
|
||||
} catch (RemoteopenhabException e) {
|
||||
logger.debug("{}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
stopStreamingUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
private void startCheckConnectionJob(RemoteopenhabRestClient client) {
|
||||
private void startCheckConnectionJob() {
|
||||
ScheduledFuture<?> localCheckConnectionJob = checkConnectionJob;
|
||||
if (localCheckConnectionJob == null || localCheckConnectionJob.isCancelled()) {
|
||||
checkConnectionJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
long millisSinceLastEvent = System.currentTimeMillis() - client.getLastEventTimestamp();
|
||||
long millisSinceLastEvent = System.currentTimeMillis() - restClient.getLastEventTimestamp();
|
||||
if (millisSinceLastEvent > CONNECTION_TIMEOUT_MILLIS) {
|
||||
logger.debug("Check: Disconnected from streaming events, millisSinceLastEvent={}",
|
||||
logger.debug("Check: Maybe disconnected from streaming events, millisSinceLastEvent={}",
|
||||
millisSinceLastEvent);
|
||||
checkConnection(client);
|
||||
checkConnection();
|
||||
} else {
|
||||
logger.debug("Check: Receiving streaming events, millisSinceLastEvent={}", millisSinceLastEvent);
|
||||
}
|
||||
|
@ -365,35 +368,32 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
|
||||
private void restartStreamingUpdates() {
|
||||
RemoteopenhabRestClient client = restClient;
|
||||
if (client != null) {
|
||||
synchronized (client) {
|
||||
stopStreamingUpdates();
|
||||
startStreamingUpdates();
|
||||
}
|
||||
synchronized (restClient) {
|
||||
stopStreamingUpdates();
|
||||
startStreamingUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
private void startStreamingUpdates() {
|
||||
RemoteopenhabRestClient client = restClient;
|
||||
if (client != null) {
|
||||
synchronized (client) {
|
||||
client.addStreamingDataListener(this);
|
||||
client.start();
|
||||
}
|
||||
synchronized (restClient) {
|
||||
restClient.addStreamingDataListener(this);
|
||||
restClient.addItemsDataListener(this);
|
||||
restClient.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopStreamingUpdates() {
|
||||
RemoteopenhabRestClient client = restClient;
|
||||
if (client != null) {
|
||||
synchronized (client) {
|
||||
client.stop();
|
||||
client.removeStreamingDataListener(this);
|
||||
}
|
||||
synchronized (restClient) {
|
||||
restClient.stop();
|
||||
restClient.removeStreamingDataListener(this);
|
||||
restClient.removeItemsDataListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteopenhabRestClient gestRestClient() {
|
||||
return restClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected() {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
@ -410,17 +410,17 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onItemAdded(Item item) {
|
||||
public void onItemAdded(RemoteopenhabItem item) {
|
||||
createChannels(List.of(item), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRemoved(Item item) {
|
||||
public void onItemRemoved(RemoteopenhabItem item) {
|
||||
removeChannels(List.of(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemUpdated(Item newItem, Item oldItem) {
|
||||
public void onItemUpdated(RemoteopenhabItem newItem, RemoteopenhabItem oldItem) {
|
||||
if (!newItem.type.equals(oldItem.type)) {
|
||||
createChannels(List.of(newItem), false);
|
||||
} else {
|
||||
|
@ -550,7 +550,6 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
logger.debug("updateState {} with {}", channel.getUID(),
|
||||
channelStateStr.length() < MAX_STATE_SIZE_FOR_LOGGING ? channelStateStr
|
||||
: channelStateStr.substring(0, MAX_STATE_SIZE_FOR_LOGGING) + "...");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,4 +561,9 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler implements Rem
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(RemoteopenhabDiscoveryService.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.handler;
|
||||
|
||||
import static org.openhab.binding.remoteopenhab.internal.RemoteopenhabBindingConstants.*;
|
||||
import static org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabTriggerChannelConfiguration.CHANNEL_UID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabThingConfiguration;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabChannel;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStatusInfo;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabThing;
|
||||
import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabThingsDataListener;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RemoteopenhabThingHandler} is responsible for handling status updates associated to
|
||||
* any remote thing.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabThingHandler extends BaseThingHandler implements RemoteopenhabThingsDataListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteopenhabThingHandler.class);
|
||||
|
||||
private @Nullable RemoteopenhabRestClient restClient;
|
||||
|
||||
private @NonNullByDefault({}) RemoteopenhabThingConfiguration config;
|
||||
|
||||
public RemoteopenhabThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// No state channel defined for this thing type and so no command to handle
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("initializing remote openHAB handler for thing {}", getThing().getUID());
|
||||
Bridge bridge = getBridge();
|
||||
initializeThing(bridge != null ? bridge.getStatus() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
|
||||
initializeThing(bridgeStatusInfo.getStatus());
|
||||
}
|
||||
|
||||
private void initializeThing(@Nullable ThingStatus bridgeStatus) {
|
||||
Bridge bridge = getBridge();
|
||||
BridgeHandler bridgeHandler = bridge != null ? bridge.getHandler() : null;
|
||||
RemoteopenhabRestClient oldClient = this.restClient;
|
||||
if (oldClient != null) {
|
||||
oldClient.removeThingsDataListener(this);
|
||||
this.restClient = null;
|
||||
}
|
||||
if (bridgeHandler != null && bridgeStatus != null) {
|
||||
if (bridgeStatus == ThingStatus.ONLINE) {
|
||||
config = getConfigAs(RemoteopenhabThingConfiguration.class);
|
||||
|
||||
String uid = getConfigThingUID();
|
||||
if (uid.length() == 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Undefined thing UID setting in the thing configuration");
|
||||
} else {
|
||||
RemoteopenhabRestClient client = ((RemoteopenhabBridgeHandler) bridgeHandler).gestRestClient();
|
||||
client.addThingsDataListener(this);
|
||||
this.restClient = client;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
RemoteopenhabThing thing = client.getRemoteThing(uid);
|
||||
createTriggerChannels(thing, config.buildTriggerChannels);
|
||||
RemoteopenhabStatusInfo statusInfo = thing.statusInfo;
|
||||
if (statusInfo != null) {
|
||||
updateThingStatus(uid, statusInfo);
|
||||
}
|
||||
} catch (RemoteopenhabException e) {
|
||||
logger.debug("{}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing remote openHAB handler for thing {}", getThing().getUID());
|
||||
RemoteopenhabRestClient client = this.restClient;
|
||||
if (client != null) {
|
||||
client.removeThingsDataListener(this);
|
||||
this.restClient = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private String getConfigThingUID() {
|
||||
return config.thingUID.trim();
|
||||
}
|
||||
|
||||
private void createTriggerChannels(RemoteopenhabThing thing, boolean addNewChannels) {
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
for (RemoteopenhabChannel channelDTO : thing.channels) {
|
||||
if (!"TRIGGER".equals(channelDTO.kind)) {
|
||||
continue;
|
||||
}
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_TRIGGER);
|
||||
ChannelUID channelUID = new ChannelUID(getThing().getUID(),
|
||||
channelDTO.uid.replaceAll("[^A-Za-z0-9_]", "_"));
|
||||
Configuration channelConfig = new Configuration();
|
||||
channelConfig.put(CHANNEL_UID, channelDTO.uid);
|
||||
logger.trace("Create the channel {} of type {}", channelUID, channelTypeUID);
|
||||
channels.add(ChannelBuilder.create(channelUID, null).withType(channelTypeUID).withKind(ChannelKind.TRIGGER)
|
||||
.withLabel(channelDTO.label).withDescription(channelDTO.description)
|
||||
.withConfiguration(channelConfig).build());
|
||||
}
|
||||
if (channels.size() > 0) {
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
int nbRemoved = 0;
|
||||
for (Channel channel : channels) {
|
||||
if (getThing().getChannel(channel.getUID()) != null) {
|
||||
thingBuilder.withoutChannel(channel.getUID());
|
||||
nbRemoved++;
|
||||
}
|
||||
}
|
||||
if (nbRemoved > 0) {
|
||||
logger.debug("{} trigger channels removed for the thing {}", nbRemoved, getThing().getUID());
|
||||
}
|
||||
int nbAdded = 0;
|
||||
if (addNewChannels) {
|
||||
for (Channel channel : channels) {
|
||||
thingBuilder.withChannel(channel);
|
||||
}
|
||||
nbAdded = channels.size();
|
||||
logger.debug("{} trigger channels added for the thing {}", nbAdded, getThing().getUID());
|
||||
}
|
||||
if (nbRemoved > 0 || nbAdded > 0) {
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingStatusUpdated(String thingUID, RemoteopenhabStatusInfo statusInfo) {
|
||||
if (thingUID.equals(getConfigThingUID())) {
|
||||
updateThingStatus(thingUID, statusInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingAdded(RemoteopenhabThing thing) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThingRemoved(RemoteopenhabThing thing) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelTriggered(String channelUID, @Nullable String event) {
|
||||
String thingUID = channelUID.substring(0, channelUID.lastIndexOf(":"));
|
||||
if (thingUID.equals(getConfigThingUID())) {
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
if (channel.getKind() == ChannelKind.TRIGGER
|
||||
&& channelUID.equals(channel.getConfiguration().get(CHANNEL_UID))) {
|
||||
if (event == null) {
|
||||
triggerChannel(channel.getUID());
|
||||
logger.debug("triggerChannel {}", channel.getUID());
|
||||
} else {
|
||||
triggerChannel(channel.getUID(), event);
|
||||
logger.debug("triggerChannel {} with event {}", channel.getUID(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateThingStatus(String thingUID, RemoteopenhabStatusInfo statusInfo) {
|
||||
ThingStatus status = ThingStatus.valueOf(statusInfo.status);
|
||||
// All remote status different from UNKNOWN or ONLINE or OFFLINE is considered as OFFLINE
|
||||
if (status != ThingStatus.UNKNOWN && status != ThingStatus.ONLINE && status != ThingStatus.OFFLINE) {
|
||||
status = ThingStatus.OFFLINE;
|
||||
}
|
||||
ThingStatusDetail detail = ThingStatusDetail.valueOf(statusInfo.statusDetail);
|
||||
updateStatus(status, detail, statusInfo.description);
|
||||
logger.debug("updateStatus {} with status {} detail {} description {}", thingUID, status, detail,
|
||||
statusInfo.description);
|
||||
}
|
||||
}
|
|
@ -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.remoteopenhab.internal.listener;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
|
||||
/**
|
||||
* Interface for listeners of events relative to items generated by the {@link RemoteopenhabRestClient}.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RemoteopenhabItemsDataListener {
|
||||
|
||||
/**
|
||||
* A new ItemStateEvent was published.
|
||||
*/
|
||||
void onItemStateEvent(String itemName, String stateType, String state);
|
||||
|
||||
/**
|
||||
* A new ItemAddedEvent was published.
|
||||
*/
|
||||
void onItemAdded(RemoteopenhabItem item);
|
||||
|
||||
/**
|
||||
* A new ItemRemovedEvent was published.
|
||||
*/
|
||||
void onItemRemoved(RemoteopenhabItem item);
|
||||
|
||||
/**
|
||||
* A new ItemUpdatedEvent was published.
|
||||
*/
|
||||
void onItemUpdated(RemoteopenhabItem newItem, RemoteopenhabItem oldItem);
|
||||
}
|
|
@ -13,11 +13,10 @@
|
|||
package org.openhab.binding.remoteopenhab.internal.listener;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.Item;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
|
||||
/**
|
||||
* Interface for listeners of events generated by the {@link RemoteopenhabRestClient}.
|
||||
* Interface for listeners of general operating events generated by the {@link RemoteopenhabRestClient}.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
|
@ -33,24 +32,4 @@ public interface RemoteopenhabStreamingDataListener {
|
|||
* An error message was published.
|
||||
*/
|
||||
void onError(String message);
|
||||
|
||||
/**
|
||||
* A new ItemStateEvent was published.
|
||||
*/
|
||||
void onItemStateEvent(String itemName, String stateType, String state);
|
||||
|
||||
/**
|
||||
* A new ItemAddedEvent was published.
|
||||
*/
|
||||
void onItemAdded(Item item);
|
||||
|
||||
/**
|
||||
* A new ItemRemovedEvent was published.
|
||||
*/
|
||||
void onItemRemoved(Item item);
|
||||
|
||||
/**
|
||||
* A new ItemUpdatedEvent was published.
|
||||
*/
|
||||
void onItemUpdated(Item newItem, Item oldItem);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* 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.remoteopenhab.internal.listener;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStatusInfo;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabThing;
|
||||
import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
|
||||
|
||||
/**
|
||||
* Interface for listeners of events relative to things generated by the {@link RemoteopenhabRestClient}.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RemoteopenhabThingsDataListener {
|
||||
|
||||
/**
|
||||
* A new ThingStatusInfoChangedEvent was published.
|
||||
*/
|
||||
void onThingStatusUpdated(String thingUID, RemoteopenhabStatusInfo statusInfo);
|
||||
|
||||
/**
|
||||
* A new ThingAddedEvent was published.
|
||||
*/
|
||||
void onThingAdded(RemoteopenhabThing thing);
|
||||
|
||||
/**
|
||||
* A new ThingRemovedEvent was published.
|
||||
*/
|
||||
void onThingRemoved(RemoteopenhabThing thing);
|
||||
|
||||
/**
|
||||
* A new ChannelTriggeredEvent was published.
|
||||
*/
|
||||
void onChannelTriggered(String channelUID, @Nullable String event);
|
||||
}
|
|
@ -17,12 +17,15 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
@ -31,13 +34,23 @@ import javax.ws.rs.sse.SseEventSource;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.Event;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.EventPayload;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.Item;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RestApi;
|
||||
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.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabChannelTriggerEvent;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabEvent;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabEventPayload;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabRestApi;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStatusInfo;
|
||||
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabThing;
|
||||
import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabItemsDataListener;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabStreamingDataListener;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabThingsDataListener;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -62,109 +75,140 @@ public class RemoteopenhabRestClient {
|
|||
private final ClientBuilder clientBuilder;
|
||||
private final SseEventSourceFactory eventSourceFactory;
|
||||
private final Gson jsonParser;
|
||||
private String accessToken;
|
||||
private final String restUrl;
|
||||
|
||||
private final Object startStopLock = new Object();
|
||||
private final List<RemoteopenhabStreamingDataListener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final List<RemoteopenhabItemsDataListener> itemsListeners = new CopyOnWriteArrayList<>();
|
||||
private final List<RemoteopenhabThingsDataListener> thingsListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private HttpClient httpClient;
|
||||
private @Nullable String restUrl;
|
||||
private @Nullable String restApiVersion;
|
||||
private @Nullable String restApiItems;
|
||||
private @Nullable String restApiEvents;
|
||||
private Map<String, @Nullable String> apiEndPointsUrls = new HashMap<>();
|
||||
private @Nullable String topicNamespace;
|
||||
private String accessToken;
|
||||
private boolean trustedCertificate;
|
||||
private boolean connected;
|
||||
|
||||
private @Nullable SseEventSource eventSource;
|
||||
private long lastEventTimestamp;
|
||||
|
||||
public RemoteopenhabRestClient(final ClientBuilder clientBuilder, final SseEventSourceFactory eventSourceFactory,
|
||||
final Gson jsonParser, final String accessToken, final String restUrl) {
|
||||
public RemoteopenhabRestClient(final HttpClient httpClient, final ClientBuilder clientBuilder,
|
||||
final SseEventSourceFactory eventSourceFactory, final Gson jsonParser) {
|
||||
this.httpClient = httpClient;
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.eventSourceFactory = eventSourceFactory;
|
||||
this.jsonParser = jsonParser;
|
||||
this.accessToken = accessToken;
|
||||
this.accessToken = "";
|
||||
}
|
||||
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public String getRestUrl() throws RemoteopenhabException {
|
||||
String url = restUrl;
|
||||
if (url == null) {
|
||||
throw new RemoteopenhabException("REST client not correctly setup");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setRestUrl(String restUrl) {
|
||||
this.restUrl = restUrl;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public void setTrustedCertificate(boolean trustedCertificate) {
|
||||
this.trustedCertificate = trustedCertificate;
|
||||
}
|
||||
|
||||
public void tryApi() throws RemoteopenhabException {
|
||||
try {
|
||||
Properties httpHeaders = new Properties();
|
||||
if (!accessToken.isEmpty()) {
|
||||
httpHeaders.put(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
}
|
||||
httpHeaders.put(HttpHeaders.ACCEPT, "application/json");
|
||||
String jsonResponse = HttpUtil.executeUrl("GET", restUrl, httpHeaders, null, null, REQUEST_TIMEOUT);
|
||||
String jsonResponse = executeUrl(HttpMethod.GET, getRestUrl(), "application/json", null, null);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("Failed to execute the root REST API");
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
RestApi restApi = jsonParser.fromJson(jsonResponse, RestApi.class);
|
||||
RemoteopenhabRestApi restApi = jsonParser.fromJson(jsonResponse, RemoteopenhabRestApi.class);
|
||||
restApiVersion = restApi.version;
|
||||
logger.debug("REST API version = {}", restApiVersion);
|
||||
restApiItems = null;
|
||||
apiEndPointsUrls.clear();
|
||||
for (int i = 0; i < restApi.links.length; i++) {
|
||||
if ("items".equals(restApi.links[i].type)) {
|
||||
restApiItems = restApi.links[i].url;
|
||||
} else if ("events".equals(restApi.links[i].type)) {
|
||||
restApiEvents = restApi.links[i].url;
|
||||
}
|
||||
apiEndPointsUrls.put(restApi.links[i].type, restApi.links[i].url);
|
||||
}
|
||||
logger.debug("REST API items = {}", restApiItems);
|
||||
logger.debug("REST API events = {}", restApiEvents);
|
||||
logger.debug("REST API items = {}", apiEndPointsUrls.get("items"));
|
||||
logger.debug("REST API things = {}", apiEndPointsUrls.get("things"));
|
||||
logger.debug("REST API events = {}", apiEndPointsUrls.get("events"));
|
||||
topicNamespace = restApi.runtimeInfo != null ? "openhab" : "smarthome";
|
||||
logger.debug("topic namespace = {}", topicNamespace);
|
||||
} catch (RemoteopenhabException e) {
|
||||
throw new RemoteopenhabException(e.getMessage());
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException("Failed to parse the result of the root REST API", e);
|
||||
} catch (IOException e) {
|
||||
throw new RemoteopenhabException("Failed to execute the root REST API", e);
|
||||
} catch (RemoteopenhabException | JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException("Failed to execute the root REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Item> getRemoteItems() throws RemoteopenhabException {
|
||||
public List<RemoteopenhabItem> getRemoteItems() throws RemoteopenhabException {
|
||||
try {
|
||||
Properties httpHeaders = new Properties();
|
||||
if (!accessToken.isEmpty()) {
|
||||
httpHeaders.put(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
String url = String.format("%s?recursive=false", getRestApiUrl("items"));
|
||||
String jsonResponse = executeUrl(HttpMethod.GET, url, "application/json", null, null);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
httpHeaders.put(HttpHeaders.ACCEPT, "application/json");
|
||||
String url = String.format("%s?recursive=fasle", getRestApiItems());
|
||||
String jsonResponse = HttpUtil.executeUrl("GET", url, httpHeaders, null, null, REQUEST_TIMEOUT);
|
||||
return Arrays.asList(jsonParser.fromJson(jsonResponse, Item[].class));
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException("Failed to get the list of remote items using the items REST API", e);
|
||||
return Arrays.asList(jsonParser.fromJson(jsonResponse, RemoteopenhabItem[].class));
|
||||
} catch (RemoteopenhabException | JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException(
|
||||
"Failed to get the list of remote items using the items REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteItemState(String itemName) throws RemoteopenhabException {
|
||||
try {
|
||||
Properties httpHeaders = new Properties();
|
||||
if (!accessToken.isEmpty()) {
|
||||
httpHeaders.put(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
}
|
||||
httpHeaders.put(HttpHeaders.ACCEPT, "text/plain");
|
||||
String url = String.format("%s/%s/state", getRestApiItems(), itemName);
|
||||
return HttpUtil.executeUrl("GET", url, httpHeaders, null, null, REQUEST_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
throw new RemoteopenhabException(
|
||||
"Failed to get the state of remote item " + itemName + " using the items REST API", e);
|
||||
String url = String.format("%s/%s/state", getRestApiUrl("items"), itemName);
|
||||
return executeUrl(HttpMethod.GET, url, "text/plain", null, null);
|
||||
} catch (RemoteopenhabException e) {
|
||||
throw new RemoteopenhabException("Failed to get the state of remote item " + itemName
|
||||
+ " using the items REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommandToRemoteItem(String itemName, Command command) throws RemoteopenhabException {
|
||||
try {
|
||||
Properties httpHeaders = new Properties();
|
||||
if (!accessToken.isEmpty()) {
|
||||
httpHeaders.put(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
}
|
||||
httpHeaders.put(HttpHeaders.ACCEPT, "application/json");
|
||||
String url = String.format("%s/%s", getRestApiItems(), itemName);
|
||||
String url = String.format("%s/%s", getRestApiUrl("items"), itemName);
|
||||
InputStream stream = new ByteArrayInputStream(command.toFullString().getBytes(StandardCharsets.UTF_8));
|
||||
HttpUtil.executeUrl("POST", url, httpHeaders, stream, "text/plain", REQUEST_TIMEOUT);
|
||||
executeUrl(HttpMethod.POST, url, "application/json", stream, "text/plain");
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
} catch (RemoteopenhabException | IOException e) {
|
||||
throw new RemoteopenhabException("Failed to send command to the remote item " + itemName
|
||||
+ " using the items REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<RemoteopenhabThing> getRemoteThings() throws RemoteopenhabException {
|
||||
try {
|
||||
String jsonResponse = executeUrl(HttpMethod.GET, getRestApiUrl("things"), "application/json", null, null);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
return Arrays.asList(jsonParser.fromJson(jsonResponse, RemoteopenhabThing[].class));
|
||||
} catch (RemoteopenhabException | JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException(
|
||||
"Failed to send command to the remote item " + itemName + " using the items REST API", e);
|
||||
"Failed to get the list of remote things using the things REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteopenhabThing getRemoteThing(String uid) throws RemoteopenhabException {
|
||||
try {
|
||||
String url = String.format("%s/%s", getRestApiUrl("things"), uid);
|
||||
String jsonResponse = executeUrl(HttpMethod.GET, url, "application/json", null, null);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
return Objects.requireNonNull(jsonParser.fromJson(jsonResponse, RemoteopenhabThing.class));
|
||||
} catch (RemoteopenhabException | JsonSyntaxException e) {
|
||||
throw new RemoteopenhabException(
|
||||
"Failed to get the remote thing " + uid + " using the things REST API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,14 +216,9 @@ public class RemoteopenhabRestClient {
|
|||
return restApiVersion;
|
||||
}
|
||||
|
||||
public String getRestApiItems() {
|
||||
String url = restApiItems;
|
||||
return url != null ? url : restUrl + "/items";
|
||||
}
|
||||
|
||||
public String getRestApiEvents() {
|
||||
String url = restApiEvents;
|
||||
return url != null ? url : restUrl + "/events";
|
||||
private String getRestApiUrl(String endPoint) throws RemoteopenhabException {
|
||||
String url = apiEndPointsUrls.get(endPoint);
|
||||
return url != null ? url : getRestUrl() + "/" + endPoint;
|
||||
}
|
||||
|
||||
public String getTopicNamespace() {
|
||||
|
@ -189,7 +228,7 @@ public class RemoteopenhabRestClient {
|
|||
|
||||
public void start() {
|
||||
synchronized (startStopLock) {
|
||||
logger.debug("Opening EventSource {}", getItemsRestSseUrl());
|
||||
logger.debug("Opening EventSource");
|
||||
reopenEventSource();
|
||||
logger.debug("EventSource started");
|
||||
}
|
||||
|
@ -197,18 +236,29 @@ public class RemoteopenhabRestClient {
|
|||
|
||||
public void stop() {
|
||||
synchronized (startStopLock) {
|
||||
logger.debug("Closing EventSource {}", getItemsRestSseUrl());
|
||||
logger.debug("Closing EventSource");
|
||||
closeEventSource(0, TimeUnit.SECONDS);
|
||||
logger.debug("EventSource stopped");
|
||||
lastEventTimestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private String getItemsRestSseUrl() {
|
||||
return String.format("%s?topics=%s/items/*/*", getRestApiEvents(), getTopicNamespace());
|
||||
}
|
||||
|
||||
private SseEventSource createEventSource(String restSseUrl) {
|
||||
Client client = clientBuilder.register(new RemoteopenhabStreamingRequestFilter(accessToken)).build();
|
||||
Client client;
|
||||
// Avoid a timeout exception after 1 minute by setting the read timeout to 0 (infinite)
|
||||
if (trustedCertificate) {
|
||||
client = clientBuilder.sslContext(httpClient.getSslContextFactory().getSslContext())
|
||||
.hostnameVerifier(new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(@Nullable String hostname, @Nullable SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}).readTimeout(0, TimeUnit.SECONDS).register(new RemoteopenhabStreamingRequestFilter(accessToken))
|
||||
.build();
|
||||
} else {
|
||||
client = clientBuilder.readTimeout(0, TimeUnit.SECONDS)
|
||||
.register(new RemoteopenhabStreamingRequestFilter(accessToken)).build();
|
||||
}
|
||||
SseEventSource eventSource = eventSourceFactory.newSource(client.target(restSseUrl));
|
||||
eventSource.register(this::onEvent, this::onError);
|
||||
return eventSource;
|
||||
|
@ -216,10 +266,20 @@ public class RemoteopenhabRestClient {
|
|||
|
||||
private void reopenEventSource() {
|
||||
logger.debug("Reopening EventSource");
|
||||
|
||||
String url;
|
||||
try {
|
||||
url = String.format("%s?topics=%s/items/*/*,%s/things/*/*,%s/channels/*/triggered", getRestApiUrl("events"),
|
||||
getTopicNamespace(), getTopicNamespace(), getTopicNamespace());
|
||||
} catch (RemoteopenhabException e) {
|
||||
logger.debug("{}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
closeEventSource(10, TimeUnit.SECONDS);
|
||||
|
||||
logger.debug("Opening new EventSource {}", getItemsRestSseUrl());
|
||||
SseEventSource localEventSource = createEventSource(getItemsRestSseUrl());
|
||||
logger.debug("Opening new EventSource {}", url);
|
||||
SseEventSource localEventSource = createEventSource(url);
|
||||
localEventSource.open();
|
||||
|
||||
eventSource = localEventSource;
|
||||
|
@ -248,6 +308,22 @@ public class RemoteopenhabRestClient {
|
|||
return listeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean addItemsDataListener(RemoteopenhabItemsDataListener listener) {
|
||||
return itemsListeners.add(listener);
|
||||
}
|
||||
|
||||
public boolean removeItemsDataListener(RemoteopenhabItemsDataListener listener) {
|
||||
return itemsListeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean addThingsDataListener(RemoteopenhabThingsDataListener listener) {
|
||||
return thingsListeners.add(listener);
|
||||
}
|
||||
|
||||
public boolean removeThingsDataListener(RemoteopenhabThingsDataListener listener) {
|
||||
return thingsListeners.remove(listener);
|
||||
}
|
||||
|
||||
public long getLastEventTimestamp() {
|
||||
return lastEventTimestamp;
|
||||
}
|
||||
|
@ -270,43 +346,75 @@ public class RemoteopenhabRestClient {
|
|||
}
|
||||
|
||||
try {
|
||||
Event event = jsonParser.fromJson(data, Event.class);
|
||||
RemoteopenhabEvent event = jsonParser.fromJson(data, RemoteopenhabEvent.class);
|
||||
String itemName;
|
||||
EventPayload payload;
|
||||
Item item;
|
||||
String thingUID;
|
||||
RemoteopenhabEventPayload payload;
|
||||
RemoteopenhabItem item;
|
||||
RemoteopenhabThing thing;
|
||||
switch (event.type) {
|
||||
case "ItemStateEvent":
|
||||
itemName = extractItemNameFromTopic(event.topic, event.type, "state");
|
||||
payload = jsonParser.fromJson(event.payload, EventPayload.class);
|
||||
listeners.forEach(listener -> listener.onItemStateEvent(itemName, payload.type, payload.value));
|
||||
payload = jsonParser.fromJson(event.payload, RemoteopenhabEventPayload.class);
|
||||
itemsListeners
|
||||
.forEach(listener -> listener.onItemStateEvent(itemName, payload.type, payload.value));
|
||||
break;
|
||||
case "GroupItemStateChangedEvent":
|
||||
itemName = extractItemNameFromTopic(event.topic, event.type, "statechanged");
|
||||
payload = jsonParser.fromJson(event.payload, EventPayload.class);
|
||||
listeners.forEach(listener -> listener.onItemStateEvent(itemName, payload.type, payload.value));
|
||||
payload = jsonParser.fromJson(event.payload, RemoteopenhabEventPayload.class);
|
||||
itemsListeners
|
||||
.forEach(listener -> listener.onItemStateEvent(itemName, payload.type, payload.value));
|
||||
break;
|
||||
case "ItemAddedEvent":
|
||||
itemName = extractItemNameFromTopic(event.topic, event.type, "added");
|
||||
item = Objects.requireNonNull(jsonParser.fromJson(event.payload, Item.class));
|
||||
listeners.forEach(listener -> listener.onItemAdded(item));
|
||||
item = Objects.requireNonNull(jsonParser.fromJson(event.payload, RemoteopenhabItem.class));
|
||||
itemsListeners.forEach(listener -> listener.onItemAdded(item));
|
||||
break;
|
||||
case "ItemRemovedEvent":
|
||||
itemName = extractItemNameFromTopic(event.topic, event.type, "removed");
|
||||
item = Objects.requireNonNull(jsonParser.fromJson(event.payload, Item.class));
|
||||
listeners.forEach(listener -> listener.onItemRemoved(item));
|
||||
item = Objects.requireNonNull(jsonParser.fromJson(event.payload, RemoteopenhabItem.class));
|
||||
itemsListeners.forEach(listener -> listener.onItemRemoved(item));
|
||||
break;
|
||||
case "ItemUpdatedEvent":
|
||||
itemName = extractItemNameFromTopic(event.topic, event.type, "updated");
|
||||
Item[] updItem = jsonParser.fromJson(event.payload, Item[].class);
|
||||
RemoteopenhabItem[] updItem = jsonParser.fromJson(event.payload, RemoteopenhabItem[].class);
|
||||
if (updItem.length == 2) {
|
||||
listeners.forEach(listener -> listener.onItemUpdated(updItem[0], updItem[1]));
|
||||
itemsListeners.forEach(listener -> listener.onItemUpdated(updItem[0], updItem[1]));
|
||||
} else {
|
||||
logger.debug("Invalid payload for event type {} for topic {}", event.type, event.topic);
|
||||
}
|
||||
break;
|
||||
case "ThingStatusInfoChangedEvent":
|
||||
thingUID = extractThingUIDFromTopic(event.topic, event.type, "statuschanged");
|
||||
RemoteopenhabStatusInfo[] updStatus = jsonParser.fromJson(event.payload,
|
||||
RemoteopenhabStatusInfo[].class);
|
||||
if (updStatus.length == 2) {
|
||||
thingsListeners.forEach(listener -> listener.onThingStatusUpdated(thingUID, updStatus[0]));
|
||||
} else {
|
||||
logger.debug("Invalid payload for event type {} for topic {}", event.type, event.topic);
|
||||
}
|
||||
break;
|
||||
case "ThingAddedEvent":
|
||||
thingUID = extractThingUIDFromTopic(event.topic, event.type, "added");
|
||||
thing = Objects.requireNonNull(jsonParser.fromJson(event.payload, RemoteopenhabThing.class));
|
||||
thingsListeners.forEach(listener -> listener.onThingAdded(thing));
|
||||
break;
|
||||
case "ThingRemovedEvent":
|
||||
thingUID = extractThingUIDFromTopic(event.topic, event.type, "removed");
|
||||
thing = Objects.requireNonNull(jsonParser.fromJson(event.payload, RemoteopenhabThing.class));
|
||||
thingsListeners.forEach(listener -> listener.onThingRemoved(thing));
|
||||
break;
|
||||
case "ChannelTriggeredEvent":
|
||||
RemoteopenhabChannelTriggerEvent triggerEvent = jsonParser.fromJson(event.payload,
|
||||
RemoteopenhabChannelTriggerEvent.class);
|
||||
thingsListeners
|
||||
.forEach(listener -> listener.onChannelTriggered(triggerEvent.channel, triggerEvent.event));
|
||||
break;
|
||||
case "ItemStatePredictedEvent":
|
||||
case "ItemStateChangedEvent":
|
||||
case "ItemCommandEvent":
|
||||
case "ThingStatusInfoEvent":
|
||||
case "ThingUpdatedEvent":
|
||||
logger.trace("Ignored event type {} for topic {}", event.type, event.topic);
|
||||
break;
|
||||
default:
|
||||
|
@ -334,4 +442,48 @@ public class RemoteopenhabRestClient {
|
|||
}
|
||||
return parts[2];
|
||||
}
|
||||
|
||||
private String extractThingUIDFromTopic(String topic, String eventType, String finalPart)
|
||||
throws RemoteopenhabException {
|
||||
String[] parts = topic.split("/");
|
||||
int expectedNbParts = 4;
|
||||
if (parts.length != expectedNbParts || !getTopicNamespace().equals(parts[0]) || !"things".equals(parts[1])
|
||||
|| !finalPart.equals(parts[parts.length - 1])) {
|
||||
throw new RemoteopenhabException("Invalid event topic " + topic + " for event type " + eventType);
|
||||
}
|
||||
return parts[2];
|
||||
}
|
||||
|
||||
public String executeUrl(HttpMethod httpMethod, String url, String acceptHeader, @Nullable InputStream content,
|
||||
@Nullable String contentType) throws RemoteopenhabException {
|
||||
final Request request = httpClient.newRequest(url).method(httpMethod).timeout(REQUEST_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
request.header(HttpHeaders.ACCEPT, acceptHeader);
|
||||
if (!accessToken.isEmpty()) {
|
||||
request.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
}
|
||||
|
||||
if (content != null && (HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod))
|
||||
&& contentType != null) {
|
||||
try (final InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(
|
||||
content)) {
|
||||
request.content(inputStreamContentProvider, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ContentResponse response = request.send();
|
||||
int statusCode = response.getStatus();
|
||||
if (statusCode >= HttpStatus.BAD_REQUEST_400) {
|
||||
String statusLine = statusCode + " " + response.getReason();
|
||||
throw new RemoteopenhabException("HTTP call failed: " + statusLine);
|
||||
}
|
||||
String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim()
|
||||
: StandardCharsets.UTF_8.name();
|
||||
return new String(response.getContent(), encoding);
|
||||
} catch (Exception e) {
|
||||
throw new RemoteopenhabException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,32 @@
|
|||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="useHttps" type="boolean">
|
||||
<label>Use HTTPS</label>
|
||||
<description>Set it to true in case you want to use HTTPS to communicate with the remote openHAB server. Default is
|
||||
false.</description>
|
||||
<required>false</required>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="port" type="integer">
|
||||
<label>Server HTTP Port</label>
|
||||
<description>The HTTP port to be used to communicate with the remote openHAB server.</description>
|
||||
<description>The HTTP port to use to communicate with the remote openHAB server.</description>
|
||||
<required>true</required>
|
||||
<default>8080</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="trustedCertificate" type="boolean">
|
||||
<label>Trust SSL Certificate</label>
|
||||
<description>Set it to true in case you want to use HTTPS even without a valid SSL certificate provided by your
|
||||
remote server.</description>
|
||||
<required>false</required>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="restPath" type="text">
|
||||
<label>REST API Path</label>
|
||||
<description>The subpath of the REST API on the remote openHAB server.</description>
|
||||
|
@ -44,4 +62,43 @@
|
|||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="thing">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="server"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Remote Thing</label>
|
||||
<description>A thing from the remote openHAB server.</description>
|
||||
|
||||
<representation-property>thingUID</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="thingUID" type="text">
|
||||
<label>Remote Thing UID</label>
|
||||
<description>The thing UID in the remote openHAB server.</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="buildTriggerChannels" type="boolean">
|
||||
<label>Automatic Trigger Channels Building</label>
|
||||
<description>If set to true, a trigger channel will be automatically created and linked to each trigger channel from
|
||||
the remote thing.</description>
|
||||
<required>false</required>
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="trigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Trigger Channel</label>
|
||||
<config-description>
|
||||
<parameter name="channelUID" type="text" required="true">
|
||||
<label>Remote Channel UID</label>
|
||||
<description>The channel UID in the remote openHAB server.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
|
Loading…
Reference in New Issue