All sensor-things have an additional `lastSeenPolling` parameter.
Due to limitations in the API of deCONZ, the `lastSeen` channel (available some sensors) is only available when using polling.
Allowed values are all positive integers, the unit is minutes.
-The default-value is `0`, which means "no polling at all".
+The default-value is `1440`, which means "once a day".
`dimmablelight`, `extendedcolorlight`, `colorlight` and `colortemperaturelight` have an additional optional parameter `transitiontime`.
The transition time is the time to move between two states and is configured in seconds.
properties.put(Thing.PROPERTY_VENDOR, light.manufacturername);
properties.put(Thing.PROPERTY_MODEL_ID, light.modelid);
- if (light.ctmax != null && light.ctmin != null) {
- properties.put(PROPERTY_CT_MAX,
- Integer.toString(Util.constrainToRange(light.ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
- properties.put(PROPERTY_CT_MIN,
- Integer.toString(Util.constrainToRange(light.ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
+ Integer ctmax = light.ctmax;
+ Integer ctmin = light.ctmin;
+ if (ctmax != null && ctmin != null) {
+ 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)));
}
switch (lightType) {
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;
import org.eclipse.jdt.annotation.Nullable;
registerListener();
// get initial values
- requestState();
+ requestState(this::processStateResponse);
} else {
// if the bridge is not ONLINE, we assume communication is not possible, so we unregister the listener and
// set the thing status to OFFLINE
protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
/**
- * processes a newly received state response
+ * processes a newly received (initial) state response
*
* MUST set the thing status!
*
/**
* Perform a request to the REST API for retrieving the full light state with all data and configuration.
*/
- protected void requestState() {
+ protected void requestState(Consumer<@Nullable T> processor) {
AsyncHttpClient asyncHttpClient = http;
if (asyncHttpClient == null) {
return;
}
stopInitializationJob();
- initializationJob = scheduler.schedule((Runnable) this::requestState, 10, TimeUnit.SECONDS);
+ initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
+ TimeUnit.SECONDS);
return null;
- }).thenAccept(this::processStateResponse);
+ }).thenAccept(processor);
}
/**
case CHANNEL_COLOR:
if (command instanceof HSBType) {
HSBType hsbCommand = (HSBType) command;
- newGroupAction.bri = Util.fromPercentType(hsbCommand.getBrightness());
- if (newGroupAction.bri > 0) {
+ Integer bri = Util.fromPercentType(hsbCommand.getBrightness());
+ newGroupAction.bri = bri;
+ if (bri > 0) {
newGroupAction.hue = (int) (hsbCommand.getHue().doubleValue() * HUE_FACTOR);
newGroupAction.sat = Util.fromPercentType(hsbCommand.getSaturation());
}
return;
}
- if (newGroupAction.bri != null && newGroupAction.bri > 0) {
+ Integer bri = newGroupAction.bri;
+ if (bri != null && bri > 0) {
newGroupAction.on = true;
}
return;
}
- if (newLightState.on != null && !newLightState.on) {
+ Boolean newOn = newLightState.on;
+ if (newOn != null && !newOn) {
// if light shall be off, no other commands are allowed, so reset the new light state
newLightState.clear();
newLightState.on = false;
}
sendCommand(newLightState, command, channelUID, () -> {
+ Integer transitionTime = newLightState.transitiontime;
lastCommandExpireTimestamp = System.currentTimeMillis()
- + (newLightState.transitiontime != null ? newLightState.transitiontime
- : DEFAULT_COMMAND_EXPIRY_TIME);
+ + (transitionTime != null ? transitionTime : DEFAULT_COMMAND_EXPIRY_TIME);
lastCommand = newLightState;
});
}
return null;
} else if (r.getResponseCode() == 200) {
LightMessage lightMessage = gson.fromJson(r.getBody(), LightMessage.class);
- if (lightMessage != null && needsPropertyUpdate) {
+ if (needsPropertyUpdate) {
// if we did not receive an ctmin/ctmax, then we probably don't need it
needsPropertyUpdate = false;
- if (lightMessage.ctmin != null && lightMessage.ctmax != null) {
+ 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(lightMessage.ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
+ Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
properties.put(PROPERTY_CT_MIN,
- Integer.toString(Util.constrainToRange(lightMessage.ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
+ Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
updateProperties(properties);
}
}
private void valueUpdated(String channelId, LightState newState) {
Integer bri = newState.bri;
+ Integer hue = newState.hue;
+ Integer sat = newState.sat;
Boolean on = newState.on;
switch (channelId) {
case CHANNEL_COLOR:
if (on != null && on == false) {
updateState(channelId, OnOffType.OFF);
- } else if (bri != null && newState.colormode != null && newState.colormode.equals("xy")) {
+ } else if (bri != null && "xy".equals(newState.colormode)) {
final double @Nullable [] xy = newState.xy;
if (xy != null && xy.length == 2) {
HSBType color = HSBType.fromXY((float) xy[0], (float) xy[1]);
updateState(channelId, new HSBType(color.getHue(), color.getSaturation(), toPercentType(bri)));
}
- } else if (bri != null && newState.hue != null && newState.sat != null) {
- final Integer hue = newState.hue;
- final Integer sat = newState.sat;
+ } else if (bri != null && hue != null && sat != null) {
updateState(channelId,
new HSBType(new DecimalType(hue / HUE_FACTOR), toPercentType(sat), toPercentType(bri)));
}
return;
}
lightStateCache = lightState;
- if (lightState.reachable != null && lightState.reachable) {
+ if (Boolean.TRUE.equals(lightState.reachable)) {
updateStatus(ThingStatus.ONLINE);
thing.getChannels().stream().map(c -> c.getUID().getId()).forEach(c -> valueUpdated(c, lightState));
} else {
// "Last seen" is the last "ping" from the device, whereas "last update" is the last status changed.
// For example, for a fire sensor, the device pings regularly, without necessarily updating channels.
// 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;
if (lastSeen != null && config.lastSeenPolling > 0) {
createChannel(CHANNEL_LAST_SEEN, ChannelKind.STATE);
updateState(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime(lastSeen));
- // 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 (default is off)
- if (config.lastSeenPolling > 0) {
- lastSeenPollingJob = scheduler.schedule((Runnable) this::requestState, config.lastSeenPolling,
- TimeUnit.MINUTES);
- logger.trace("lastSeen polling enabled for thing {} with interval of {} minutes", thing.getUID(),
- config.lastSeenPolling);
- }
+ lastSeenPollingJob = scheduler.schedule(() -> requestState(this::processLastSeen), config.lastSeenPolling,
+ TimeUnit.MINUTES);
+ logger.trace("lastSeen polling enabled for thing {} with interval of {} minutes", thing.getUID(),
+ config.lastSeenPolling);
}
updateStatus(ThingStatus.ONLINE);
}
+ private void processLastSeen(@Nullable SensorMessage stateResponse) {
+ if (stateResponse == null) {
+ return;
+ }
+ String lastSeen = stateResponse.lastseen;
+ if (lastSeen != null) {
+ updateState(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime(lastSeen));
+ }
+ }
+
protected void createChannel(String channelId, ChannelKind kind) {
ThingHandlerCallback callback = getCallback();
if (callback != null) {
import java.util.List;
import java.util.Set;
+import javax.measure.quantity.Temperature;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.deconz.internal.dto.SensorConfig;
@Override
protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
super.valueUpdated(channelUID, newConfig);
- String mode = newConfig.mode != null ? newConfig.mode.name() : ThermostatMode.UNKNOWN.name();
+ ThermostatMode thermostatMode = newConfig.mode;
+ String mode = thermostatMode != null ? thermostatMode.name() : ThermostatMode.UNKNOWN.name();
String channelID = channelUID.getId();
switch (channelID) {
case CHANNEL_HEATSETPOINT:
updateQuantityTypeChannel(channelID, newConfig.offset, CELSIUS, 1.0 / 100);
break;
case CHANNEL_THERMOSTAT_MODE:
- if (mode != null) {
- updateState(channelUID, new StringType(mode));
- }
+ updateState(channelUID, new StringType(mode));
break;
}
}
if (command instanceof DecimalType) {
newTemperature = ((DecimalType) command).toBigDecimal();
} else if (command instanceof QuantityType) {
- newTemperature = ((QuantityType) command).toUnit(CELSIUS).toBigDecimal();
+ @SuppressWarnings("unchecked")
+ QuantityType<Temperature> temperatureCelsius = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
+ if (temperatureCelsius != null) {
+ newTemperature = temperatureCelsius.toBigDecimal();
+ } else {
+ return null;
+ }
} else {
return null;
}
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
CHANNEL_TEMPERATURE);
- private final Logger logger = LoggerFactory.getLogger(SensorThingHandler.class);
-
public SensorThingHandler(Thing thing, Gson gson) {
super(thing, gson);
}
@NonNullByDefault
public class ThingConfig {
public String id = "";
- public int lastSeenPolling = 0;
+ public int lastSeenPolling = 1440;
public @Nullable Double transitiontime;
}
<parameter name="lastSeenPolling" type="integer" min="0" unit="min">
<label>LastSeen Poll Interval</label>
<description>Interval to poll the deCONZ Gateway for this sensor's "lastSeen" channel. Polling is disabled when set
- to 0.</description>
- <default>0</default>
+ to 1440 (once per day).</description>
+ <default>1440</default>
</parameter>
</config-description>