import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.PercentType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* The {@link Util} class defines common utility methods
*/
@NonNullByDefault
public class Util {
- private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
public static String buildUrl(String host, int port, String... urlParts) {
StringBuilder url = new StringBuilder();
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.deconz.internal.Util;
-import org.openhab.binding.deconz.internal.dto.BridgeFullState;
import org.openhab.binding.deconz.internal.dto.GroupMessage;
import org.openhab.binding.deconz.internal.dto.LightMessage;
import org.openhab.binding.deconz.internal.dto.SensorMessage;
}
@Override
- protected void startScan() {
+ public void startScan() {
final DeconzBridgeHandler handler = this.handler;
if (handler != null) {
- handler.requestFullState(false);
+ handler.getBridgeFullState().thenAccept(fullState -> {
+ stopScan();
+ removeOlderResults(getTimestampOfLastScan());
+ fullState.ifPresent(state -> {
+ state.sensors.forEach(this::addSensor);
+ state.lights.forEach(this::addLight);
+ state.groups.forEach(this::addGroup);
+ });
+
+ });
}
}
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof DeconzBridgeHandler) {
this.handler = (DeconzBridgeHandler) handler;
- ((DeconzBridgeHandler) handler).setDiscoveryService(this);
this.bridgeUID = handler.getThing().getUID();
}
}
public void deactivate() {
super.deactivate();
}
-
- /**
- * Call this method when a full bridge state request has been performed and either the fullState
- * are known or a failure happened.
- *
- * @param fullState The fullState or null.
- */
- public void stateRequestFinished(final @Nullable BridgeFullState fullState) {
- stopScan();
- removeOlderResults(getTimestampOfLastScan());
- if (fullState != null) {
- fullState.sensors.forEach(this::addSensor);
- fullState.lights.forEach(this::addLight);
- fullState.groups.forEach(this::addGroup);
- }
- }
}
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.deconz.internal.types.ResourceType;
/**
* http://dresden-elektronik.github.io/deconz-rest-doc/configuration/
public Map<String, SensorMessage> sensors = Collections.emptyMap();
public Map<String, LightMessage> lights = Collections.emptyMap();
public Map<String, GroupMessage> groups = Collections.emptyMap();
+
+ public @Nullable DeconzBaseMessage getMessage(ResourceType resourceType, String id) {
+ switch (resourceType) {
+ case LIGHTS:
+ return lights.get(id);
+ case SENSORS:
+ return sensors.get(id);
+ case GROUPS:
+ return groups.get(id);
+ default:
+ return null;
+ }
+ }
}
import static org.openhab.binding.deconz.internal.Util.buildUrl;
-import java.net.SocketTimeoutException;
-import java.util.concurrent.CompletionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Jan N. Klug - Refactored to abstract class
*/
@NonNullByDefault
-public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
- implements WebSocketMessageListener {
+public abstract class DeconzBaseThingHandler extends BaseThingHandler implements WebSocketMessageListener {
private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
protected final ResourceType resourceType;
protected ThingConfig config = new ThingConfig();
}
}
+ private @Nullable DeconzBridgeHandler getBridgeHandler() {
+ Bridge bridge = getBridge();
+ if (bridge == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ return null;
+ }
+ return (DeconzBridgeHandler) bridge.getHandler();
+ }
+
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (config.id.isEmpty()) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
// the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
// register the listener
- Bridge bridge = getBridge();
- if (bridge == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
- return;
- }
- DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
+ DeconzBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
}
- /**
- * parse the initial state response message
- *
- * @param r AsyncHttpClient.Result with the state response result
- * @return a message of the correct type
- */
- protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
-
/**
* processes a newly received (initial) state response
*
*
* @param stateResponse
*/
- protected abstract void processStateResponse(@Nullable T stateResponse);
+ protected abstract void processStateResponse(DeconzBaseMessage stateResponse);
/**
* Perform a request to the REST API for retrieving the full light state with all data and configuration.
*/
- protected void requestState(Consumer<@Nullable T> processor) {
- AsyncHttpClient asyncHttpClient = http;
- if (asyncHttpClient == null) {
- return;
+ protected void requestState(Consumer<DeconzBaseMessage> processor) {
+ DeconzBridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler != null) {
+ bridgeHandler.getBridgeFullState()
+ .thenAccept(f -> f.map(s -> s.getMessage(resourceType, config.id)).ifPresentOrElse(message -> {
+ logger.trace("{} processing {}", thing.getUID(), message);
+ processor.accept(message);
+ }, () -> {
+ if (initializationJob != null) {
+ stopInitializationJob();
+ initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
+ TimeUnit.SECONDS);
+ }
+ }));
}
-
- String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
- resourceType.getIdentifier(), config.id);
- logger.trace("Requesting URL for initial data: {}", url);
-
- // Get initial data
- asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
- if (e instanceof SocketTimeoutException || e instanceof TimeoutException
- || e instanceof CompletionException) {
- logger.debug("Get new state failed: ", e);
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
- }
-
- stopInitializationJob();
- initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
- TimeUnit.SECONDS);
-
- return null;
- }).thenAccept(processor);
}
/**
import static org.openhab.binding.deconz.internal.Util.buildUrl;
import java.net.SocketTimeoutException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ScheduledFuture;
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
import org.openhab.binding.deconz.internal.netutils.WebSocketConnectionListener;
+import org.openhab.core.cache.ExpiringCacheAsync;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.Bridge;
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(BRIDGE_TYPE);
private final Logger logger = LoggerFactory.getLogger(DeconzBridgeHandler.class);
- private @Nullable ThingDiscoveryService thingDiscoveryService;
private final WebSocketConnection websocket;
private final AsyncHttpClient http;
private DeconzBridgeConfig config = new DeconzBridgeConfig();
private boolean ignoreConfigurationUpdate;
private boolean websocketReconnect = false;
+ private final ExpiringCacheAsync<Optional<BridgeFullState>> fullStateCache = new ExpiringCacheAsync<>(1000);
+
/** The poll frequency for the API Key verification */
private static final int POLL_FREQUENCY_SEC = 10;
stopTimer();
scheduledFuture = scheduler.schedule(() -> requestApiKey(), POLL_FREQUENCY_SEC, TimeUnit.SECONDS);
} else if (r.getResponseCode() == 200) {
- ApiKeyMessage[] response = gson.fromJson(r.getBody(), ApiKeyMessage[].class);
+ ApiKeyMessage[] response = Objects.requireNonNull(gson.fromJson(r.getBody(), ApiKeyMessage[].class));
if (response.length == 0) {
throw new IllegalStateException("Authorisation request response is empty");
}
configuration.put(CONFIG_APIKEY, config.apikey);
updateConfiguration(configuration);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Waiting for configuration");
- requestFullState(true);
+ initializeBridgeState();
} else {
throw new IllegalStateException("Unknown status code for authorisation request");
}
}
/**
- * Parses the response message to the REST API for retrieving the full bridge state with all sensors and switches
- * and configuration.
+ * get the full state of the bridge from the cache
*
- * @param r The response
+ * @return a CompletableFuture that returns an Optional of the bridge full state
*/
- private @Nullable BridgeFullState parseBridgeFullStateResponse(AsyncHttpClient.Result r) {
- if (r.getResponseCode() == 403) {
- return null;
- } else if (r.getResponseCode() == 200) {
- return gson.fromJson(r.getBody(), BridgeFullState.class);
- } else {
- throw new IllegalStateException("Unknown status code for full state request");
- }
+ public CompletableFuture<Optional<BridgeFullState>> getBridgeFullState() {
+ return fullStateCache.getValue(this::refreshFullStateCache);
}
/**
- * Perform a request to the REST API for retrieving the full bridge state with all sensors and switches
- * and configuration.
+ * refresh the full bridge state (used for initial processing and state-lookup)
+ *
+ * @return Completable future with an Optional of the BridgeFullState
*/
- public void requestFullState(boolean isInitialRequest) {
+ private CompletableFuture<Optional<BridgeFullState>> refreshFullStateCache() {
+ logger.trace("{} starts refreshing the fullStateCache", thing.getUID());
if (config.apikey == null) {
- return;
+ return CompletableFuture.completedFuture(Optional.empty());
}
String url = buildUrl(config.getHostWithoutPort(), config.httpPort, config.apikey);
- http.get(url, config.timeout).thenApply(this::parseBridgeFullStateResponse).exceptionally(e -> {
- if (e instanceof SocketTimeoutException || e instanceof TimeoutException
- || e instanceof CompletionException) {
- logger.debug("Get full state failed", e);
+ return http.get(url, config.timeout).thenApply(r -> {
+ if (r.getResponseCode() == 403) {
+ return Optional.ofNullable((BridgeFullState) null);
+ } else if (r.getResponseCode() == 200) {
+ return Optional.ofNullable(gson.fromJson(r.getBody(), BridgeFullState.class));
} else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
- }
- return null;
- }).whenComplete((value, error) -> {
- final ThingDiscoveryService thingDiscoveryService = this.thingDiscoveryService;
- if (thingDiscoveryService != null) {
- // Hand over sensors to discovery service
- thingDiscoveryService.stateRequestFinished(value);
+ throw new IllegalStateException("Unknown status code for full state request");
}
- }).thenAccept(fullState -> {
- if (fullState == null) {
- if (isInitialRequest) {
- scheduledFuture = scheduler.schedule(() -> requestFullState(true), POLL_FREQUENCY_SEC,
- TimeUnit.SECONDS);
- }
- return;
+ }).handle((v, t) -> {
+ if (t == null) {
+ return v;
+ } else if (t instanceof SocketTimeoutException || t instanceof TimeoutException
+ || t instanceof CompletionException) {
+ logger.debug("Get full state failed", t);
+ } else if (t != null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, t.getMessage());
}
- if (fullState.config.name.isEmpty()) {
+ return Optional.empty();
+ });
+ }
+
+ /**
+ * Perform a request to the REST API for retrieving the full bridge state with all sensors and switches
+ * and configuration.
+ */
+ public void initializeBridgeState() {
+ getBridgeFullState().thenAccept(fullState -> fullState.ifPresentOrElse(state -> {
+ if (state.config.name.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"You are connected to a HUE bridge, not a deCONZ software!");
return;
}
- if (fullState.config.websocketport == 0) {
+ if (state.config.websocketport == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"deCONZ software too old. No websocket support!");
return;
// Add some information about the bridge
Map<String, String> editProperties = editProperties();
- editProperties.put("apiversion", fullState.config.apiversion);
- editProperties.put("swversion", fullState.config.swversion);
- editProperties.put("fwversion", fullState.config.fwversion);
- editProperties.put("uuid", fullState.config.uuid);
- editProperties.put("zigbeechannel", String.valueOf(fullState.config.zigbeechannel));
- editProperties.put("ipaddress", fullState.config.ipaddress);
+ editProperties.put("apiversion", state.config.apiversion);
+ editProperties.put("swversion", state.config.swversion);
+ editProperties.put("fwversion", state.config.fwversion);
+ editProperties.put("uuid", state.config.uuid);
+ editProperties.put("zigbeechannel", String.valueOf(state.config.zigbeechannel));
+ editProperties.put("ipaddress", state.config.ipaddress);
ignoreConfigurationUpdate = true;
updateProperties(editProperties);
ignoreConfigurationUpdate = false;
// Use requested websocket port if no specific port is given
- websocketPort = config.port == 0 ? fullState.config.websocketport : config.port;
+ websocketPort = config.port == 0 ? state.config.websocketport : config.port;
websocketReconnect = true;
startWebsocket();
- }).exceptionally(e -> {
+ }, () -> {
+ // initial response was empty, re-trying in POLL_FREQUENCY_SEC seconds
+ scheduledFuture = scheduler.schedule(this::initializeBridgeState, POLL_FREQUENCY_SEC, TimeUnit.SECONDS);
+ })).exceptionally(e -> {
if (e != null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);
}
- logger.warn("Full state parsing failed", e);
+ logger.warn("Initial full state parsing failed", e);
return null;
});
}
/**
* Starts the websocket connection.
- *
- * {@link #requestFullState} need to be called first to obtain the websocket port.
+ * {@link #initializeBridgeState} need to be called first to obtain the websocket port.
*/
private void startWebsocket() {
if (websocket.isConnected() || websocketPort == 0 || websocketReconnect == false) {
if (config.apikey == null) {
requestApiKey();
} else {
- requestFullState(true);
+ initializeBridgeState();
}
}
public DeconzBridgeConfig getBridgeConfig() {
return config;
}
-
- /**
- * Called by the {@link ThingDiscoveryService}. Informs the bridge handler about the service.
- *
- * @param thingDiscoveryService The service
- */
- public void setDiscoveryService(ThingDiscoveryService thingDiscoveryService) {
- this.thingDiscoveryService = thingDiscoveryService;
- }
}
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.deconz.internal.Util;
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
import org.openhab.binding.deconz.internal.dto.GroupAction;
import org.openhab.binding.deconz.internal.dto.GroupMessage;
import org.openhab.binding.deconz.internal.dto.GroupState;
-import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
-public class GroupThingHandler extends DeconzBaseThingHandler<GroupMessage> {
+public class GroupThingHandler extends DeconzBaseThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_LIGHTGROUP);
private final Logger logger = LoggerFactory.getLogger(GroupThingHandler.class);
}
@Override
- protected @Nullable GroupMessage parseStateResponse(AsyncHttpClient.Result r) {
- if (r.getResponseCode() == 403) {
- return null;
- } else if (r.getResponseCode() == 200) {
- return gson.fromJson(r.getBody(), GroupMessage.class);
- } else {
- throw new IllegalStateException("Unknown status code " + r.getResponseCode() + " for full state request");
- }
- }
-
- @Override
- protected void processStateResponse(@Nullable GroupMessage stateResponse) {
- if (stateResponse == null) {
- return;
- }
+ protected void processStateResponse(DeconzBaseMessage stateResponse) {
messageReceived(config.id, stateResponse);
}
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
import org.openhab.binding.deconz.internal.dto.LightMessage;
import org.openhab.binding.deconz.internal.dto.LightState;
-import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.openhab.core.library.types.*;
import org.openhab.core.thing.ChannelUID;
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
-public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
+public class LightThingHandler extends DeconzBaseThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_COLOR_TEMPERATURE_LIGHT,
THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_LIGHT, THING_TYPE_EXTENDED_COLOR_LIGHT, THING_TYPE_ONOFF_LIGHT,
THING_TYPE_WINDOW_COVERING, THING_TYPE_WARNING_DEVICE, THING_TYPE_DOORLOCK);
}
@Override
- protected @Nullable LightMessage parseStateResponse(AsyncHttpClient.Result r) {
- if (r.getResponseCode() == 403) {
- return null;
- } else if (r.getResponseCode() == 200) {
- LightMessage lightMessage = Objects.requireNonNull(gson.fromJson(r.getBody(), LightMessage.class));
- if (needsPropertyUpdate) {
- // if we did not receive an ctmin/ctmax, then we probably don't need it
- needsPropertyUpdate = false;
-
- Integer ctmax = lightMessage.ctmax;
- Integer ctmin = lightMessage.ctmin;
- if (ctmin != null && ctmax != null) {
- Map<String, String> properties = new HashMap<>(thing.getProperties());
- properties.put(PROPERTY_CT_MAX,
- Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
- properties.put(PROPERTY_CT_MIN,
- Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
- updateProperties(properties);
- }
- }
- return lightMessage;
- } else {
- throw new IllegalStateException("Unknown status code " + r.getResponseCode() + " for full state request");
+ protected void processStateResponse(DeconzBaseMessage stateResponse) {
+ if (!(stateResponse instanceof LightMessage)) {
+ return;
}
- }
- @Override
- protected void processStateResponse(@Nullable LightMessage stateResponse) {
- if (stateResponse == null) {
- return;
+ LightMessage lightMessage = (LightMessage) stateResponse;
+ if (needsPropertyUpdate) {
+ // if we did not receive an ctmin/ctmax, then we probably don't need it
+ needsPropertyUpdate = false;
+
+ Integer ctmax = lightMessage.ctmax;
+ Integer ctmin = lightMessage.ctmin;
+ if (ctmin != null && ctmax != null) {
+ Map<String, String> properties = new HashMap<>(thing.getProperties());
+ properties.put(PROPERTY_CT_MAX, Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
+ properties.put(PROPERTY_CT_MIN, Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
+ updateProperties(properties);
+ }
}
- if (stateResponse.state.effect != null) {
- checkAndUpdateEffectChannels(stateResponse);
+
+ if (lightMessage.state.effect != null) {
+ checkAndUpdateEffectChannels(lightMessage);
}
- messageReceived(config.id, stateResponse);
+
+ messageReceived(config.id, lightMessage);
}
private enum EffectLightModel {
import org.openhab.binding.deconz.internal.dto.SensorConfig;
import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.openhab.binding.deconz.internal.dto.SensorState;
-import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
* @author Lukas Agethen - Refactored to provide better extensibility
*/
@NonNullByDefault
-public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler<SensorMessage> {
+public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SensorBaseThingHandler.class);
/**
* The sensor state. Contains all possible fields for all supported sensors and switches
}
@Override
- protected @Nullable SensorMessage parseStateResponse(AsyncHttpClient.Result r) {
- if (r.getResponseCode() == 403) {
- return null;
- } else if (r.getResponseCode() == 200) {
- return gson.fromJson(r.getBody(), SensorMessage.class);
- } else {
- throw new IllegalStateException("Unknown status code " + r.getResponseCode() + " for full state request");
- }
- }
-
- @Override
- protected void processStateResponse(@Nullable SensorMessage stateResponse) {
- logger.trace("{} received {}", thing.getUID(), stateResponse);
- if (stateResponse == null) {
+ protected void processStateResponse(DeconzBaseMessage stateResponse) {
+ if (!(stateResponse instanceof SensorMessage)) {
return;
}
- SensorConfig newSensorConfig = stateResponse.config;
+
+ SensorMessage sensorMessage = (SensorMessage) stateResponse;
+ SensorConfig newSensorConfig = sensorMessage.config;
sensorConfig = newSensorConfig != null ? newSensorConfig : new SensorConfig();
- SensorState newSensorState = stateResponse.state;
+ SensorState newSensorState = sensorMessage.state;
sensorState = newSensorState != null ? newSensorState : new SensorState();
// Add some information about the sensor
}
Map<String, String> editProperties = editProperties();
- editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, stateResponse.swversion);
- editProperties.put(Thing.PROPERTY_MODEL_ID, stateResponse.modelid);
- editProperties.put(UNIQUE_ID, stateResponse.uniqueid);
+ editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, sensorMessage.swversion);
+ editProperties.put(Thing.PROPERTY_MODEL_ID, sensorMessage.modelid);
+ editProperties.put(UNIQUE_ID, sensorMessage.uniqueid);
ignoreConfigurationUpdate = true;
updateProperties(editProperties);
// So to monitor a sensor is still alive, the "last seen" is necessary.
// Because "last seen" is never updated by the WebSocket API - if this is supported, then we have to
// manually poll it after the defined time
- String lastSeen = stateResponse.lastseen;
+ String lastSeen = sensorMessage.lastseen;
if (lastSeen != null && config.lastSeenPolling > 0) {
createChannel(CHANNEL_LAST_SEEN, ChannelKind.STATE);
updateState(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime(lastSeen));
updateStatus(ThingStatus.ONLINE);
}
- private void processLastSeen(@Nullable SensorMessage stateResponse) {
- if (stateResponse == null) {
- return;
- }
+ private void processLastSeen(DeconzBaseMessage stateResponse) {
String lastSeen = stateResponse.lastseen;
if (lastSeen != null) {
updateState(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime(lastSeen));
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
-import org.openhab.binding.deconz.internal.dto.GroupMessage;
-import org.openhab.binding.deconz.internal.dto.LightMessage;
-import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WebSocket
@NonNullByDefault
public class WebSocketConnection {
- private static final Map<ResourceType, Class<? extends DeconzBaseMessage>> EXPECTED_MESSAGE_TYPES = Map.of(
- ResourceType.GROUPS, GroupMessage.class, ResourceType.LIGHTS, LightMessage.class, ResourceType.SENSORS,
- SensorMessage.class);
-
private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
private final WebSocketClient client;
return;
}
- Class<? extends DeconzBaseMessage> expectedMessageType = EXPECTED_MESSAGE_TYPES.get(changedMessage.r);
+ Class<? extends DeconzBaseMessage> expectedMessageType = changedMessage.r.getExpectedMessageType();
if (expectedMessageType == null) {
logger.warn("BUG! Could not get expected message type for resource type {}. Please report this incident.",
changedMessage.r);
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
+import org.openhab.binding.deconz.internal.dto.GroupMessage;
+import org.openhab.binding.deconz.internal.dto.LightMessage;
+import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public enum ResourceType {
- GROUPS("groups", "action"),
- LIGHTS("lights", "state"),
- SENSORS("sensors", "config"),
- UNKNOWN("", "");
+ GROUPS("groups", "action", GroupMessage.class),
+ LIGHTS("lights", "state", LightMessage.class),
+ SENSORS("sensors", "config", SensorMessage.class),
+ UNKNOWN("", "", null);
private static final Map<String, ResourceType> MAPPING = Arrays.stream(ResourceType.values())
.collect(Collectors.toMap(v -> v.identifier, v -> v));
private String identifier;
private String commandUrl;
+ private @Nullable Class<? extends DeconzBaseMessage> expectedMessageType;
- ResourceType(String identifier, String commandUrl) {
+ ResourceType(String identifier, String commandUrl,
+ @Nullable Class<? extends DeconzBaseMessage> expectedMessageType) {
this.identifier = identifier;
this.commandUrl = commandUrl;
+ this.expectedMessageType = expectedMessageType;
}
/**
return commandUrl;
}
+ /**
+ * get the expected message type for this resource type
+ *
+ * @return
+ */
+ public @Nullable Class<? extends DeconzBaseMessage> getExpectedMessageType() {
+ return expectedMessageType;
+ }
+
/**
* get the resource type from a string
*
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
assertEquals(6, bridgeFullState.lights.size());
assertEquals(9, bridgeFullState.sensors.size());
+ Mockito.doAnswer(answer -> CompletableFuture.completedFuture(Optional.of(bridgeFullState))).when(bridgeHandler)
+ .getBridgeFullState();
ThingDiscoveryService discoveryService = new ThingDiscoveryService();
discoveryService.setThingHandler(bridgeHandler);
discoveryService.addDiscoveryListener(discoveryListener);
-
- discoveryService.stateRequestFinished(bridgeFullState);
+ discoveryService.startScan();
Mockito.verify(discoveryListener, times(20)).thingDiscovered(any(), any());
}