Skip to content

Commit

Permalink
[tradfri] Added basic support for IKEA blinds FYRTUR and KADRILJ (ope…
Browse files Browse the repository at this point in the history
…nhab#6977)

* Adds basic support for IKEA smart blinds FYRTUR and KADRILJ

* adds the "blind" thing and associated data
* temporarily assigned zigbee id "0999", needs review

Signed-off-by: Manuel Raffel <manidu@outlook.com>

* Restores ISO 8859-1 encoding of .properties file

Signed-off-by: Manuel Raffel <manidu@outlook.com>

* - Incorporated review comments
- Added discovery
- Added check for percent values
- Aplied formatter
- Fixed unit tests

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Added repeater

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Added TRADFRI open/close remote

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Refactoring of discovery service

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Added handling for STOP command

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Incorporated changes from review

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

Co-authored-by: Manuel <manraf@users.noreply.github.com>
  • Loading branch information
2 people authored and andrewfg committed Aug 31, 2020
1 parent bf753ce commit f38ef26
Show file tree
Hide file tree
Showing 20 changed files with 418 additions and 132 deletions.
48 changes: 29 additions & 19 deletions bundles/org.openhab.binding.tradfri/README.md
Expand Up @@ -22,18 +22,22 @@ These are:
| Non-Colour Controller | 0x0820 | 0820 |
| Non-Colour Scene Controller | 0x0830 | 0830 |
| Control Outlet | 0x0010 | 0010 |
| Window Covering Device | 0x0202 | 0202 |
| Window Covering Controller | 0x0202 | 0203 |

The following matrix lists the capabilities (channels) for each of the supported lighting device types:

| Thing type | Brightness | Color | Color Temperature | Battery Level | Battery Low | Power |
|-------------|:----------:|:-----:|:-----------------:|:-------------:|:-----------:|:-----:|
| 0010 | | | | | | X |
| 0100 | X | | | | | |
| 0220 | X | | X | | | |
| 0210 | | X | X | | | |
| 0107 | | | | X | X | |
| 0820 | | | | X | X | |
| 0830 | | | | X | X | |
| Thing type | Brightness | Color | Color Temperature | Battery Level | Battery Low | Power | Position |
|-------------|:----------:|:-----:|:-----------------:|:-------------:|:-----------:|:-----:|:---------|
| 0010 | | | | | | X | |
| 0100 | X | | | | | | |
| 0220 | X | | X | | | | |
| 0210 | | X | X | | | | |
| 0107 | | | | X | X | | |
| 0820 | | | | X | X | | |
| 0830 | | | | X | X | | |
| 0202 | | | | X | X | | X |
| 0203 | | | | X | X | | |

## Thing Configuration

Expand All @@ -59,16 +63,19 @@ The remote control and the motion sensor supports the `battery_level` and `batte

The control outlet supports the `power` channel.

A blind or curtain supports beside `battery_level` and `battery_low` channels a `positon` channel.

Refer to the matrix above.

| Channel Type ID | Item Type | Description |
|-------------------|-----------|--------------------------------------------------|
| brightness | Dimmer | The brightness of the bulb in percent |
| color_temperature | Dimmer | color temperature from 0% = cold to 100% = warm |
| color | Color | full color |
| battery_level | Number | battery level (in %) |
| battery_low | Switch | battery low warning (<=10% = ON, >10% = OFF) |
| power | Switch | power switch |
| Channel Type ID | Item Type | Description |
|-------------------|---------------|--------------------------------------------------------|
| brightness | Dimmer | The brightness of the bulb in percent |
| color_temperature | Dimmer | color temperature from 0% = cold to 100% = warm |
| color | Color | full color |
| battery_level | Number | battery level (in %) |
| battery_low | Switch | battery low warning (<=10% = ON, >10% = OFF) |
| power | Switch | power switch |
| position | Rollershutter | position of the blinds from 0% = open to 100% = closed |

## Full Example

Expand All @@ -81,6 +88,7 @@ Bridge tradfri:gateway:mygateway [ host="192.168.0.177", code="EHPW5rIJKyXFgjH3"
0210 myColorBulb "My Color Bulb" [ id=65539 ]
0830 myRemoteControl "My Remote Control" [ id=65545 ]
0010 myControlOutlet "My Control Outlet" [ id=65542 ]
0202 myBlinds "My Blinds" [ id=65547 ]
}
```

Expand All @@ -94,6 +102,7 @@ Color ColorLight { channel="tradfri:0210:mygateway:myColorBulb:color" }
Number RemoteControlBatteryLevel { channel="tradfri:0830:mygateway:myRemoteControl:battery_level" }
Switch RemoteControlBatteryLow { channel="tradfri:0830:mygateway:myRemoteControl:battery_low" }
Switch ControlOutlet { channel="tradfri:0010:mygateway:myControlOutlet:power" }
Rollershutter BlindPosition { channel="tradfri:0202:mygateway:myBlinds:position" }
```

demo.sitemap:
Expand All @@ -106,9 +115,10 @@ sitemap demo label="Main Menu"
Slider item=Light2_Brightness label="Light2 Brightness [%.1f %%]"
Slider item=Light2_ColorTemperature label="Light2 Color Temperature [%.1f %%]"
Colorpicker item=ColorLight label="Color"
Text item=RemoteControlBatteryLevel label="Battery level [%d %%]"
Switch item=RemoteControlBatteryLow label="Battery low warning"
Text item=RemoteControlBatteryLevel label="Battery Level [%d %%]"
Switch item=RemoteControlBatteryLow label="Battery Low Warning"
Switch item=ControlOutlet label="Power Switch"
Rollershutter item)BlindPosition label="Blind Position"
}
}
```
4 changes: 3 additions & 1 deletion bundles/org.openhab.binding.tradfri/pom.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

Expand Down
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.tradfri.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;

import com.google.gson.JsonObject;
Expand All @@ -22,6 +23,7 @@
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public interface DeviceUpdateListener {

/**
Expand Down
Expand Up @@ -17,6 +17,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.smarthome.core.thing.ThingTypeUID;

/**
Expand All @@ -25,7 +26,9 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
* @author Manuel Raffel - Added support for blinds
*/
@NonNullByDefault
public class TradfriBindingConstants {

private static final String BINDING_ID = "tradfri";
Expand All @@ -40,13 +43,16 @@ public class TradfriBindingConstants {
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "0820");
public static final ThingTypeUID THING_TYPE_REMOTE_CONTROL = new ThingTypeUID(BINDING_ID, "0830");
public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "0107");
public static final ThingTypeUID THING_TYPE_BLINDS = new ThingTypeUID(BINDING_ID, "0202");
public static final ThingTypeUID THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL = new ThingTypeUID(BINDING_ID, "0203");

public static final Set<ThingTypeUID> SUPPORTED_LIGHT_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_TEMP_LIGHT, THING_TYPE_COLOR_LIGHT)
.collect(Collectors.toSet()));

