Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tradfri][WIP] Adds basic support for IKEA smart blinds FYRTUR and KADRILJ #6167

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 20 additions & 17 deletions bundles/org.openhab.binding.tradfri/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ These are:
| Non-Colour Controller | 0x0820 | 0820 |
| Non-Colour Scene Controller | 0x0830 | 0830 |
| Control Outlet | 0x0010 | 0010 |
| Blind | 0x0999 | 0999 |

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 | | |
| 0999 | | | | X | X | | X |

## Thing Configuration

Expand All @@ -60,14 +62,15 @@ The control outlet supports the `power` 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 blind in percent |

## Full Example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*
* @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
*/
public class TradfriBindingConstants {

Expand All @@ -40,6 +41,7 @@ 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_BLIND = new ThingTypeUID(BINDING_ID, "0999");

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)
Expand All @@ -48,6 +50,10 @@ public class TradfriBindingConstants {
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_BLIND_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_BLIND).collect(Collectors.toSet()));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty line


// List of all Gateway Configuration Properties
public static final String GATEWAY_CONFIG_HOST = "host";
public static final String GATEWAY_CONFIG_PORT = "port";
Expand All @@ -64,12 +70,14 @@ public class TradfriBindingConstants {
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 BLIND = "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,6 +143,7 @@ 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";
Expand Down Expand Up @@ -175,6 +184,7 @@ public class TradfriBindingConstants {
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_BLIND = "7";
public static final String DEVICE_VENDOR = "0";
public static final String DEVICE_MODEL = "1";
public static final String DEVICE_FIRMWARE = "3";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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.TradfriBlindHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriPlugHandler;
import org.openhab.binding.tradfri.internal.handler.TradfriSensorHandler;
import org.osgi.framework.ServiceRegistration;
Expand All @@ -45,14 +46,15 @@
*
* @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())
SUPPORTED_CONTROLLER_TYPES_UIDS.stream(), SUPPORTED_PLUG_TYPES_UIDS.stream(), SUPPORTED_BLIND_TYPES_UIDS.stream())
.reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toSet());

private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
Expand All @@ -74,6 +76,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new TradfriControllerHandler(thing);
} else if (THING_TYPE_MOTION_SENSOR.equals(thingTypeUID)) {
return new TradfriSensorHandler(thing);
} else if (THING_TYPE_BLIND.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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
* @author Andre Fuechsel - fixed the results removal
* @author Manuel Raffel - Added support for blinds
*/
@NonNullByDefault
public class TradfriDiscoveryService extends AbstractDiscoveryService implements DeviceUpdateListener {
Expand Down Expand Up @@ -119,6 +120,9 @@ public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
thingType = THING_TYPE_DIMMABLE_LIGHT;
}
thingId = new ThingUID(thingType, bridge, Integer.toString(id));
} else if(TYPE_BLIND.equals(type) && data.has(BLIND)) {
ThingTypeUID thingType = THING_TYPE_BLIND;
thingId = new ThingUID(thingType, bridge, Integer.toString(id));
} else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
// Smart plug
ThingTypeUID thingType = THING_TYPE_ONOFF_PLUG;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2019 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.tradfri.internal.handler;

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.library.types.PercentType;
import org.eclipse.smarthome.core.library.types.StopMoveType;
import org.eclipse.smarthome.core.library.types.UpDownType;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.openhab.binding.tradfri.internal.model.TradfriBlindData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonElement;

/**
* The {@link TradfriBlindHandler} is responsible for handling commands for individual blinds.
*
* @author Manuel Raffel - Initial contribution
*/
@NonNullByDefault
public class TradfriBlindHandler extends TradfriThingHandler {

private final Logger logger = LoggerFactory.getLogger(TradfriBlindHandler.class);

// keeps track of the current state for handling of stop/move
private @Nullable TradfriBlindData state;

public TradfriBlindHandler(Thing thing) {
super(thing);
}

@Override
public void onUpdate(JsonElement data) {
if (active && !(data.isJsonNull())) {
TradfriBlindData state = new TradfriBlindData(data);
updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);

PercentType position = state.getPosition();
if (position != null) {
updateState(CHANNEL_POSITION, position);
}

DecimalType batteryLevel = state.getBatteryLevel();
if (batteryLevel != null) {
updateState(CHANNEL_BATTERY_LEVEL, batteryLevel);
}

OnOffType batteryLow = state.getBatteryLow();
if (batteryLow != null) {
updateState(CHANNEL_BATTERY_LOW, batteryLow);
}

updateDeviceProperties(state);

this.state = state;

logger.debug(
"Updating thing for blindId {} to state {position: {}, firmwareVersion: {}, modelId: {}, vendor: {}}",
state.getDeviceId(), position, state.getFirmwareVersion(), state.getModelId(),
state.getVendor());
}
}

