[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:
lolodomo
2020-11-15 10:59:38 +01:00
committed by GitHub
parent d055c057d7
commit 07abbafadc
27 changed files with 1249 additions and 232 deletions

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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 = "";
}

View File

@@ -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;
}

View File

@@ -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 = "";
}

View File

@@ -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 = "";
}

View File

@@ -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;
}

View File

@@ -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 = "";

View File

@@ -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 = "";

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 = "";

View File

@@ -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 = "";

View File

@@ -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;
}

View File

@@ -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 = "";

View File

@@ -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 = "";
}

View File

@@ -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<>();
}

View File

@@ -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();

View File

@@ -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_]", "_"));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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>