public static final Set<ThingTypeUID> SUPPORTED_PLUG_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_ONOFF_PLUG).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_PLUG_TYPES_UIDS = Collections.singleton(THING_TYPE_ONOFF_PLUG);

public static final Set<ThingTypeUID> SUPPORTED_BLINDS_TYPES_UIDS = Collections.singleton(THING_TYPE_BLINDS);

// List of all Gateway Configuration Properties
public static final String GATEWAY_CONFIG_HOST = "host";
Expand All @@ -56,20 +62,34 @@ public class TradfriBindingConstants {
public static final String GATEWAY_CONFIG_PRE_SHARED_KEY = "preSharedKey";

// Not yet used - included for future support
public static final Set<ThingTypeUID> SUPPORTED_CONTROLLER_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_DIMMER, THING_TYPE_REMOTE_CONTROL, THING_TYPE_MOTION_SENSOR).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_CONTROLLER_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_DIMMER, THING_TYPE_REMOTE_CONTROL,
THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL, THING_TYPE_MOTION_SENSOR).collect(Collectors.toSet()));

public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_TYPES_UIDS = Collections.singleton(GATEWAY_TYPE_UID);

public static final Set<ThingTypeUID> SUPPORTED_DEVICE_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(SUPPORTED_LIGHT_TYPES_UIDS.stream(), SUPPORTED_CONTROLLER_TYPES_UIDS.stream(),
SUPPORTED_PLUG_TYPES_UIDS.stream(), SUPPORTED_BLINDS_TYPES_UIDS.stream())
.reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toSet()));

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.concat(SUPPORTED_BRIDGE_TYPES_UIDS.stream(), SUPPORTED_DEVICE_TYPES_UIDS.stream())
.collect(Collectors.toSet()));