private void setPosition(PercentType percent) {
TradfriBlindData data = new TradfriBlindData();
data.setPosition(percent);
set(data.getJsonString());
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (active) {
if (command instanceof RefreshType) {
logger.debug("Refreshing channel {}", channelUID);
coapClient.asyncGet(this);
return;
}

switch (channelUID.getId()) {
case CHANNEL_POSITION:
handlePositionCommand(command);
break;
default:
logger.error("Unknown channel UID {}", channelUID);
}
}
}

private void handlePositionCommand(Command command) {
if (command instanceof PercentType) {
setPosition((PercentType) command);
} else if (command instanceof StopMoveType) {
final TradfriBlindData state = this.state;
if (state != null && state.getPosition() != null) {
if(StopMoveType.STOP.equals(command)) {
// setPosition(state.getPosition());
} else {
// (what) TODO (?)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good question. I am not sure if we are able to interrupt an previous command sent to TRADFRI gateway. I hence suggest to ignore these commands and log a info / warning / debug for the user.

According to the Zigbee Cluster Library is should be possible to send a "stop" on protocol level. Do you know if there is another property beside the position ("5536") available in the TRADFRI COAP interface?

}
} else {
logger.debug("Cannot handle stop/move as current state is not known.");
}
} else if (command instanceof UpDownType) {
if (UpDownType.UP.equals(command)) {
setPosition(PercentType.ZERO);
} else {
setPosition(PercentType.HUNDRED);
}
} else {
logger.debug("Cannot handle command {} for channel {}", command, CHANNEL_POSITION);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2019 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.tradfri.internal.model;

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.PercentType;

import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;

/**
* The {@link TradfriBlindData} class is a Java wrapper for the raw JSON data about the blinds state.
*
* @author Manuel Raffel - Initial contribution
*/
@NonNullByDefault
public class TradfriBlindData extends TradfriWirelessDeviceData {
public TradfriBlindData() {
super(BLIND);
}

public TradfriBlindData(JsonElement json) {
super(BLIND, json);
}

public TradfriBlindData setPosition(PercentType position) {
attributes.add(POSITION, new JsonPrimitive(position.intValue()));
return this;
}

public @Nullable PercentType getPosition() {
PercentType result = null;

JsonElement position = attributes.get(POSITION);
if (position != null) {
result = new PercentType(position.getAsInt());
}

return result;
}

public String getJsonString() {
return root.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ thing-type.tradfri.0820.label = Kabelloser Dimmer
thing-type.tradfri.0820.description = Der Kabellose Dimmer liefert Daten wie z.B. die Batterieladung.
thing-type.tradfri.0830.label = Fernbedienung
thing-type.tradfri.0830.description = Die Fernbedienung liefert Daten wie z.B. die Batterieladung.
thing-type.tradfri.0999.label = Rollo
thing-type.tradfri.0999.description = Akkubetriebene Rollo mit einstellbarer Position. Liefert au�erdem Daten wie z.B. die Akkuladung.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batteriebetriebenes Rollo ...


# thing types config
thing-type.config.tradfri.device.id.label = ID des Ger�tes
Expand All @@ -39,3 +41,5 @@ channel-type.tradfri.color_temperature.label = Farbtemperatur
channel-type.tradfri.color_temperature.description = Erm�glicht die Steuerung der Farbtemperatur. Von Tageslichtwei� (0) bis Warmwei� (100).
channel-type.tradfri.color.label = Farbe
channel-type.tradfri.color.description = Erm�glicht die Steuerung der Farbe.
channel-type.tradfri.position.label = Position
channel-type.tradfri.position.description = Erm�glicht die Steuerung der Position von Offen (0) bis Geschlossen (100).