[deconz] add group support (#8715)
* add group message Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
parent
1ac55a58e0
commit
8abcc252df
@ -44,7 +44,7 @@
|
||||
/bundles/org.openhab.binding.daikin/ @caffineehacker
|
||||
/bundles/org.openhab.binding.danfossairunit/ @pravussum
|
||||
/bundles/org.openhab.binding.darksky/ @cweitkamp
|
||||
/bundles/org.openhab.binding.deconz/ @davidgraeff
|
||||
/bundles/org.openhab.binding.deconz/ @J-N-K
|
||||
/bundles/org.openhab.binding.denonmarantz/ @jwveldhuis
|
||||
/bundles/org.openhab.binding.digiplex/ @rmichalak
|
||||
/bundles/org.openhab.binding.digitalstrom/ @MichaelOchel @msiegele
|
||||
|
||||
@ -42,10 +42,12 @@ Additionally lights, window coverings (blinds) and thermostats are supported:
|
||||
| Thermostat | ZHAThermostat | `thermostat` |
|
||||
| Warning Device (Siren) | Warning device | `warningdevice` |
|
||||
|
||||
Currently only light-groups are supported via the thing-type `lightgroup`.
|
||||
|
||||
## Discovery
|
||||
|
||||
deCONZ software instances are discovered automatically in the same subnet.
|
||||
Sensors, switches, lights and blinds are discovered as soon as a `deconz` bridge thing comes online.
|
||||
Sensors, switches, groups, lights and blinds are discovered as soon as a `deconz` bridge thing comes online.
|
||||
If your device is not discovered, please check the DEBUG log for unknown devices and report your findings.
|
||||
|
||||
## Thing Configuration
|
||||
@ -81,13 +83,11 @@ Due to limitations in the API of deCONZ, the `lastSeen` channel (available some
|
||||
Allowed values are all positive integers, the unit is minutes.
|
||||
The default-value is `0`, which means "no polling at all".
|
||||
|
||||
|
||||
`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.
|
||||
The resolution provided is 1/10s.
|
||||
If no value is provided, the default value of the device is used.
|
||||
|
||||
|
||||
### Textual Thing Configuration - Retrieving an API Key
|
||||
|
||||
If you use the textual configuration, the thing file without an API key will look like this, for example:
|
||||
@ -154,18 +154,23 @@ The `last_seen` channel is added when it is available AND the `lastSeenPolling`
|
||||
|
||||
Other devices support
|
||||
|
||||
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|
||||
|-------------------|--------------------------|:-----------:|---------------------------------------|-----------------------------------------------|
|
||||
| brightness | Dimmer | R/W | Brightness of the light | `dimmablelight`, `colortemperaturelight` |
|
||||
| switch | Switch | R/W | State of a ON/OFF device | `onofflight` |
|
||||
| color | Color | R/W | Color of an multi-color light | `colorlight`, `extendedcolorlight` |
|
||||
| color_temperature | Number | R/W | Color temperature in kelvin. The value range is determined by each individual light | `colortemperaturelight`, `extendedcolorlight` |
|
||||
| position | Rollershutter | R/W | Position of the blind | `windowcovering` |
|
||||
| heatsetpoint | Number:Temperature | R/W | Target Temperature in °C | `thermostat` |
|
||||
| valve | Number:Dimensionless | R | Valve position in % | `thermostat` |
|
||||
| mode | String | R/W | Mode: "auto", "heat" and "off" | `thermostat` |
|
||||
| offset | Number | R | Temperature offset for sensor | `thermostat` |
|
||||
| alert | Switch | R/W | Turn alerts on/off | `warningdevice` |
|
||||
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|
||||
|-------------------|--------------------------|:-----------:|---------------------------------------|-------------------------------------------------|
|
||||
| brightness | Dimmer | R/W | Brightness of the light | `dimmablelight`, `colortemperaturelight` |
|
||||
| switch | Switch | R/W | State of a ON/OFF device | `onofflight` |
|
||||
| color | Color | R/W | Color of an multi-color light | `colorlight`, `extendedcolorlight`, `lightgroup`|
|
||||
| color_temperature | Number | R/W | Color temperature in kelvin. The value range is determined by each individual light | `colortemperaturelight`, `extendedcolorlight`, `lightgroup` |
|
||||
| position | Rollershutter | R/W | Position of the blind | `windowcovering` |
|
||||
| heatsetpoint | Number:Temperature | R/W | Target Temperature in °C | `thermostat` |
|
||||
| valve | Number:Dimensionless | R | Valve position in % | `thermostat` |
|
||||
| mode | String | R/W | Mode: "auto", "heat" and "off" | `thermostat` |
|
||||
| offset | Number | R | Temperature offset for sensor | `thermostat` |
|
||||
| alert | Switch | R/W | Turn alerts on/off | `warningdevice`, `lightgroup` |
|
||||
| all_on | Switch | R | All lights in group are on | `lightgroup` |
|
||||
| any_on | Switch | R | Any light in group is on | `lightgroup` |
|
||||
|
||||
**NOTE:** For groups `color` and `color_temperature` are used for sending commands to the group.
|
||||
Their state represents the last command send to the group, not necessarily the actual state of the group.
|
||||
|
||||
### Trigger Channels
|
||||
|
||||
@ -207,6 +212,7 @@ Bridge deconz:deconz:homeserver [ host="192.168.0.10", apikey="ABCDEFGHIJ" ] {
|
||||
waterleakagesensor basement-water-leakage "Basement Water Leakage" [ id="7" ]
|
||||
alarmsensor basement-alarm "Basement Alarm Sensor" [ id="8", lastSeenPolling=5 ]
|
||||
dimmablelight livingroom-ceiling "Livingroom Ceiling" [ id="1" ]
|
||||
lightgroup livingroom "Livingroom" [ id="1" ]
|
||||
}
|
||||
```
|
||||
|
||||
@ -221,6 +227,7 @@ Contact Livingroom_Window "Window Livingroom [%s]"
|
||||
Switch Basement_Water_Leakage "Basement Water Leakage [%s]" { channel="deconz:waterleakagesensor:homeserver:basement-water-leakage:waterleakage" }
|
||||
Switch Basement_Alarm "Basement Alarm Triggered [%s]" { channel="deconz:alarmsensor:homeserver:basement-alarm:alarm" }
|
||||
Dimmer Livingroom_Ceiling "Livingroom Ceiling [%d]" <light> { channel="deconz:dimmablelight:homeserver:livingroom-ceiling:brightness" }
|
||||
Color Livingroom "Livingroom Light Control"
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
@ -23,7 +23,6 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "deconz";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
@ -63,7 +62,10 @@ public class BindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_WINDOW_COVERING = new ThingTypeUID(BINDING_ID, "windowcovering");
|
||||
public static final ThingTypeUID THING_TYPE_WARNING_DEVICE = new ThingTypeUID(BINDING_ID, "warningdevice");
|
||||
|
||||
// List of all Channel ids
|
||||
// groups
|
||||
public static final ThingTypeUID THING_TYPE_LIGHTGROUP = new ThingTypeUID(BINDING_ID, "lightgroup");
|
||||
|
||||
// sensor channel ids
|
||||
public static final String CHANNEL_PRESENCE = "presence";
|
||||
public static final String CHANNEL_LAST_UPDATED = "last_updated";
|
||||
public static final String CHANNEL_LAST_SEEN = "last_seen";
|
||||
@ -98,19 +100,22 @@ public class BindingConstants {
|
||||
public static final String CHANNEL_TEMPERATURE_OFFSET = "offset";
|
||||
public static final String CHANNEL_VALVE_POSITION = "valve";
|
||||
|
||||
// group + light channel ids
|
||||
public static final String CHANNEL_SWITCH = "switch";
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
|
||||
public static final String CHANNEL_COLOR = "color";
|
||||
public static final String CHANNEL_POSITION = "position";
|
||||
public static final String CHANNEL_ALERT = "alert";
|
||||
public static final String CHANNEL_ALL_ON = "all_on";
|
||||
public static final String CHANNEL_ANY_ON = "any_on";
|
||||
|
||||
// Thing configuration
|
||||
public static final String CONFIG_HOST = "host";
|
||||
public static final String CONFIG_HTTP_PORT = "httpPort";
|
||||
public static final String CONFIG_APIKEY = "apikey";
|
||||
public static final String PROPERTY_UDN = "UDN";
|
||||
|
||||
public static final String CONFIG_ID = "id";
|
||||
public static final String UNIQUE_ID = "uid";
|
||||
|
||||
public static final String PROPERTY_CT_MIN = "ctmin";
|
||||
@ -121,4 +126,7 @@ public class BindingConstants {
|
||||
public static final int ZCL_CT_MIN = 1;
|
||||
public static final int ZCL_CT_MAX = 65279; // 0xFEFF
|
||||
public static final int ZCL_CT_INVALID = 65535; // 0xFFFF
|
||||
|
||||
public static final double HUE_FACTOR = 65535 / 360.0;
|
||||
public static final double BRIGHTNESS_FACTOR = 2.54;
|
||||
}
|
||||
|
||||
@ -18,15 +18,9 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.LightThingHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.SensorThermostatThingHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.*;
|
||||
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatMode;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
|
||||
import org.openhab.binding.deconz.internal.types.*;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.io.net.http.WebSocketFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -53,7 +47,8 @@ import com.google.gson.GsonBuilder;
|
||||
public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(DeconzBridgeHandler.SUPPORTED_THING_TYPES, LightThingHandler.SUPPORTED_THING_TYPE_UIDS,
|
||||
SensorThingHandler.SUPPORTED_THING_TYPES, SensorThermostatThingHandler.SUPPORTED_THING_TYPES)
|
||||
SensorThingHandler.SUPPORTED_THING_TYPES, SensorThermostatThingHandler.SUPPORTED_THING_TYPES,
|
||||
GroupThingHandler.SUPPORTED_THING_TYPE_UIDS)
|
||||
.flatMap(Set::stream).collect(Collectors.toSet());
|
||||
|
||||
private final Gson gson;
|
||||
@ -71,6 +66,8 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(GroupType.class, new GroupTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ResourceType.class, new ResourceTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
}
|
||||
@ -93,6 +90,8 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
||||
return new SensorThingHandler(thing, gson);
|
||||
} else if (SensorThermostatThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SensorThermostatThingHandler(thing, gson);
|
||||
} else if (GroupThingHandler.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID)) {
|
||||
return new GroupThingHandler(thing, gson);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.deconz.internal;
|
||||
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.BRIGHTNESS_FACTOR;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
@ -22,6 +24,9 @@ import java.util.stream.Stream;
|
||||
|
||||
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
|
||||
@ -30,6 +35,8 @@ import org.openhab.core.library.types.DateTimeType;
|
||||
*/
|
||||
@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();
|
||||
url.append("http://");
|
||||
@ -54,6 +61,33 @@ public class Util {
|
||||
return Math.max(min, Math.min(intValue, max));
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a brightness value from int to PercentType
|
||||
*
|
||||
* @param val the value
|
||||
* @return the corresponding PercentType value
|
||||
*/
|
||||
public static PercentType toPercentType(int val) {
|
||||
int scaledValue = (int) Math.ceil(val / BRIGHTNESS_FACTOR);
|
||||
if (scaledValue < 0 || scaledValue > 100) {
|
||||
LOGGER.trace("received value {} (converted to {}). Coercing.", val, scaledValue);
|
||||
scaledValue = scaledValue < 0 ? 0 : scaledValue;
|
||||
scaledValue = scaledValue > 100 ? 100 : scaledValue;
|
||||
}
|
||||
|
||||
return new PercentType(scaledValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a brightness value from PercentType to int
|
||||
*
|
||||
* @param val the value
|
||||
* @return the corresponding int value
|
||||
*/
|
||||
public static int fromPercentType(PercentType val) {
|
||||
return (int) Math.floor(val.doubleValue() * BRIGHTNESS_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a timestamp string to a DateTimeType
|
||||
*
|
||||
|
||||
@ -26,12 +26,14 @@ 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;
|
||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.LightThingHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.SensorThermostatThingHandler;
|
||||
import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
|
||||
import org.openhab.binding.deconz.internal.types.GroupType;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
@ -93,12 +95,53 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements D
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sensor device to the discovery inbox.
|
||||
* Add a group to the discovery inbox.
|
||||
*
|
||||
* @param lightID The id of the light
|
||||
* @param light The sensor description
|
||||
* @param groupId The id of the light
|
||||
* @param group The group description
|
||||
*/
|
||||
private void addLight(String lightID, LightMessage light) {
|
||||
private void addGroup(String groupId, GroupMessage group) {
|
||||
final ThingUID bridgeUID = this.bridgeUID;
|
||||
if (bridgeUID == null) {
|
||||
logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
|
||||
return;
|
||||
}
|
||||
|
||||
ThingTypeUID thingTypeUID;
|
||||
GroupType groupType = group.type;
|
||||
|
||||
if (groupType == null) {
|
||||
logger.warn("No group type reported for group {} ({})", group.modelid, group.name);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(CONFIG_ID, groupId);
|
||||
|
||||
switch (groupType) {
|
||||
case LIGHT_GROUP:
|
||||
thingTypeUID = THING_TYPE_LIGHTGROUP;
|
||||
break;
|
||||
default:
|
||||
logger.debug(
|
||||
"Found group: {} ({}), type {} but no thing type defined for that type. This should be reported.",
|
||||
group.id, group.name, group.type);
|
||||
return;
|
||||
}
|
||||
|
||||
ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, group.id);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(group.name)
|
||||
.withProperties(properties).withRepresentationProperty(CONFIG_ID).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a light device to the discovery inbox.
|
||||
*
|
||||
* @param lightId The id of the light
|
||||
* @param light The light description
|
||||
*/
|
||||
private void addLight(String lightId, LightMessage light) {
|
||||
final ThingUID bridgeUID = this.bridgeUID;
|
||||
if (bridgeUID == null) {
|
||||
logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
|
||||
@ -114,7 +157,7 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements D
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("id", lightID);
|
||||
properties.put(CONFIG_ID, lightId);
|
||||
properties.put(UNIQUE_ID, light.uniqueid);
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, light.swversion);
|
||||
properties.put(Thing.PROPERTY_VENDOR, light.manufacturername);
|
||||
@ -227,7 +270,7 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements D
|
||||
ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
|
||||
.withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperty("id", sensorID)
|
||||
.withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperty(CONFIG_ID, sensorID)
|
||||
.withProperty(UNIQUE_ID, sensor.uniqueid).withRepresentationProperty(UNIQUE_ID).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
@ -268,6 +311,7 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements D
|
||||
if (fullState != null) {
|
||||
fullState.sensors.forEach(this::addSensor);
|
||||
fullState.lights.forEach(this::addLight);
|
||||
fullState.groups.forEach(this::addGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,4 +41,5 @@ public class BridgeFullState {
|
||||
|
||||
public Map<String, SensorMessage> sensors = Collections.emptyMap();
|
||||
public Map<String, LightMessage> lights = Collections.emptyMap();
|
||||
public Map<String, GroupMessage> groups = Collections.emptyMap();
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ package org.openhab.binding.deconz.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||
|
||||
/**
|
||||
* The REST interface and websocket connection are using the same fields.
|
||||
@ -25,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
public class DeconzBaseMessage {
|
||||
// For websocket change events
|
||||
public String e = ""; // "changed"
|
||||
public String r = ""; // "sensors"
|
||||
public ResourceType r = ResourceType.UNKNOWN; // "sensors"
|
||||
public String t = ""; // "event"
|
||||
public String id = ""; // "3"
|
||||
|
||||
|
||||
@ -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.deconz.internal.dto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link GroupAction} is send by the websocket connection as well as the Rest API.
|
||||
* It is part of a {@link GroupMessage}.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupAction {
|
||||
public @Nullable Boolean on;
|
||||
public @Nullable Boolean toggle;
|
||||
public @Nullable Integer bri;
|
||||
public @Nullable Integer hue;
|
||||
public @Nullable Integer sat;
|
||||
public @Nullable Integer ct;
|
||||
public double @Nullable [] xy;
|
||||
public @Nullable String alert;
|
||||
public @Nullable String effect;
|
||||
public @Nullable Integer colorloopspeed;
|
||||
public @Nullable Integer transitiontime;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupAction{" + "on=" + on + ", toggle=" + toggle + ", bri=" + bri + ", hue=" + hue + ", sat=" + sat
|
||||
+ ", ct=" + ct + ", xy=" + Arrays.toString(xy) + ", alert='" + alert + '\'' + ", effect='" + effect
|
||||
+ '\'' + ", colorloopspeed=" + colorloopspeed + ", transitiontime=" + transitiontime + '}';
|
||||
}
|
||||
}
|
||||
@ -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.deconz.internal.dto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.deconz.internal.types.GroupType;
|
||||
|
||||
/**
|
||||
* The REST interface and websocket connection are using the same fields.
|
||||
* The REST data contains more descriptive info like the manufacturer and name.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupMessage extends DeconzBaseMessage {
|
||||
public @Nullable GroupAction action;
|
||||
public String @Nullable [] devicemembership;
|
||||
public @Nullable Boolean hidden;
|
||||
public String @Nullable [] lights;
|
||||
public String @Nullable [] lightsequence;
|
||||
public String @Nullable [] multideviceids;
|
||||
public Scene @Nullable [] scenes;
|
||||
public @Nullable GroupState state;
|
||||
public @Nullable GroupType type;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupMessage{" + "action=" + action + ", devicemembership=" + Arrays.toString(devicemembership)
|
||||
+ ", hidden=" + hidden + ", lights=" + Arrays.toString(lights) + ", lightsequence="
|
||||
+ Arrays.toString(lightsequence) + ", multideviceids=" + Arrays.toString(multideviceids) + ", scenes="
|
||||
+ Arrays.toString(scenes) + ", state=" + state + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.deconz.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link GroupState} is send by the websocket connection as well as the Rest API.
|
||||
* It is part of a {@link GroupMessage}.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupState {
|
||||
public boolean all_on;
|
||||
public boolean any_on;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupState{" + "all_on=" + all_on + ", any_on=" + any_on + '}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.deconz.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Scene} is send by the websocket connection as well as the Rest API.
|
||||
* It is part of a {@link GroupMessage}.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Scene {
|
||||
public String id = "";
|
||||
public String name = "";
|
||||
}
|
||||
@ -26,12 +26,10 @@ import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
|
||||
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
||||
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
|
||||
import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.binding.deconz.internal.types.ResourceType;
|
||||
import org.openhab.core.thing.*;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -42,9 +40,7 @@ import com.google.gson.Gson;
|
||||
*
|
||||
* It waits for the bridge to come online, grab the websocket connection and bridge configuration
|
||||
* and registers to the websocket connection as a listener.
|
||||
*
|
||||
* A REST API call is made to get the initial light/rollershutter state.
|
||||
*
|
||||
**
|
||||
* @author David Graeff - Initial contribution
|
||||
* @author Jan N. Klug - Refactored to abstract class
|
||||
*/
|
||||
@ -52,6 +48,7 @@ import com.google.gson.Gson;
|
||||
public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
|
||||
implements WebSocketMessageListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
|
||||
protected final ResourceType resourceType;
|
||||
protected ThingConfig config = new ThingConfig();
|
||||
protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
|
||||
protected final Gson gson;
|
||||
@ -59,9 +56,10 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
||||
protected @Nullable WebSocketConnection connection;
|
||||
protected @Nullable AsyncHttpClient http;
|
||||
|
||||
public DeconzBaseThingHandler(Thing thing, Gson gson) {
|
||||
public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
|
||||
super(thing);
|
||||
this.gson = gson;
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,9 +73,19 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void registerListener();
|
||||
private void registerListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.registerListener(resourceType, config.id, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void unregisterListener();
|
||||
private void unregisterListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.unregisterListener(resourceType, config.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
@ -86,39 +94,38 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
||||
return;
|
||||
}
|
||||
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
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();
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
|
||||
this.connection = webSocketConnection;
|
||||
this.http = bridgeHandler.getHttp();
|
||||
this.bridgeConfig = bridgeHandler.getBridgeConfig();
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
|
||||
|
||||
// Real-time data
|
||||
registerListener();
|
||||
|
||||
// get initial values
|
||||
requestState();
|
||||
} 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
|
||||
unregisterListener();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
|
||||
this.connection = webSocketConnection;
|
||||
this.http = bridgeHandler.getHttp();
|
||||
this.bridgeConfig = bridgeHandler.getBridgeConfig();
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
|
||||
|
||||
// Real-time data
|
||||
registerListener();
|
||||
|
||||
// get initial values
|
||||
requestState();
|
||||
}
|
||||
|
||||
protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
|
||||
@ -132,21 +139,17 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
||||
*/
|
||||
protected abstract void processStateResponse(@Nullable T stateResponse);
|
||||
|
||||
/**
|
||||
* call requestState(type) in this method only
|
||||
*/
|
||||
protected abstract void requestState();
|
||||
|
||||
/**
|
||||
* Perform a request to the REST API for retrieving the full light state with all data and configuration.
|
||||
*/
|
||||
protected void requestState(String type) {
|
||||
protected void requestState() {
|
||||
AsyncHttpClient asyncHttpClient = http;
|
||||
if (asyncHttpClient == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, type, config.id);
|
||||
String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
|
||||
resourceType.getIdentifier(), config.id);
|
||||
logger.trace("Requesting URL for initial data: {}", url);
|
||||
|
||||
// Get initial data
|
||||
@ -165,13 +168,42 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
||||
}).thenAccept(this::processStateResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* sends a command to the bridge
|
||||
*
|
||||
* @param object must be serializable and contain the command
|
||||
* @param originalCommand the original openHAB command (used for logging purposes)
|
||||
* @param channelUID the channel that this command was send to (used for logging purposes)
|
||||
* @param acceptProcessing additional processing after the command was successfully send (might be null)
|
||||
*/
|
||||
protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
|
||||
@Nullable Runnable acceptProcessing) {
|
||||
AsyncHttpClient asyncHttpClient = http;
|
||||
if (asyncHttpClient == null) {
|
||||
return;
|
||||
}
|
||||
String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
|
||||
resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
|
||||
|
||||
String json = gson.toJson(object);
|
||||
logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
|
||||
|
||||
asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
|
||||
if (acceptProcessing != null) {
|
||||
acceptProcessing.run();
|
||||
}
|
||||
logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
|
||||
}).exceptionally(e -> {
|
||||
logger.debug("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
|
||||
e.getMessage());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopInitializationJob();
|
||||
WebSocketConnection webSocketConnection = connection;
|
||||
if (webSocketConnection != null) {
|
||||
webSocketConnection.unregisterLightListener(config.id);
|
||||
}
|
||||
unregisterListener();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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.deconz.internal.handler;
|
||||
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
||||
|
||||
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;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This light thing doesn't establish any connections, that is done by the bridge Thing.
|
||||
*
|
||||
* It waits for the bridge to come online, grab the websocket connection and bridge configuration
|
||||
* and registers to the websocket connection as a listener.
|
||||
*
|
||||
* A REST API call is made to get the initial light/rollershutter state.
|
||||
*
|
||||
* Every light and rollershutter is supported by this Thing, because a unified state is kept
|
||||
* in {@link #groupStateCache}. Every field that got received by the REST API for this specific
|
||||
* sensor is published to the framework.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupThingHandler extends DeconzBaseThingHandler<GroupMessage> {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_LIGHTGROUP);
|
||||
private final Logger logger = LoggerFactory.getLogger(GroupThingHandler.class);
|
||||
|
||||
/**
|
||||
* The group state.
|
||||
*/
|
||||
private GroupState groupStateCache = new GroupState();
|
||||
|
||||
public GroupThingHandler(Thing thing, Gson gson) {
|
||||
super(thing, gson, ResourceType.GROUPS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelId = channelUID.getId();
|
||||
|
||||
GroupAction newGroupAction = new GroupAction();
|
||||
switch (channelId) {
|
||||
case CHANNEL_ALL_ON:
|
||||
case CHANNEL_ANY_ON:
|
||||
if (command instanceof RefreshType) {
|
||||
valueUpdated(channelUID.getId(), groupStateCache);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_ALERT:
|
||||
if (command instanceof OnOffType) {
|
||||
newGroupAction.alert = command == OnOffType.ON ? "alert" : "none";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
if (command instanceof HSBType) {
|
||||
HSBType hsbCommand = (HSBType) command;
|
||||
newGroupAction.bri = Util.fromPercentType(hsbCommand.getBrightness());
|
||||
if (newGroupAction.bri > 0) {
|
||||
newGroupAction.hue = (int) (hsbCommand.getHue().doubleValue() * HUE_FACTOR);
|
||||
newGroupAction.sat = Util.fromPercentType(hsbCommand.getSaturation());
|
||||
}
|
||||
} else if (command instanceof PercentType) {
|
||||
newGroupAction.bri = Util.fromPercentType((PercentType) command);
|
||||
} else if (command instanceof DecimalType) {
|
||||
newGroupAction.bri = ((DecimalType) command).intValue();
|
||||
} else if (command instanceof OnOffType) {
|
||||
newGroupAction.on = OnOffType.ON.equals(command);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
if (command instanceof DecimalType) {
|
||||
int miredValue = Util.kelvinToMired(((DecimalType) command).intValue());
|
||||
newGroupAction.ct = Util.constrainToRange(miredValue, ZCL_CT_MIN, ZCL_CT_MAX);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (newGroupAction.bri != null && newGroupAction.bri > 0) {
|
||||
newGroupAction.on = true;
|
||||
}
|
||||
|
||||
sendCommand(newGroupAction, command, channelUID, null);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
messageReceived(config.id, stateResponse);
|
||||
}
|
||||
|
||||
private void valueUpdated(String channelId, GroupState newState) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_ALL_ON:
|
||||
updateState(channelId, OnOffType.from(newState.all_on));
|
||||
break;
|
||||
case CHANNEL_ANY_ON:
|
||||
updateState(channelId, OnOffType.from(newState.any_on));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(String sensorID, DeconzBaseMessage message) {
|
||||
if (message instanceof GroupMessage) {
|
||||
GroupMessage groupMessage = (GroupMessage) message;
|
||||
logger.trace("{} received {}", thing.getUID(), groupMessage);
|
||||
GroupState groupState = groupMessage.state;
|
||||
if (groupState != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
thing.getChannels().stream().map(c -> c.getUID().getId()).forEach(c -> valueUpdated(c, groupState));
|
||||
groupStateCache = groupState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,8 +19,6 @@ import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -30,7 +28,7 @@ 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.netutils.WebSocketConnection;
|
||||
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -68,12 +66,10 @@ import com.google.gson.Gson;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Stream.of(THING_TYPE_COLOR_TEMPERATURE_LIGHT,
|
||||
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).collect(Collectors.toSet());
|
||||
THING_TYPE_WINDOW_COVERING, THING_TYPE_WARNING_DEVICE);
|
||||
|
||||
private static final double HUE_FACTOR = 65535 / 360.0;
|
||||
private static final double BRIGHTNESS_FACTOR = 2.54;
|
||||
private static final long DEFAULT_COMMAND_EXPIRY_TIME = 250; // in ms
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LightThingHandler.class);
|
||||
@ -94,8 +90,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
private int ctMin = ZCL_CT_MIN;
|
||||
|
||||
public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider) {
|
||||
super(thing, gson);
|
||||
|
||||
super(thing, gson, ResourceType.LIGHTS);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@ -127,27 +122,6 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.registerLightListener(config.id, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.unregisterLightListener(config.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestState() {
|
||||
requestState("lights");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
@ -186,15 +160,15 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
logger.warn("Failed to convert {} to xy-values", command);
|
||||
}
|
||||
newLightState.xy = new double[] { xy[0].doubleValue() / 100.0, xy[1].doubleValue() / 100.0 };
|
||||
newLightState.bri = fromPercentType(hsbCommand.getBrightness());
|
||||
newLightState.bri = Util.fromPercentType(hsbCommand.getBrightness());
|
||||
} else {
|
||||
// default is colormode "hs" (used when colormode "hs" is set or colormode is unknown)
|
||||
newLightState.bri = fromPercentType(hsbCommand.getBrightness());
|
||||
newLightState.bri = Util.fromPercentType(hsbCommand.getBrightness());
|
||||
newLightState.hue = (int) (hsbCommand.getHue().doubleValue() * HUE_FACTOR);
|
||||
newLightState.sat = fromPercentType(hsbCommand.getSaturation());
|
||||
newLightState.sat = Util.fromPercentType(hsbCommand.getSaturation());
|
||||
}
|
||||
} else if (command instanceof PercentType) {
|
||||
newLightState.bri = fromPercentType((PercentType) command);
|
||||
newLightState.bri = Util.fromPercentType((PercentType) command);
|
||||
} else if (command instanceof DecimalType) {
|
||||
newLightState.bri = ((DecimalType) command).intValue();
|
||||
} else {
|
||||
@ -203,7 +177,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
|
||||
// send on/off state together with brightness if not already set or unknown
|
||||
Integer newBri = newLightState.bri;
|
||||
if ((newBri != null) && ((currentOn == null) || ((newBri > 0) != currentOn))) {
|
||||
if (newBri != null) {
|
||||
newLightState.on = (newBri > 0);
|
||||
}
|
||||
|
||||
@ -222,13 +196,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
if (command instanceof DecimalType) {
|
||||
int miredValue = kelvinToMired(((DecimalType) command).intValue());
|
||||
newLightState.ct = constrainToRange(miredValue, ctMin, ctMax);
|
||||
|
||||
if (currentOn != null && !currentOn) {
|
||||
// sending new color temperature is only allowed when light is on
|
||||
newLightState.on = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
newLightState.on = true;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_POSITION:
|
||||
@ -253,31 +221,17 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncHttpClient asyncHttpClient = http;
|
||||
if (asyncHttpClient == null) {
|
||||
return;
|
||||
}
|
||||
String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, "lights", config.id,
|
||||
"state");
|
||||
|
||||
if (newLightState.on != null && !newLightState.on) {
|
||||
// if light shall be off, no other commands are allowed, so reset the new light state
|
||||
newLightState.clear();
|
||||
newLightState.on = false;
|
||||
}
|
||||
|
||||
String json = gson.toJson(newLightState);
|
||||
logger.trace("Sending {} to light {} via {}", json, config.id, url);
|
||||
|
||||
asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
|
||||
sendCommand(newLightState, command, channelUID, () -> {
|
||||
lastCommandExpireTimestamp = System.currentTimeMillis()
|
||||
+ (newLightState.transitiontime != null ? newLightState.transitiontime
|
||||
: DEFAULT_COMMAND_EXPIRY_TIME);
|
||||
lastCommand = newLightState;
|
||||
logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
|
||||
}).exceptionally(e -> {
|
||||
logger.debug("Sending command {} to channel {} failed:", command, channelUID, e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@ -389,19 +343,4 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PercentType toPercentType(int val) {
|
||||
int scaledValue = (int) Math.ceil(val / BRIGHTNESS_FACTOR);
|
||||
if (scaledValue < 0 || scaledValue > 100) {
|
||||
logger.trace("received value {} (converted to {}). Coercing.", val, scaledValue);
|
||||
scaledValue = scaledValue < 0 ? 0 : scaledValue;
|
||||
scaledValue = scaledValue > 100 ? 100 : scaledValue;
|
||||
}
|
||||
logger.debug("val = '{}', scaledValue = '{}'", val, scaledValue);
|
||||
return new PercentType(scaledValue);
|
||||
}
|
||||
|
||||
private int fromPercentType(PercentType val) {
|
||||
return (int) Math.floor(val.doubleValue() * BRIGHTNESS_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ 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.netutils.WebSocketConnection;
|
||||
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@ -77,28 +77,7 @@ public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler<Sens
|
||||
private @Nullable ScheduledFuture<?> lastSeenPollingJob;
|
||||
|
||||
public SensorBaseThingHandler(Thing thing, Gson gson) {
|
||||
super(thing, gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestState() {
|
||||
requestState("sensors");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.registerSensorListener(config.id, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListener() {
|
||||
WebSocketConnection conn = connection;
|
||||
if (conn != null) {
|
||||
conn.unregisterSensorListener(config.id);
|
||||
}
|
||||
super(thing, gson, ResourceType.SENSORS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
package org.openhab.binding.deconz.internal.handler;
|
||||
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
||||
import static org.openhab.binding.deconz.internal.Util.buildUrl;
|
||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||
import static org.openhab.core.library.unit.SmartHomeUnits.PERCENT;
|
||||
|
||||
@ -28,7 +27,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorConfig;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorState;
|
||||
import org.openhab.binding.deconz.internal.dto.ThermostatConfig;
|
||||
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatMode;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@ -121,26 +119,7 @@ public class SensorThermostatThingHandler extends SensorBaseThingHandler {
|
||||
|
||||
}
|
||||
|
||||
AsyncHttpClient asyncHttpClient = http;
|
||||
if (asyncHttpClient == null) {
|
||||
return;
|
||||
}
|
||||
String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, "sensors", config.id,
|
||||
"config");
|
||||
|
||||
String json = gson.toJson(newConfig);
|
||||
logger.trace("Sending {} to sensor {} via {}", json, config.id, url);
|
||||
asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
|
||||
String bodyContent = v.getBody();
|
||||
logger.trace("Result code={}, body={}", v.getResponseCode(), bodyContent);
|
||||
if (!bodyContent.contains("success")) {
|
||||
logger.debug("Sending command {} to channel {} failed: {}", command, channelUID, bodyContent);
|
||||
}
|
||||
|
||||
}).exceptionally(e -> {
|
||||
logger.debug("Sending command {} to channel {} failed:", command, channelUID, e);
|
||||
return null;
|
||||
});
|
||||
sendCommand(newConfig, command, channelUID, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -18,16 +18,12 @@ import static org.openhab.core.library.unit.SIUnits.*;
|
||||
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorConfig;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorState;
|
||||
import org.openhab.binding.deconz.internal.dto.*;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@ -59,13 +55,12 @@ import com.google.gson.Gson;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensorThingHandler extends SensorBaseThingHandler {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_PRESENCE_SENSOR, THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR,
|
||||
THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR, THING_TYPE_TEMPERATURE_SENSOR,
|
||||
THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
|
||||
THING_TYPE_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
|
||||
THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
|
||||
THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_COLOR_CONTROL).collect(Collectors.toSet()));
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR,
|
||||
THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR, THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR,
|
||||
THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
|
||||
THING_TYPE_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
|
||||
THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
|
||||
THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_COLOR_CONTROL);
|
||||
|
||||
private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
|
||||
CHANNEL_TEMPERATURE);
|
||||
|
||||
@ -25,8 +25,10 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
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;
|
||||
|
||||
@ -42,12 +44,16 @@ import com.google.gson.Gson;
|
||||
@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;
|
||||
private final WebSocketConnectionListener connectionListener;
|
||||
private final Map<String, WebSocketMessageListener> sensorListener = new ConcurrentHashMap<>();
|
||||
private final Map<String, WebSocketMessageListener> lightListener = new ConcurrentHashMap<>();
|
||||
private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
|
||||
|
||||
private final Gson gson;
|
||||
private boolean connected = false;
|
||||
|
||||
@ -84,20 +90,12 @@ public class WebSocketConnection {
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
public void registerSensorListener(String sensorID, WebSocketMessageListener listener) {
|
||||
sensorListener.put(sensorID, listener);
|
||||
public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
|
||||
listeners.put(Map.entry(resourceType, sensorID), listener);
|
||||
}
|
||||
|
||||
public void unregisterSensorListener(String sensorID) {
|
||||
sensorListener.remove(sensorID);
|
||||
}
|
||||
|
||||
public void registerLightListener(String lightID, WebSocketMessageListener listener) {
|
||||
lightListener.put(lightID, listener);
|
||||
}
|
||||
|
||||
public void unregisterLightListener(String lightID) {
|
||||
sensorListener.remove(lightID);
|
||||
public void unregisterListener(ResourceType resourceType, String sensorID) {
|
||||
listeners.remove(Map.entry(resourceType, sensorID));
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
@ -111,27 +109,29 @@ public class WebSocketConnection {
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String message) {
|
||||
logger.trace("Raw data received by websocket: {}", message);
|
||||
|
||||
DeconzBaseMessage changedMessage = gson.fromJson(message, DeconzBaseMessage.class);
|
||||
switch (changedMessage.r) {
|
||||
case "sensors":
|
||||
WebSocketMessageListener listener = sensorListener.get(changedMessage.id);
|
||||
if (listener != null) {
|
||||
listener.messageReceived(changedMessage.id, gson.fromJson(message, SensorMessage.class));
|
||||
} else {
|
||||
logger.trace("Couldn't find sensor listener for id {}", changedMessage.id);
|
||||
}
|
||||
break;
|
||||
case "lights":
|
||||
listener = lightListener.get(changedMessage.id);
|
||||
if (listener != null) {
|
||||
listener.messageReceived(changedMessage.id, gson.fromJson(message, LightMessage.class));
|
||||
} else {
|
||||
logger.trace("Couldn't find light listener for id {}", changedMessage.id);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown message type: {}", changedMessage.r);
|
||||
if (changedMessage.r == ResourceType.UNKNOWN) {
|
||||
logger.trace("Received message has unknown resource type. Skipping message.");
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketMessageListener listener = listeners.get(Map.entry(changedMessage.r, changedMessage.id));
|
||||
if (listener == null) {
|
||||
logger.debug(
|
||||
"Couldn't find listener for id {} with resource type {}. Either no thing for this id has been defined or this is a bug.",
|
||||
changedMessage.id, changedMessage.r);
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends DeconzBaseMessage> expectedMessageType = EXPECTED_MESSAGE_TYPES.get(changedMessage.r);
|
||||
if (expectedMessageType == null) {
|
||||
logger.warn("BUG! Could not get expected message type for resource type {}. Please report this incident.",
|
||||
changedMessage.r);
|
||||
return;
|
||||
}
|
||||
|
||||
listener.messageReceived(changedMessage.id, gson.fromJson(message, expectedMessageType));
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
|
||||
@ -28,6 +28,5 @@ public interface WebSocketMessageListener {
|
||||
* @param sensorID The sensor ID (API endpoint)
|
||||
* @param message The received message
|
||||
*/
|
||||
|
||||
void messageReceived(String sensorID, DeconzBaseMessage message);
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.deconz.internal.types;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Type of a group as reported by the REST API for usage in {@link org.openhab.binding.deconz.internal.dto.LightMessage}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum GroupType {
|
||||
LIGHT_GROUP("LightGroup"),
|
||||
UNKNOWN("");
|
||||
|
||||
private static final Map<String, GroupType> MAPPING = Arrays.stream(GroupType.values())
|
||||
.collect(Collectors.toMap(v -> v.type, v -> v));
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GroupType.class);
|
||||
|
||||
private String type;
|
||||
|
||||
GroupType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static GroupType fromString(String s) {
|
||||
GroupType lightType = MAPPING.getOrDefault(s, UNKNOWN);
|
||||
if (lightType == UNKNOWN) {
|
||||
LOGGER.debug("Unknown group type '{}' found. This should be reported.", s);
|
||||
}
|
||||
return lightType;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.deconz.internal.types;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Custom deserializer for {@link GroupType}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupTypeDeserializer implements JsonDeserializer<GroupType> {
|
||||
@Override
|
||||
public GroupType deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
String s = json != null ? json.getAsString() : null;
|
||||
return s == null ? GroupType.UNKNOWN : GroupType.fromString(s);
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,9 @@ package org.openhab.binding.deconz.internal.types;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
@ -24,11 +27,12 @@ import com.google.gson.JsonParseException;
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LightTypeDeserializer implements JsonDeserializer<LightType> {
|
||||
@Override
|
||||
public LightType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
String s = json.getAsString();
|
||||
public LightType deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
String s = json != null ? json.getAsString() : null;
|
||||
return s == null ? LightType.UNKNOWN : LightType.fromString(s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.deconz.internal.types;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ResourceType} defines an enum for websocket messages
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ResourceType {
|
||||
GROUPS("groups", "action"),
|
||||
LIGHTS("lights", "state"),
|
||||
SENSORS("sensors", ""),
|
||||
UNKNOWN("", "");
|
||||
|
||||
private static final Map<String, ResourceType> MAPPING = Arrays.stream(ResourceType.values())
|
||||
.collect(Collectors.toMap(v -> v.identifier, v -> v));
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceType.class);
|
||||
|
||||
private String identifier;
|
||||
private String commandUrl;
|
||||
|
||||
ResourceType(String identifier, String commandUrl) {
|
||||
this.identifier = identifier;
|
||||
this.commandUrl = commandUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the identifier string of this resource type
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the commandUrl part for this resource type
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getCommandUrl() {
|
||||
return commandUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the resource type from a string
|
||||
*
|
||||
* @param s the string
|
||||
* @return the corresponding resource type (or UNKNOWN)
|
||||
*/
|
||||
public static ResourceType fromString(String s) {
|
||||
ResourceType lightType = MAPPING.getOrDefault(s, UNKNOWN);
|
||||
if (lightType == UNKNOWN) {
|
||||
LOGGER.debug("Unknown resource type '{}' found. This should be reported.", s);
|
||||
}
|
||||
return lightType;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.deconz.internal.types;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Custom deserializer for {@link ResourceType}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ResourceTypeDeserializer implements JsonDeserializer<ResourceType> {
|
||||
@Override
|
||||
public ResourceType deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
String s = json != null ? json.getAsString() : null;
|
||||
return s == null ? ResourceType.UNKNOWN : ResourceType.fromString(s);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="deconz"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="lightgroup">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="deconz"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Light Group</label>
|
||||
<channels>
|
||||
<channel typeId="all_on" id="all_on"/>
|
||||
<channel typeId="any_on" id="any_on"/>
|
||||
<channel typeId="alert" id="alert"/>
|
||||
<channel typeId="color" id="color"/>
|
||||
<channel typeId="ct" id="color_temperature"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>uid</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:deconz:sensor"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="alert">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alert</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="all_on">
|
||||
<item-type>Switch</item-type>
|
||||
<label>All On</label>
|
||||
<description>"On" if all lights in this group are "On", otherwise "Off".</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="any_on">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Any On</label>
|
||||
<description>"On" if any light in this group is "On", otherwise "Off".</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>Color</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ct">
|
||||
<item-type>Number</item-type>
|
||||
<label>Color Temperature</label>
|
||||
<state pattern="%d K" min="15" max="100000" step="100"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@ -132,7 +132,7 @@
|
||||
<channel-type id="ct">
|
||||
<item-type>Number</item-type>
|
||||
<label>Color Temperature</label>
|
||||
<state pattern="%d" min="15" max="100000" step="100"/>
|
||||
<state pattern="%d K" min="15" max="100000" step="100"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert">
|
||||
|
||||
@ -34,10 +34,7 @@ import org.openhab.binding.deconz.internal.Util;
|
||||
import org.openhab.binding.deconz.internal.discovery.ThingDiscoveryService;
|
||||
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatMode;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
|
||||
import org.openhab.binding.deconz.internal.types.*;
|
||||
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -68,6 +65,8 @@ public class DeconzTest {
|
||||
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(GroupType.class, new GroupTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ResourceType.class, new ResourceTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
}
|
||||
@ -84,7 +83,7 @@ public class DeconzTest {
|
||||
discoveryService.addDiscoveryListener(discoveryListener);
|
||||
|
||||
discoveryService.stateRequestFinished(bridgeFullState);
|
||||
Mockito.verify(discoveryListener, times(15)).thingDiscovered(any(), any());
|
||||
Mockito.verify(discoveryListener, times(20)).thingDiscovered(any(), any());
|
||||
}
|
||||
|
||||
public static <T> T getObjectFromJson(String filename, Class<T> clazz, Gson gson) throws IOException {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user