// List of all Channel IDs
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_POSITION = "position";
public static final String CHANNEL_BATTERY_LEVEL = "battery_level";
public static final String CHANNEL_BATTERY_LOW = "battery_low";

// IPSO Objects
public static final String DEVICES = "15001";
public static final String AUTH_PATH = "9063";
public static final String BLINDS = "15015";
public static final String CLIENT_IDENTITY_PROPOSED = "9090";
public static final String COLOR = "5706";
public static final String COLOR_X = "5709";
Expand Down Expand Up @@ -135,9 +155,11 @@ public class TradfriBindingConstants {
public static final String OTA_UPDATE_STATE = "9054";
public static final String PLUG = "3312";
public static final String POWER_FACTOR = "5820";
public static final String POSITION = "5536";
public static final String REACHABILITY_STATE = "9019";
public static final String REBOOT = "9030";
public static final String REPEAT_DAYS = "9041";
public static final String REPEATER = "15014";
public static final String RESET = "9031";
public static final String RESET_MIN_MAX_MSR = "5605";
public static final String SCENE = "15005";
Expand All @@ -158,6 +180,7 @@ public class TradfriBindingConstants {
public static final String START_ACTION = "9042";
public static final String START_TIME_HR = "9046";
public static final String START_TIME_MN = "9047";
public static final String STOP_TRIGGER = "5523";
public static final String SWITCH = "15009";
public static final String TIME_ARRAY = "9994";
public static final String TIME_REMAINING_IN_SECONDS = "9024";
Expand All @@ -172,9 +195,12 @@ public class TradfriBindingConstants {
public static final int WAKE_UP_SMART_TASK = 3;

public static final String TYPE_SWITCH = "0";
public static final String TYPE_REMOTE = "1";
public static final String TYPE_LIGHT = "2";
public static final String TYPE_PLUG = "3";
public static final String TYPE_SENSOR = "4";
public static final String TYPE_REPEATER = "6";
public static final String TYPE_BLINDS = "7";
public static final String DEVICE_VENDOR = "0";
public static final String DEVICE_MODEL = "1";
public static final String DEVICE_FIRMWARE = "3";
Expand Down
Expand Up @@ -32,8 +32,8 @@ public class TradfriColor {
// Tradfri uses the CIE color space (see https://en.wikipedia.org/wiki/CIE_1931_color_space),
// which uses x,y-coordinates.
// Its own app comes with 3 predefined color temperature settings (0,1,2), which have those values:
private final static double[] PRESET_X = new double[] { 24933.0, 30138.0, 33137.0 };
private final static double[] PRESET_Y = new double[] { 24691.0, 26909.0, 27211.0 };
private static final double[] PRESET_X = new double[] { 24933.0, 30138.0, 33137.0 };
private static final double[] PRESET_Y = new double[] { 24691.0, 26909.0, 27211.0 };

/**
* CIE XY color values in the tradfri range 0 to 65535.
Expand Down Expand Up @@ -178,7 +178,6 @@ public PercentType getColorTemperature() {
* @return {@link PercentType} with brightness level (0 = light is off, 1 = lowest, 100 = highest)
*/
public static PercentType xyBrightnessToPercentType(int xyBrightness) {

if (xyBrightness > 254) {
return PercentType.HUNDRED;
} else if (xyBrightness < 0) {
Expand Down
Expand Up @@ -14,49 +14,33 @@

import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.config.discovery.DiscoveryService;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
import org.openhab.binding.tradfri.internal.discovery.TradfriDiscoveryService;
import org.openhab.binding.tradfri.internal.handler.TradfriBlindHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriControllerHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriLightHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriPlugHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriSensorHandler;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;

/**
* The {@link TradfriHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Kai Kreuzer - Initial contribution
* @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
* @author Manuel Raffel - Added support for blinds
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tradfri")
@NonNullByDefault
public class TradfriHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(Stream.of(GATEWAY_TYPE_UID), SUPPORTED_LIGHT_TYPES_UIDS.stream(),
SUPPORTED_CONTROLLER_TYPES_UIDS.stream(), SUPPORTED_PLUG_TYPES_UIDS.stream())
.reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toSet());

private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand All @@ -67,13 +51,14 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (GATEWAY_TYPE_UID.equals(thingTypeUID)) {
TradfriGatewayHandler handler = new TradfriGatewayHandler((Bridge) thing);
registerDiscoveryService(handler);
return handler;
} else if (THING_TYPE_DIMMER.equals(thingTypeUID) || THING_TYPE_REMOTE_CONTROL.equals(thingTypeUID)) {
return new TradfriGatewayHandler((Bridge) thing);
} else if (THING_TYPE_DIMMER.equals(thingTypeUID) || THING_TYPE_REMOTE_CONTROL.equals(thingTypeUID)
|| THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL.equals(thingTypeUID)) {
return new TradfriControllerHandler(thing);
} else if (THING_TYPE_MOTION_SENSOR.equals(thingTypeUID)) {
return new TradfriSensorHandler(thing);
} else if (THING_TYPE_BLINDS.equals(thingTypeUID)) {
return new TradfriBlindHandler(thing);
} else if (SUPPORTED_LIGHT_TYPES_UIDS.contains(thingTypeUID)) {
return new TradfriLightHandler(thing);
} else if (SUPPORTED_PLUG_TYPES_UIDS.contains(thingTypeUID)) {
Expand All @@ -82,30 +67,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return null;
}

@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof TradfriGatewayHandler) {
unregisterDiscoveryService((TradfriGatewayHandler) thingHandler);
}
}

private synchronized void registerDiscoveryService(TradfriGatewayHandler bridgeHandler) {
TradfriDiscoveryService discoveryService = new TradfriDiscoveryService(bridgeHandler);
discoveryService.activate();
this.discoveryServiceRegs.put(bridgeHandler.getThing().getUID(), getBundleContext()
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<String, Object>()));
}

@SuppressWarnings("null")
private synchronized void unregisterDiscoveryService(TradfriGatewayHandler bridgeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(bridgeHandler.getThing().getUID());
if (serviceReg != null) {
TradfriDiscoveryService service = (TradfriDiscoveryService) getBundleContext()
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
Expand Up @@ -15,7 +15,6 @@
import static org.eclipse.smarthome.core.thing.Thing.*;
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -54,11 +53,11 @@ public class TradfriDiscoveryParticipant implements MDNSDiscoveryParticipant {
* gw:001122334455, gw-001122334455, gw:00-11-22-33-44-55, gw-001122334455ServiceName
*
*/
private final Pattern GATEWAY_NAME_REGEX_PATTERN = Pattern.compile("(gw[:-]{1}([a-f0-9]{2}[-]?){6}){1}");
private static final Pattern GATEWAY_NAME_REGEX_PATTERN = Pattern.compile("(gw[:-]{1}([a-f0-9]{2}[-]?){6}){1}");

@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(GATEWAY_TYPE_UID);
return SUPPORTED_BRIDGE_TYPES_UIDS;
}

@Override
Expand Down

0 comments on commit f38ef26

Please sign in to comment.