[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.daikin/ @caffineehacker
|
||||||
/bundles/org.openhab.binding.danfossairunit/ @pravussum
|
/bundles/org.openhab.binding.danfossairunit/ @pravussum
|
||||||
/bundles/org.openhab.binding.darksky/ @cweitkamp
|
/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.denonmarantz/ @jwveldhuis
|
||||||
/bundles/org.openhab.binding.digiplex/ @rmichalak
|
/bundles/org.openhab.binding.digiplex/ @rmichalak
|
||||||
/bundles/org.openhab.binding.digitalstrom/ @MichaelOchel @msiegele
|
/bundles/org.openhab.binding.digitalstrom/ @MichaelOchel @msiegele
|
||||||
|
|||||||
@ -42,10 +42,12 @@ Additionally lights, window coverings (blinds) and thermostats are supported:
|
|||||||
| Thermostat | ZHAThermostat | `thermostat` |
|
| Thermostat | ZHAThermostat | `thermostat` |
|
||||||
| Warning Device (Siren) | Warning device | `warningdevice` |
|
| Warning Device (Siren) | Warning device | `warningdevice` |
|
||||||
|
|
||||||
|
Currently only light-groups are supported via the thing-type `lightgroup`.
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
deCONZ software instances are discovered automatically in the same subnet.
|
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.
|
If your device is not discovered, please check the DEBUG log for unknown devices and report your findings.
|
||||||
|
|
||||||
## Thing Configuration
|
## 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.
|
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 `0`, which means "no polling at all".
|
||||||
|
|
||||||
|
|
||||||
`dimmablelight`, `extendedcolorlight`, `colorlight` and `colortemperaturelight` have an additional optional parameter `transitiontime`.
|
`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 transition time is the time to move between two states and is configured in seconds.
|
||||||
The resolution provided is 1/10s.
|
The resolution provided is 1/10s.
|
||||||
If no value is provided, the default value of the device is used.
|
If no value is provided, the default value of the device is used.
|
||||||
|
|
||||||
|
|
||||||
### Textual Thing Configuration - Retrieving an API Key
|
### 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:
|
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
|
Other devices support
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|
||||||
|-------------------|--------------------------|:-----------:|---------------------------------------|-----------------------------------------------|
|
|-------------------|--------------------------|:-----------:|---------------------------------------|-------------------------------------------------|
|
||||||
| brightness | Dimmer | R/W | Brightness of the light | `dimmablelight`, `colortemperaturelight` |
|
| brightness | Dimmer | R/W | Brightness of the light | `dimmablelight`, `colortemperaturelight` |
|
||||||
| switch | Switch | R/W | State of a ON/OFF device | `onofflight` |
|
| switch | Switch | R/W | State of a ON/OFF device | `onofflight` |
|
||||||
| color | Color | R/W | Color of an multi-color light | `colorlight`, `extendedcolorlight` |
|
| 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` |
|
| 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` |
|
| position | Rollershutter | R/W | Position of the blind | `windowcovering` |
|
||||||
| heatsetpoint | Number:Temperature | R/W | Target Temperature in °C | `thermostat` |
|
| heatsetpoint | Number:Temperature | R/W | Target Temperature in °C | `thermostat` |
|
||||||
| valve | Number:Dimensionless | R | Valve position in % | `thermostat` |
|
| valve | Number:Dimensionless | R | Valve position in % | `thermostat` |
|
||||||
| mode | String | R/W | Mode: "auto", "heat" and "off" | `thermostat` |
|
| mode | String | R/W | Mode: "auto", "heat" and "off" | `thermostat` |
|
||||||
| offset | Number | R | Temperature offset for sensor | `thermostat` |
|
| offset | Number | R | Temperature offset for sensor | `thermostat` |
|
||||||
| alert | Switch | R/W | Turn alerts on/off | `warningdevice` |
|
| 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
|
### 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" ]
|
waterleakagesensor basement-water-leakage "Basement Water Leakage" [ id="7" ]
|
||||||
alarmsensor basement-alarm "Basement Alarm Sensor" [ id="8", lastSeenPolling=5 ]
|
alarmsensor basement-alarm "Basement Alarm Sensor" [ id="8", lastSeenPolling=5 ]
|
||||||
dimmablelight livingroom-ceiling "Livingroom Ceiling" [ id="1" ]
|
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_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" }
|
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" }
|
Dimmer Livingroom_Ceiling "Livingroom Ceiling [%d]" <light> { channel="deconz:dimmablelight:homeserver:livingroom-ceiling:brightness" }
|
||||||
|
Color Livingroom "Livingroom Light Control"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class BindingConstants {
|
public class BindingConstants {
|
||||||
|
|
||||||
public static final String BINDING_ID = "deconz";
|
public static final String BINDING_ID = "deconz";
|
||||||
|
|
||||||
// List of all Thing Type UIDs
|
// 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_WINDOW_COVERING = new ThingTypeUID(BINDING_ID, "windowcovering");
|
||||||
public static final ThingTypeUID THING_TYPE_WARNING_DEVICE = new ThingTypeUID(BINDING_ID, "warningdevice");
|
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_PRESENCE = "presence";
|
||||||
public static final String CHANNEL_LAST_UPDATED = "last_updated";
|
public static final String CHANNEL_LAST_UPDATED = "last_updated";
|
||||||
public static final String CHANNEL_LAST_SEEN = "last_seen";
|
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_TEMPERATURE_OFFSET = "offset";
|
||||||
public static final String CHANNEL_VALVE_POSITION = "valve";
|
public static final String CHANNEL_VALVE_POSITION = "valve";
|
||||||
|
|
||||||
|
// group + light channel ids
|
||||||
public static final String CHANNEL_SWITCH = "switch";
|
public static final String CHANNEL_SWITCH = "switch";
|
||||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||||
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
|
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
|
||||||
public static final String CHANNEL_COLOR = "color";
|
public static final String CHANNEL_COLOR = "color";
|
||||||
public static final String CHANNEL_POSITION = "position";
|
public static final String CHANNEL_POSITION = "position";
|
||||||
public static final String CHANNEL_ALERT = "alert";
|
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
|
// Thing configuration
|
||||||
public static final String CONFIG_HOST = "host";
|
public static final String CONFIG_HOST = "host";
|
||||||
public static final String CONFIG_HTTP_PORT = "httpPort";
|
public static final String CONFIG_HTTP_PORT = "httpPort";
|
||||||
public static final String CONFIG_APIKEY = "apikey";
|
public static final String CONFIG_APIKEY = "apikey";
|
||||||
public static final String PROPERTY_UDN = "UDN";
|
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 UNIQUE_ID = "uid";
|
||||||
|
|
||||||
public static final String PROPERTY_CT_MIN = "ctmin";
|
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_MIN = 1;
|
||||||
public static final int ZCL_CT_MAX = 65279; // 0xFEFF
|
public static final int ZCL_CT_MAX = 65279; // 0xFEFF
|
||||||
public static final int ZCL_CT_INVALID = 65535; // 0xFFFF
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
import org.openhab.binding.deconz.internal.handler.*;
|
||||||
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.netutils.AsyncHttpClient;
|
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
||||||
import org.openhab.binding.deconz.internal.types.LightType;
|
import org.openhab.binding.deconz.internal.types.*;
|
||||||
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.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.io.net.http.WebSocketFactory;
|
import org.openhab.core.io.net.http.WebSocketFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
@ -53,7 +47,8 @@ import com.google.gson.GsonBuilder;
|
|||||||
public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||||
.of(DeconzBridgeHandler.SUPPORTED_THING_TYPES, LightThingHandler.SUPPORTED_THING_TYPE_UIDS,
|
.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());
|
.flatMap(Set::stream).collect(Collectors.toSet());
|
||||||
|
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
@ -71,6 +66,8 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
|
|
||||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||||
|
gsonBuilder.registerTypeAdapter(GroupType.class, new GroupTypeDeserializer());
|
||||||
|
gsonBuilder.registerTypeAdapter(ResourceType.class, new ResourceTypeDeserializer());
|
||||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||||
gson = gsonBuilder.create();
|
gson = gsonBuilder.create();
|
||||||
}
|
}
|
||||||
@ -93,6 +90,8 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
return new SensorThingHandler(thing, gson);
|
return new SensorThingHandler(thing, gson);
|
||||||
} else if (SensorThermostatThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
} else if (SensorThermostatThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new SensorThermostatThingHandler(thing, gson);
|
return new SensorThermostatThingHandler(thing, gson);
|
||||||
|
} else if (GroupThingHandler.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID)) {
|
||||||
|
return new GroupThingHandler(thing, gson);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.deconz.internal;
|
package org.openhab.binding.deconz.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.deconz.internal.BindingConstants.BRIGHTNESS_FACTOR;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
@ -22,6 +24,9 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
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
|
* The {@link Util} class defines common utility methods
|
||||||
@ -30,6 +35,8 @@ import org.openhab.core.library.types.DateTimeType;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class Util {
|
public class Util {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
|
||||||
|
|
||||||
public static String buildUrl(String host, int port, String... urlParts) {
|
public static String buildUrl(String host, int port, String... urlParts) {
|
||||||
StringBuilder url = new StringBuilder();
|
StringBuilder url = new StringBuilder();
|
||||||
url.append("http://");
|
url.append("http://");
|
||||||
@ -54,6 +61,33 @@ public class Util {
|
|||||||
return Math.max(min, Math.min(intValue, max));
|
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
|
* 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.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.deconz.internal.Util;
|
import org.openhab.binding.deconz.internal.Util;
|
||||||
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
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.LightMessage;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
||||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
||||||
import org.openhab.binding.deconz.internal.handler.LightThingHandler;
|
import org.openhab.binding.deconz.internal.handler.LightThingHandler;
|
||||||
import org.openhab.binding.deconz.internal.handler.SensorThermostatThingHandler;
|
import org.openhab.binding.deconz.internal.handler.SensorThermostatThingHandler;
|
||||||
import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
|
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.binding.deconz.internal.types.LightType;
|
||||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
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 groupId The id of the light
|
||||||
* @param light The sensor description
|
* @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;
|
final ThingUID bridgeUID = this.bridgeUID;
|
||||||
if (bridgeUID == null) {
|
if (bridgeUID == null) {
|
||||||
logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
|
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<>();
|
Map<String, Object> properties = new HashMap<>();
|
||||||
properties.put("id", lightID);
|
properties.put(CONFIG_ID, lightId);
|
||||||
properties.put(UNIQUE_ID, light.uniqueid);
|
properties.put(UNIQUE_ID, light.uniqueid);
|
||||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, light.swversion);
|
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, light.swversion);
|
||||||
properties.put(Thing.PROPERTY_VENDOR, light.manufacturername);
|
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\\[\\]]", ""));
|
ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
|
||||||
|
|
||||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
|
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();
|
.withProperty(UNIQUE_ID, sensor.uniqueid).withRepresentationProperty(UNIQUE_ID).build();
|
||||||
thingDiscovered(discoveryResult);
|
thingDiscovered(discoveryResult);
|
||||||
}
|
}
|
||||||
@ -268,6 +311,7 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements D
|
|||||||
if (fullState != null) {
|
if (fullState != null) {
|
||||||
fullState.sensors.forEach(this::addSensor);
|
fullState.sensors.forEach(this::addSensor);
|
||||||
fullState.lights.forEach(this::addLight);
|
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, SensorMessage> sensors = Collections.emptyMap();
|
||||||
public Map<String, LightMessage> lights = 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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.
|
* The REST interface and websocket connection are using the same fields.
|
||||||
@ -25,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
public class DeconzBaseMessage {
|
public class DeconzBaseMessage {
|
||||||
// For websocket change events
|
// For websocket change events
|
||||||
public String e = ""; // "changed"
|
public String e = ""; // "changed"
|
||||||
public String r = ""; // "sensors"
|
public ResourceType r = ResourceType.UNKNOWN; // "sensors"
|
||||||
public String t = ""; // "event"
|
public String t = ""; // "event"
|
||||||
public String id = ""; // "3"
|
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.AsyncHttpClient;
|
||||||
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
|
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
|
||||||
import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
|
import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.*;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.openhab.core.thing.ThingStatusInfo;
|
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* It waits for the bridge to come online, grab the websocket connection and bridge configuration
|
||||||
* and registers to the websocket connection as a listener.
|
* 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 David Graeff - Initial contribution
|
||||||
* @author Jan N. Klug - Refactored to abstract class
|
* @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
|
public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
|
||||||
implements WebSocketMessageListener {
|
implements WebSocketMessageListener {
|
||||||
private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
|
||||||
|
protected final ResourceType resourceType;
|
||||||
protected ThingConfig config = new ThingConfig();
|
protected ThingConfig config = new ThingConfig();
|
||||||
protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
|
protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
|
||||||
protected final Gson gson;
|
protected final Gson gson;
|
||||||
@ -59,9 +56,10 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
|||||||
protected @Nullable WebSocketConnection connection;
|
protected @Nullable WebSocketConnection connection;
|
||||||
protected @Nullable AsyncHttpClient http;
|
protected @Nullable AsyncHttpClient http;
|
||||||
|
|
||||||
public DeconzBaseThingHandler(Thing thing, Gson gson) {
|
public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.gson = gson;
|
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
|
@Override
|
||||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||||
@ -86,39 +94,38 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
// 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();
|
unregisterListener();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bridge bridge = getBridge();
|
|
||||||
if (bridge == null) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
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);
|
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);
|
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.
|
* 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;
|
AsyncHttpClient asyncHttpClient = http;
|
||||||
if (asyncHttpClient == null) {
|
if (asyncHttpClient == null) {
|
||||||
return;
|
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);
|
logger.trace("Requesting URL for initial data: {}", url);
|
||||||
|
|
||||||
// Get initial data
|
// Get initial data
|
||||||
@ -165,13 +168,42 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
|
|||||||
}).thenAccept(this::processStateResponse);
|
}).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
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
stopInitializationJob();
|
stopInitializationJob();
|
||||||
WebSocketConnection webSocketConnection = connection;
|
unregisterListener();
|
||||||
if (webSocketConnection != null) {
|
|
||||||
webSocketConnection.unregisterLightListener(config.id);
|
|
||||||
}
|
|
||||||
super.dispose();
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.LightMessage;
|
||||||
import org.openhab.binding.deconz.internal.dto.LightState;
|
import org.openhab.binding.deconz.internal.dto.LightState;
|
||||||
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
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.DecimalType;
|
||||||
import org.openhab.core.library.types.HSBType;
|
import org.openhab.core.library.types.HSBType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
@ -68,12 +66,10 @@ import com.google.gson.Gson;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
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_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 static final long DEFAULT_COMMAND_EXPIRY_TIME = 250; // in ms
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(LightThingHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(LightThingHandler.class);
|
||||||
@ -94,8 +90,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
private int ctMin = ZCL_CT_MIN;
|
private int ctMin = ZCL_CT_MIN;
|
||||||
|
|
||||||
public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider) {
|
public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider) {
|
||||||
super(thing, gson);
|
super(thing, gson, ResourceType.LIGHTS);
|
||||||
|
|
||||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,27 +122,6 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
super.initialize();
|
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
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (command instanceof RefreshType) {
|
if (command instanceof RefreshType) {
|
||||||
@ -186,15 +160,15 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
logger.warn("Failed to convert {} to xy-values", command);
|
logger.warn("Failed to convert {} to xy-values", command);
|
||||||
}
|
}
|
||||||
newLightState.xy = new double[] { xy[0].doubleValue() / 100.0, xy[1].doubleValue() / 100.0 };
|
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 {
|
} else {
|
||||||
// default is colormode "hs" (used when colormode "hs" is set or colormode is unknown)
|
// 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.hue = (int) (hsbCommand.getHue().doubleValue() * HUE_FACTOR);
|
||||||
newLightState.sat = fromPercentType(hsbCommand.getSaturation());
|
newLightState.sat = Util.fromPercentType(hsbCommand.getSaturation());
|
||||||
}
|
}
|
||||||
} else if (command instanceof PercentType) {
|
} else if (command instanceof PercentType) {
|
||||||
newLightState.bri = fromPercentType((PercentType) command);
|
newLightState.bri = Util.fromPercentType((PercentType) command);
|
||||||
} else if (command instanceof DecimalType) {
|
} else if (command instanceof DecimalType) {
|
||||||
newLightState.bri = ((DecimalType) command).intValue();
|
newLightState.bri = ((DecimalType) command).intValue();
|
||||||
} else {
|
} else {
|
||||||
@ -203,7 +177,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
|
|
||||||
// send on/off state together with brightness if not already set or unknown
|
// send on/off state together with brightness if not already set or unknown
|
||||||
Integer newBri = newLightState.bri;
|
Integer newBri = newLightState.bri;
|
||||||
if ((newBri != null) && ((currentOn == null) || ((newBri > 0) != currentOn))) {
|
if (newBri != null) {
|
||||||
newLightState.on = (newBri > 0);
|
newLightState.on = (newBri > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,13 +196,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
if (command instanceof DecimalType) {
|
if (command instanceof DecimalType) {
|
||||||
int miredValue = kelvinToMired(((DecimalType) command).intValue());
|
int miredValue = kelvinToMired(((DecimalType) command).intValue());
|
||||||
newLightState.ct = constrainToRange(miredValue, ctMin, ctMax);
|
newLightState.ct = constrainToRange(miredValue, ctMin, ctMax);
|
||||||
|
newLightState.on = true;
|
||||||
if (currentOn != null && !currentOn) {
|
|
||||||
// sending new color temperature is only allowed when light is on
|
|
||||||
newLightState.on = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_POSITION:
|
case CHANNEL_POSITION:
|
||||||
@ -253,31 +221,17 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
|
|||||||
return;
|
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 (newLightState.on != null && !newLightState.on) {
|
||||||
// if light shall be off, no other commands are allowed, so reset the new light state
|
// if light shall be off, no other commands are allowed, so reset the new light state
|
||||||
newLightState.clear();
|
newLightState.clear();
|
||||||
newLightState.on = false;
|
newLightState.on = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String json = gson.toJson(newLightState);
|
sendCommand(newLightState, command, channelUID, () -> {
|
||||||
logger.trace("Sending {} to light {} via {}", json, config.id, url);
|
|
||||||
|
|
||||||
asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
|
|
||||||
lastCommandExpireTimestamp = System.currentTimeMillis()
|
lastCommandExpireTimestamp = System.currentTimeMillis()
|
||||||
+ (newLightState.transitiontime != null ? newLightState.transitiontime
|
+ (newLightState.transitiontime != null ? newLightState.transitiontime
|
||||||
: DEFAULT_COMMAND_EXPIRY_TIME);
|
: DEFAULT_COMMAND_EXPIRY_TIME);
|
||||||
lastCommand = newLightState;
|
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.SensorMessage;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorState;
|
import org.openhab.binding.deconz.internal.dto.SensorState;
|
||||||
import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
|
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.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
@ -77,28 +77,7 @@ public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler<Sens
|
|||||||
private @Nullable ScheduledFuture<?> lastSeenPollingJob;
|
private @Nullable ScheduledFuture<?> lastSeenPollingJob;
|
||||||
|
|
||||||
public SensorBaseThingHandler(Thing thing, Gson gson) {
|
public SensorBaseThingHandler(Thing thing, Gson gson) {
|
||||||
super(thing, gson);
|
super(thing, gson, ResourceType.SENSORS);
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
package org.openhab.binding.deconz.internal.handler;
|
package org.openhab.binding.deconz.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
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.SIUnits.CELSIUS;
|
||||||
import static org.openhab.core.library.unit.SmartHomeUnits.PERCENT;
|
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.SensorConfig;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorState;
|
import org.openhab.binding.deconz.internal.dto.SensorState;
|
||||||
import org.openhab.binding.deconz.internal.dto.ThermostatConfig;
|
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.binding.deconz.internal.types.ThermostatMode;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
@ -121,26 +119,7 @@ public class SensorThermostatThingHandler extends SensorBaseThingHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncHttpClient asyncHttpClient = http;
|
sendCommand(newConfig, command, channelUID, null);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -18,16 +18,12 @@ import static org.openhab.core.library.unit.SIUnits.*;
|
|||||||
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorConfig;
|
import org.openhab.binding.deconz.internal.dto.*;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorState;
|
|
||||||
import org.openhab.core.library.types.HSBType;
|
import org.openhab.core.library.types.HSBType;
|
||||||
import org.openhab.core.library.types.OpenClosedType;
|
import org.openhab.core.library.types.OpenClosedType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
@ -59,13 +55,12 @@ import com.google.gson.Gson;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SensorThingHandler extends SensorBaseThingHandler {
|
public class SensorThingHandler extends SensorBaseThingHandler {
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR,
|
||||||
.unmodifiableSet(Stream.of(THING_TYPE_PRESENCE_SENSOR, THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR,
|
THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR, THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR,
|
||||||
THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR, THING_TYPE_TEMPERATURE_SENSOR,
|
THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
|
||||||
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_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
|
THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
|
||||||
THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
|
THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_COLOR_CONTROL);
|
||||||
THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_COLOR_CONTROL).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
|
private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
|
||||||
CHANNEL_TEMPERATURE);
|
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.api.annotations.WebSocket;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
|
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.LightMessage;
|
||||||
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
||||||
|
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -42,12 +44,16 @@ import com.google.gson.Gson;
|
|||||||
@WebSocket
|
@WebSocket
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class WebSocketConnection {
|
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 Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
|
||||||
|
|
||||||
private final WebSocketClient client;
|
private final WebSocketClient client;
|
||||||
private final WebSocketConnectionListener connectionListener;
|
private final WebSocketConnectionListener connectionListener;
|
||||||
private final Map<String, WebSocketMessageListener> sensorListener = new ConcurrentHashMap<>();
|
private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
|
||||||
private final Map<String, WebSocketMessageListener> lightListener = new ConcurrentHashMap<>();
|
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
private boolean connected = false;
|
private boolean connected = false;
|
||||||
|
|
||||||
@ -84,20 +90,12 @@ public class WebSocketConnection {
|
|||||||
client.destroy();
|
client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerSensorListener(String sensorID, WebSocketMessageListener listener) {
|
public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
|
||||||
sensorListener.put(sensorID, listener);
|
listeners.put(Map.entry(resourceType, sensorID), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterSensorListener(String sensorID) {
|
public void unregisterListener(ResourceType resourceType, String sensorID) {
|
||||||
sensorListener.remove(sensorID);
|
listeners.remove(Map.entry(resourceType, sensorID));
|
||||||
}
|
|
||||||
|
|
||||||
public void registerLightListener(String lightID, WebSocketMessageListener listener) {
|
|
||||||
lightListener.put(lightID, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterLightListener(String lightID) {
|
|
||||||
sensorListener.remove(lightID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnWebSocketConnect
|
@OnWebSocketConnect
|
||||||
@ -111,27 +109,29 @@ public class WebSocketConnection {
|
|||||||
@OnWebSocketMessage
|
@OnWebSocketMessage
|
||||||
public void onMessage(String message) {
|
public void onMessage(String message) {
|
||||||
logger.trace("Raw data received by websocket: {}", message);
|
logger.trace("Raw data received by websocket: {}", message);
|
||||||
|
|
||||||
DeconzBaseMessage changedMessage = gson.fromJson(message, DeconzBaseMessage.class);
|
DeconzBaseMessage changedMessage = gson.fromJson(message, DeconzBaseMessage.class);
|
||||||
switch (changedMessage.r) {
|
if (changedMessage.r == ResourceType.UNKNOWN) {
|
||||||
case "sensors":
|
logger.trace("Received message has unknown resource type. Skipping message.");
|
||||||
WebSocketMessageListener listener = sensorListener.get(changedMessage.id);
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@OnWebSocketError
|
||||||
|
|||||||
@ -28,6 +28,5 @@ public interface WebSocketMessageListener {
|
|||||||
* @param sensorID The sensor ID (API endpoint)
|
* @param sensorID The sensor ID (API endpoint)
|
||||||
* @param message The received message
|
* @param message The received message
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void messageReceived(String sensorID, DeconzBaseMessage 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 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.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
@ -24,11 +27,12 @@ import com.google.gson.JsonParseException;
|
|||||||
*
|
*
|
||||||
* @author Jan N. Klug - Initial contribution
|
* @author Jan N. Klug - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class LightTypeDeserializer implements JsonDeserializer<LightType> {
|
public class LightTypeDeserializer implements JsonDeserializer<LightType> {
|
||||||
@Override
|
@Override
|
||||||
public LightType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
public LightType deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
|
||||||
throws JsonParseException {
|
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||||
String s = json.getAsString();
|
String s = json != null ? json.getAsString() : null;
|
||||||
return s == null ? LightType.UNKNOWN : LightType.fromString(s);
|
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">
|
<channel-type id="ct">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Color Temperature</label>
|
<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>
|
||||||
|
|
||||||
<channel-type id="alert">
|
<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.discovery.ThingDiscoveryService;
|
||||||
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
||||||
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
|
||||||
import org.openhab.binding.deconz.internal.types.LightType;
|
import org.openhab.binding.deconz.internal.types.*;
|
||||||
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.core.config.discovery.DiscoveryListener;
|
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
@ -68,6 +65,8 @@ public class DeconzTest {
|
|||||||
|
|
||||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||||
|
gsonBuilder.registerTypeAdapter(GroupType.class, new GroupTypeDeserializer());
|
||||||
|
gsonBuilder.registerTypeAdapter(ResourceType.class, new ResourceTypeDeserializer());
|
||||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||||
gson = gsonBuilder.create();
|
gson = gsonBuilder.create();
|
||||||
}
|
}
|
||||||
@ -84,7 +83,7 @@ public class DeconzTest {
|
|||||||
discoveryService.addDiscoveryListener(discoveryListener);
|
discoveryService.addDiscoveryListener(discoveryListener);
|
||||||
|
|
||||||
discoveryService.stateRequestFinished(bridgeFullState);
|
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 {
|
public static <T> T getObjectFromJson(String filename, Class<T> clazz, Gson gson) throws IOException {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user