added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.tradfri/.classpath
Normal file
32
bundles/org.openhab.binding.tradfri/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.tradfri/.project
Normal file
23
bundles/org.openhab.binding.tradfri/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.tradfri</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.tradfri/NOTICE
Normal file
13
bundles/org.openhab.binding.tradfri/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
124
bundles/org.openhab.binding.tradfri/README.md
Normal file
124
bundles/org.openhab.binding.tradfri/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# TRÅDFRI Binding
|
||||
|
||||
This binding integrates the IKEA TRÅDFRI gateway and devices connected to it (such as dimmable LED bulbs).
|
||||
|
||||
## Supported Things
|
||||
|
||||
Beside the gateway (thing type "gateway"), the binding currently supports colored bulbs, dimmable warm white bulbs as well as white spectrum bulbs and control outlets.
|
||||
The binding also supports read-only data from remote controls and motion sensors (e.g. the battery status).
|
||||
The TRÅDFRI controller and sensor devices currently cannot be observed right away because they are communicating directly with the bulbs or lamps without routing their commands through the gateway.
|
||||
This makes it nearly impossible to trigger events for pressed buttons.
|
||||
We only can access some static data like the present status or battery level.
|
||||
|
||||
The thing type ids are defined according to the lighting devices defined for ZigBee LightLink ([see page 24, table 2](https://www.nxp.com/documents/user_manual/JN-UG-3091.pdf).
|
||||
These are:
|
||||
|
||||
| Device type | ZigBee Device ID | Thing type |
|
||||
|---------------------------------|------------------|------------|
|
||||
| Dimmable Light | 0x0100 | 0100 |
|
||||
| Colour Temperature Light | 0x0220 | 0220 |
|
||||
| Extended Colour Light | 0x0210 | 0210 |
|
||||
| Occupancy Sensor | 0x0107 | 0107 |
|
||||
| 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 | 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
|
||||
|
||||
For first pairing - the gateway requires a `host` parameter for the hostname or IP address and a `code`, which is the security code that is printed on the bottom of the gateway.
|
||||
Optionally, a `port` can be configured, but any standard gateway uses the default port 5684.
|
||||
The gateway requires at least firmware version 1.2.42 to connect to this binding.
|
||||
|
||||
The `code` is used during the initialization for retrieving unique identity and pre-shared key from the gateway and then it is discarded from the configuration.
|
||||
The newly created authentication data is stored in advanced parameters `identity` and `preSharedKey`.
|
||||
On each initialization if the code is present in the thing configuration - the `identity` and `preSharedKey` are recreated and the `code` is again discarded.
|
||||
|
||||
The devices require only a single (integer) parameter, which is their instance id. Unfortunately, this is not displayed anywhere in the IKEA app, but it seems that they are sequentially numbered starting with 65537 for the first device. If in doubt, use the auto-discovered things to find out the correct instance ids.
|
||||
|
||||
## Channels
|
||||
|
||||
The dimmable bulbs support the `brightness` channel.
|
||||
The white spectrum bulbs additionally also support the `color_temperature` channel.
|
||||
|
||||
Full color bulbs support the `color_temperature` and `color` channels.
|
||||
Brightness can be changed with the `color` channel.
|
||||
|
||||
The remote control and the motion sensor supports the `battery_level` and `battery_low` channels for reading the battery status.
|
||||
|
||||
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 |
|
||||
| position | Rollershutter | position of the blinds from 0% = open to 100% = closed |
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Bridge tradfri:gateway:mygateway [ host="192.168.0.177", code="EHPW5rIJKyXFgjH3" ] {
|
||||
0100 myDimmableBulb "My Dimmable Bulb" [ id=65537 ]
|
||||
0220 myColorTempBulb "My Color Temp Bulb" [ id=65538 ]
|
||||
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 ]
|
||||
}
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Dimmer Light1 { channel="tradfri:0100:mygateway:myDimmableBulb:brightness" }
|
||||
Dimmer Light2_Brightness { channel="tradfri:0220:mygateway:myColorTempBulb:brightness" }
|
||||
Dimmer Light2_ColorTemperature { channel="tradfri:0220:mygateway:myColorTempBulb:color_temperature" }
|
||||
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:
|
||||
|
||||
```
|
||||
sitemap demo label="Main Menu"
|
||||
{
|
||||
Frame {
|
||||
Slider item=Light1 label="Light1 Brightness [%.1f %%]"
|
||||
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"
|
||||
Switch item=ControlOutlet label="Power Switch"
|
||||
Switch item=BlindPosition label="Blind Position [%d]"
|
||||
}
|
||||
}
|
||||
```
|
||||
17
bundles/org.openhab.binding.tradfri/pom.xml
Normal file
17
bundles/org.openhab.binding.tradfri/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.tradfri</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: TRÅDFRI Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.tradfri-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-tradfri" description="TRÅDFRI Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-coap</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tradfri/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link CoapCallback} is receives coap response data asynchronously.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface CoapCallback {
|
||||
|
||||
/**
|
||||
* This is being called, if new data is received from a CoAP request.
|
||||
*
|
||||
* @param data the received json structure
|
||||
*/
|
||||
public void onUpdate(JsonElement data);
|
||||
|
||||
/**
|
||||
* Tells the listener to set the Thing status.
|
||||
* Should usually be directly passed on to updateStatus() on the ThingHandler.
|
||||
*
|
||||
* @param status The thing status
|
||||
* @param statusDetail the status detail
|
||||
*/
|
||||
public void setStatus(ThingStatus status, ThingStatusDetail statusDetail);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* {@link DeviceUpdateListener} can register on the {@link TradfriGatewayHandler} to be
|
||||
* informed about details about devices.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DeviceUpdateListener {
|
||||
|
||||
/**
|
||||
* This method is called when new device information is received.
|
||||
*
|
||||
* @param instanceId The instance id of the device
|
||||
* @param data the json data describing the device
|
||||
*/
|
||||
public void onUpdate(String instanceId, JsonObject data);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link TradfriBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @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";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID GATEWAY_TYPE_UID = new ThingTypeUID(BINDING_ID, "gateway");
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_ONOFF_PLUG = new ThingTypeUID(BINDING_ID, "0010");
|
||||
public static final ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "0100");
|
||||
public static final ThingTypeUID THING_TYPE_COLOR_TEMP_LIGHT = new ThingTypeUID(BINDING_ID, "0220");
|
||||
public static final ThingTypeUID THING_TYPE_COLOR_LIGHT = new ThingTypeUID(BINDING_ID, "0210");
|
||||
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.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";
|
||||
public static final String GATEWAY_CONFIG_PORT = "port";
|
||||
public static final String GATEWAY_CONFIG_CODE = "code";
|
||||
public static final String GATEWAY_CONFIG_IDENTITY = "identity";
|
||||
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_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";
|
||||
public static final String COLOR_Y = "5710";
|
||||
public static final String COMMISSIONING_MODE = "9061";
|
||||
public static final String CREATED_AT = "9002";
|
||||
public static final String CUM_ACTIVE_POWER = "5805";
|
||||
public static final String CURRENT_TIMESTAMP = "9059";
|
||||
public static final int DEFAULT_DIMMER_TRANSITION_TIME = 5;
|
||||
public static final String DEVICE = "3";
|
||||
public static final String DIMMER = "5851";
|
||||
public static final int DIMMER_MAX = 254;
|
||||
public static final int DIMMER_MIN = 0;
|
||||
public static final String END_ACTION = "9043";
|
||||
public static final String END_TIME_HR = "9048";
|
||||
public static final String END_TIME_MN = "9049";
|
||||
public static final String ERROR_TAG = "errorcode";
|
||||
public static final String FORCE_CHECK_OTA_UPDATE = "9032";
|
||||
public static final String GATEWAY = "15011";
|
||||
public static final String GATEWAY_DETAILS = "15012";
|
||||
public static final String GATEWAY_NAME = "9035";
|
||||
public static final int GATEWAY_REBOOT_NOTIFICATION = 1003;
|
||||
public static final String GATEWAY_REBOOT_NOTIFICATION_TYPE = "9052";
|
||||
public static final String GATEWAY_TIME_SOURCE = "9071";
|
||||
public static final String GATEWAY_UPDATE_DETAILS_URL = "9056";
|
||||
public static final String GATEWAY_UPDATE_PROGRESS = "9055";
|
||||
public static final String GROUPS = "15004";
|
||||
public static final String GROUP_ID = "9038";
|
||||
public static final String GROUP_LINK_ARRAY = "9995";
|
||||
public static final String GROUP_SETTINGS = "9045";
|
||||
public static final String HS_ACCESSORY_LINK = "9018";
|
||||
public static final String HS_LINK = "15002";
|
||||
public static final String IKEA_MOODS = "9068";
|
||||
public static final String INSTANCE_ID = "9003";
|
||||
public static final String LAST_SEEN = "9020";
|
||||
public static final String LIGHT = "3311";
|
||||
public static final int LIGHTS_OFF_SMART_TASK = 2;
|
||||
public static final String LIGHT_SETTING = "15013";
|
||||
public static final int LOSS_OF_INTERNET_CONNECTIVITY = 5001;
|
||||
public static final String MASTER_TOKEN_TAG = "9036";
|
||||
public static final String MAX_MSR_VALUE = "5602";
|
||||
public static final String MAX_RNG_VALUE = "5604";
|
||||
public static final String MIN_MSR_VALUE = "5601";
|
||||
public static final String MIN_RNG_VALUE = "5603";
|
||||
public static final String NAME = "9001";
|
||||
public static final int NEW_FIRMWARE_AVAILABLE = 1001;
|
||||
public static final String NEW_PSK_BY_GW = "9091";
|
||||
public static final String NOTIFICATION_EVENT = "9015";
|
||||
public static final String NOTIFICATION_NVPAIR = "9017";
|
||||
public static final String NOTIFICATION_STATE = "9014";
|
||||
public static final int NOT_AT_HOME_SMART_TASK = 1;
|
||||
public static final String NTP_SERVER = "9023";
|
||||
public static final String ONOFF = "5850";
|
||||
public static final String ON_TIME = "5852";
|
||||
public static final String OPEN = "1";
|
||||
public static final int OPTION_APP_TOKEN = 2051;
|
||||
public static final int OTA_CRITICAL = 1;
|
||||
public static final int OTA_FORCED = 5;
|
||||
public static final int OTA_NORMAL = 0;
|
||||
public static final int OTA_REQUIRED = 2;
|
||||
public static final String OTA_TYPE = "9066";
|
||||
public static final String OTA_UPDATE = "9037";
|
||||
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";
|
||||
public static final String SCENE_ACTIVATE_FLAG = "9058";
|
||||
public static final String SCENE_ID = "9039";
|
||||
public static final String SCENE_INDEX = "9057";
|
||||
public static final String SCENE_LINK = "9009";
|
||||
public static final String SENSOR = "3300";
|
||||
public static final String SENSOR_TYPE = "5751";
|
||||
public static final String SENSOR_VALUE = "5700";
|
||||
public static final String SESSION_ID = "9033";
|
||||
public static final String SESSION_LENGTH = "9064";
|
||||
public static final String SHORTCUT_ICON_REFERENCE_TYPE = "9051";
|
||||
public static final String SMART_TASK_ACTION = "9050";
|
||||
public static final String SMART_TASK_TEMPLATE = "9016";
|
||||
public static final int SMART_TASK_TRIGGERED_EVENT = 1002;
|
||||
public static final String SMART_TASK_TYPE = "9040";
|
||||
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";
|
||||
public static final String TRANSITION_TIME = "5712";
|
||||
public static final String TRIGGER_TIME_INTERVAL = "9044";
|
||||
public static final String TYPE = "5750";
|
||||
public static final String UNIT = "5701";
|
||||
public static final String UPDATE_ACCEPTED_TIMESTAMP = "9069";
|
||||
public static final String UPDATE_FIRMWARE = "9034";
|
||||
public static final String USE_CURRENT_LIGHT_SETTINGS = "9070";
|
||||
public static final String VERSION = "9029";
|
||||
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";
|
||||
public static final String DEVICE_BATTERY_LEVEL = "9";
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.californium.core.CoapClient;
|
||||
import org.eclipse.californium.core.CoapObserveRelation;
|
||||
import org.eclipse.californium.core.coap.MediaTypeRegistry;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link TradfriCoapClient} provides some convenience features over the
|
||||
* plain {@link CoapClient} from californium.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriCoapClient extends CoapClient {
|
||||
|
||||
private static final long TIMEOUT = 2000;
|
||||
private static final int DEFAULT_DELAY_MILLIS = 600;
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriCoapClient.class);
|
||||
private final LinkedList<PayloadCallbackPair> commandsQueue = new LinkedList<>();
|
||||
private @Nullable Future<?> job;
|
||||
|
||||
public TradfriCoapClient(URI uri) {
|
||||
super(uri);
|
||||
setTimeout(TIMEOUT);
|
||||
}
|
||||
|
||||
private void executeCommands() {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (commandsQueue) {
|
||||
if (commandsQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
PayloadCallbackPair payloadCallbackPair = commandsQueue.poll();
|
||||
logger.debug("CoAP PUT request\nuri: {}\npayload: {}", getURI(), payloadCallbackPair.payload);
|
||||
put(new TradfriCoapHandler(payloadCallbackPair.callback), payloadCallbackPair.payload,
|
||||
MediaTypeRegistry.TEXT_PLAIN);
|
||||
}
|
||||
Thread.sleep(DEFAULT_DELAY_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("commandExecutorThread was interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts observation of the resource and uses the given callback to provide updates.
|
||||
*
|
||||
* @param callback the callback to use for updates
|
||||
*/
|
||||
public CoapObserveRelation startObserve(CoapCallback callback) {
|
||||
return observe(new TradfriCoapHandler(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a GET on the resource and provides the result through a {@link CompletableFuture}.
|
||||
*
|
||||
* @return the future that will hold the result
|
||||
*/
|
||||
public CompletableFuture<String> asyncGet() {
|
||||
logger.debug("CoAP GET request\nuri: {}", getURI());
|
||||
CompletableFuture<String> future = new CompletableFuture<>();
|
||||
get(new TradfriCoapHandler(future));
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a GET on the resource and provides the result to a given callback.
|
||||
*
|
||||
* @param callback the callback to use for the response
|
||||
*/
|
||||
public void asyncGet(CoapCallback callback) {
|
||||
logger.debug("CoAP GET request\nuri: {}", getURI());
|
||||
get(new TradfriCoapHandler(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a PUT on the resource with a payload, provides the result to a given callback
|
||||
* and blocks sending new requests to the resource for specified amount of milliseconds.
|
||||
*
|
||||
* @param payload the payload to send with the PUT request
|
||||
* @param callback the callback to use for the response
|
||||
* @param scheduler scheduler to be used for sending commands
|
||||
*/
|
||||
public void asyncPut(String payload, CoapCallback callback, ScheduledExecutorService scheduler) {
|
||||
asyncPut(new PayloadCallbackPair(payload, callback), scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a PUT on the resource with a payload and provides the result to a given callback.
|
||||
*
|
||||
* @param payloadCallbackPair object which holds the payload and callback process the PUT request
|
||||
* @param scheduler scheduler to be used for sending commands
|
||||
*/
|
||||
public void asyncPut(PayloadCallbackPair payloadCallbackPair, ScheduledExecutorService scheduler) {
|
||||
synchronized (this.commandsQueue) {
|
||||
if (this.commandsQueue.isEmpty()) {
|
||||
this.commandsQueue.offer(payloadCallbackPair);
|
||||
final Future<?> job = this.job;
|
||||
if (job == null || job.isDone()) {
|
||||
this.job = scheduler.submit(() -> executeCommands());
|
||||
}
|
||||
} else {
|
||||
this.commandsQueue.offer(payloadCallbackPair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
public final class PayloadCallbackPair {
|
||||
public final String payload;
|
||||
public final CoapCallback callback;
|
||||
|
||||
public PayloadCallbackPair(String payload, CoapCallback callback) {
|
||||
this.payload = payload;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.californium.core.CoapHandler;
|
||||
import org.eclipse.californium.core.CoapResponse;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* The {@link TradfriCoapHandler} is used to handle the asynchronous coap reponses.
|
||||
* It can either be used with a callback class or with a future.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriCoapHandler implements CoapHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriCoapHandler.class);
|
||||
private final JsonParser parser = new JsonParser();
|
||||
|
||||
private @Nullable CoapCallback callback;
|
||||
private @Nullable CompletableFuture<String> future;
|
||||
|
||||
/**
|
||||
* Constructor for using a callback
|
||||
*
|
||||
* @param callback the callback to use for responses
|
||||
*/
|
||||
public TradfriCoapHandler(CoapCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for using a future
|
||||
*
|
||||
* @param future the future to use for responses
|
||||
*/
|
||||
public TradfriCoapHandler(CompletableFuture<String> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(@Nullable CoapResponse response) {
|
||||
if (response == null) {
|
||||
logger.trace("received empty CoAP response");
|
||||
return;
|
||||
}
|
||||
logger.debug("CoAP response\noptions: {}\npayload: {}", response.getOptions(), response.getResponseText());
|
||||
if (response.isSuccess()) {
|
||||
final CoapCallback callback = this.callback;
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onUpdate(parser.parse(response.getResponseText()));
|
||||
callback.setStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("Observed value is no valid json: {}, {}", response.getResponseText(), e.getMessage());
|
||||
}
|
||||
}
|
||||
final CompletableFuture<String> future = this.future;
|
||||
if (future != null) {
|
||||
String data = response.getResponseText();
|
||||
future.complete(data);
|
||||
}
|
||||
} else {
|
||||
logger.debug("CoAP error {}", response.getCode());
|
||||
if (callback != null) {
|
||||
callback.setStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
if (future != null) {
|
||||
future.completeExceptionally(new RuntimeException("Response " + response.getCode().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
logger.debug("CoAP onError");
|
||||
if (callback != null) {
|
||||
callback.setStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
if (future != null) {
|
||||
future.completeExceptionally(new RuntimeException("CoAP GET resulted in an error."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* The {@link TradfriColor} is used for conversion between color formats.
|
||||
* Use the static methods {@link TradfriColor#fromCie(int, int, int)} and {@link TradfriColor#fromHSBType(HSBType)} for
|
||||
* construction.
|
||||
*
|
||||
* @author Holger Reichert - Initial contribution
|
||||
* @author Stefan Triller - Use conversions from HSBType
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
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 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.
|
||||
* May be <code>null</code> if the calculation method does not support this color range.
|
||||
*/
|
||||
public Integer xyX, xyY;
|
||||
|
||||
/**
|
||||
* Brightness level in the tradfri range 0 to 254.
|
||||
* May be <code>null</code> if the calculation method does not support this color range.
|
||||
*/
|
||||
public @Nullable Integer brightness;
|
||||
|
||||
/**
|
||||
* Construct from CIE XY values in the tradfri range.
|
||||
*
|
||||
* @param xyX x value 0 to 65535
|
||||
* @param xyY y value 0 to 65535
|
||||
* @param xyBrightness brightness from 0 to 254
|
||||
*/
|
||||
public TradfriColor(Integer xyX, Integer xyY, @Nullable Integer brightness) {
|
||||
this.xyX = xyX;
|
||||
this.xyY = xyY;
|
||||
if (brightness != null) {
|
||||
if (brightness > 254) {
|
||||
this.brightness = 254;
|
||||
} else {
|
||||
this.brightness = brightness;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from HSBType
|
||||
*
|
||||
* @param hsb HSBType from the framework
|
||||
*/
|
||||
public TradfriColor(HSBType hsb) {
|
||||
PercentType[] xyArray = hsb.toXY();
|
||||
this.xyX = normalize(xyArray[0].doubleValue() / 100.0);
|
||||
this.xyY = normalize(xyArray[1].doubleValue() / 100.0);
|
||||
this.brightness = (int) (hsb.getBrightness().floatValue() * 2.54);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the TradfriColor (x/y) as HSBType
|
||||
*
|
||||
* @return HSBType representing the x/y Tradfri color
|
||||
*/
|
||||
public HSBType getHSB() {
|
||||
float x = unnormalize(xyX);
|
||||
float y = unnormalize(xyY);
|
||||
|
||||
HSBType converted = HSBType.fromXY(x, y);
|
||||
|
||||
final Integer brightness = this.brightness;
|
||||
if (brightness == null) {
|
||||
throw new IllegalStateException("cannot convert to HSB with brightness=null");
|
||||
}
|
||||
return new HSBType(converted.getHue(), converted.getSaturation(), xyBrightnessToPercentType(brightness));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from color temperature in percent.
|
||||
* 0 (coldest) to 100 (warmest).
|
||||
* Note: The resulting {@link TradfriColor} has only the {@link TradfriColor#xyX X} and {@link TradfriColor#xyY y}
|
||||
* values set!
|
||||
*
|
||||
* @param percentType the color temperature in percent
|
||||
*/
|
||||
public TradfriColor(PercentType percentType) {
|
||||
double percent = percentType.doubleValue();
|
||||
|
||||
int x, y;
|
||||
if (percent < 50.0) {
|
||||
// we calculate a value that is between preset 0 and 1
|
||||
double p = percent / 50.0;
|
||||
x = (int) Math.round(PRESET_X[0] + p * (PRESET_X[1] - PRESET_X[0]));
|
||||
y = (int) Math.round(PRESET_Y[0] + p * (PRESET_Y[1] - PRESET_Y[0]));
|
||||
} else {
|
||||
// we calculate a value that is between preset 1 and 2
|
||||
double p = (percent - 50) / 50.0;
|
||||
x = (int) Math.round(PRESET_X[1] + p * (PRESET_X[2] - PRESET_X[1]));
|
||||
y = (int) Math.round(PRESET_Y[1] + p * (PRESET_Y[2] - PRESET_Y[1]));
|
||||
}
|
||||
|
||||
this.xyX = x;
|
||||
this.xyY = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize value to the tradfri range.
|
||||
*
|
||||
* @param value double in the range 0.0 to 1.0
|
||||
* @return normalized value in the range 0 to 65535
|
||||
*/
|
||||
private int normalize(double value) {
|
||||
return (int) (value * 65535 + 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse-normalize value from the tradfri range.
|
||||
*
|
||||
* @param value integer in the range 0 to 65535
|
||||
* @return unnormalized value in the range 0.0 to 1.0
|
||||
*/
|
||||
private float unnormalize(int value) {
|
||||
return (value / 65535.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the color temperature from given x and y values.
|
||||
*
|
||||
* @return {@link PercentType} with color temperature (0 = coolest, 100 = warmest)
|
||||
*/
|
||||
public PercentType getColorTemperature() {
|
||||
double x = xyX;
|
||||
double y = xyY;
|
||||
double value = 0.0;
|
||||
if ((x > PRESET_X[1] && y > PRESET_Y[1]) && (x <= PRESET_X[2] && y <= PRESET_Y[2])) {
|
||||
// is it between preset 1 and 2?
|
||||
value = (x - PRESET_X[1]) / (PRESET_X[2] - PRESET_X[1]) / 2.0 + 0.5;
|
||||
} else if ((x >= PRESET_X[0] && y >= PRESET_Y[0]) && (x <= (PRESET_X[1] + 2.0) && y <= PRESET_Y[1])) {
|
||||
// is it between preset 0 and 1?
|
||||
// hint: in the above line we calculate 2.0 to PRESET_X[1] because
|
||||
// some bulbs send slighty higher x values for this preset (maybe rounding errors?)
|
||||
value = (x - PRESET_X[0]) / (PRESET_X[1] - PRESET_X[0]) / 2.0;
|
||||
} else if (x < PRESET_X[0]) {
|
||||
// cooler than coolest preset (full color bulbs)
|
||||
value = 0.0;
|
||||
} else if (x > PRESET_X[2]) {
|
||||
// warmer than warmest preset (full color bulbs)
|
||||
value = 1.0;
|
||||
}
|
||||
return new PercentType((int) Math.round(value * 100.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the xyBrightness value to PercentType
|
||||
*
|
||||
* @param xyBrightness xy brightness level 0 to 254
|
||||
* @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) {
|
||||
return PercentType.ZERO;
|
||||
}
|
||||
return new PercentType((int) Math.ceil(xyBrightness / 2.54));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (GATEWAY_TYPE_UID.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)) {
|
||||
return new TradfriPlugHandler(thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.tradfri.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link TradfriDeviceConfig} holds the
|
||||
* configuration information needed to access single bulbs on the gateway.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class TradfriDeviceConfig {
|
||||
|
||||
public static final String CONFIG_ID = "id";
|
||||
|
||||
public Integer id;
|
||||
}
|
||||
@@ -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.tradfri.internal.config;
|
||||
|
||||
/**
|
||||
* Configuration class for the gateway.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class TradfriGatewayConfig {
|
||||
|
||||
public String host;
|
||||
public int port = 5684; // default port
|
||||
public String code;
|
||||
public String identity;
|
||||
public String preSharedKey;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.tradfri.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class identifies Tradfri gateways by their mDNS service information.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@Component(service = MDNSDiscoveryParticipant.class, immediate = true)
|
||||
@NonNullByDefault
|
||||
public class TradfriDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryParticipant.class);
|
||||
|
||||
private static final String SERVICE_TYPE = "_coap._udp.local.";
|
||||
|
||||
/**
|
||||
* RegEx patter to match the gateway name announced by mDNS
|
||||
* Possible values:
|
||||
* gw:001122334455, gw-001122334455, gw:00-11-22-33-44-55, gw-001122334455ServiceName
|
||||
*
|
||||
*/
|
||||
private static final Pattern GATEWAY_NAME_REGEX_PATTERN = Pattern.compile("(gw[:-]{1}([a-f0-9]{2}[-]?){6}){1}");
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_BRIDGE_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(@Nullable ServiceInfo service) {
|
||||
if (service != null) {
|
||||
Matcher m = GATEWAY_NAME_REGEX_PATTERN.matcher(service.getName());
|
||||
if (m.find()) {
|
||||
return new ThingUID(GATEWAY_TYPE_UID, m.group(1).replaceAll("[^A-Za-z0-9_]", ""));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
ThingUID thingUID = getThingUID(service);
|
||||
if (thingUID != null) {
|
||||
if (service.getHostAddresses() != null && service.getHostAddresses().length > 0
|
||||
&& !service.getHostAddresses()[0].isEmpty()) {
|
||||
logger.debug("Discovered Tradfri gateway: {}", service);
|
||||
Map<String, Object> properties = new HashMap<>(4);
|
||||
properties.put(PROPERTY_VENDOR, "IKEA of Sweden");
|
||||
properties.put(GATEWAY_CONFIG_HOST, service.getHostAddresses()[0]);
|
||||
properties.put(GATEWAY_CONFIG_PORT, service.getPort());
|
||||
properties.put(PROPERTY_SERIAL_NUMBER, service.getName());
|
||||
String fwVersion = service.getPropertyString("version");
|
||||
if (fwVersion != null) {
|
||||
properties.put(PROPERTY_FIRMWARE_VERSION, fwVersion);
|
||||
}
|
||||
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel("TRÅDFRI Gateway")
|
||||
.withRepresentationProperty(GATEWAY_CONFIG_HOST).build();
|
||||
} else {
|
||||
logger.warn("Discovered Tradfri gateway doesn't have an IP address: {}", service);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* 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.tradfri.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tradfri.internal.DeviceUpdateListener;
|
||||
import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* This class identifies devices that are available on the gateway and adds discovery results for them.
|
||||
*
|
||||
* @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, DiscoveryService, ThingHandlerService {
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
|
||||
|
||||
private @Nullable TradfriGatewayHandler handler;
|
||||
|
||||
private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
|
||||
|
||||
private static final Set<String> COLOR_TEMP_MODELS = Collections
|
||||
.unmodifiableSet(Stream
|
||||
.of("TRADFRI bulb E27 WS opal 980lm", "TRADFRI bulb E27 WS clear 950lm",
|
||||
"TRADFRI bulb GU10 WS 400lm", "TRADFRI bulb E14 WS opal 400lm", "FLOALT panel WS 30x30",
|
||||
"FLOALT panel WS 60x60", "FLOALT panel WS 30x90", "TRADFRI bulb E12 WS opal 400lm")
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
|
||||
|
||||
public TradfriDiscoveryService() {
|
||||
super(SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
handler.startScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof TradfriGatewayHandler) {
|
||||
this.handler = (TradfriGatewayHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
handler.registerDeviceUpdateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
removeOlderResults(new Date().getTime());
|
||||
handler.unregisterDeviceUpdateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
|
||||
ThingUID bridge = handler.getThing().getUID();
|
||||
try {
|
||||
if (data != null && data.has(INSTANCE_ID)) {
|
||||
int id = data.get(INSTANCE_ID).getAsInt();
|
||||
String type = data.get(TYPE).getAsString();
|
||||
JsonObject deviceInfo = data.get(DEVICE).getAsJsonObject();
|
||||
String model = deviceInfo.get(DEVICE_MODEL).getAsString();
|
||||
ThingUID thingId = null;
|
||||
|
||||
if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
|
||||
JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
|
||||
|
||||
// Color temperature light:
|
||||
// We do not always receive a COLOR attribute, even the light supports it - but the gateway does not
|
||||
// seem to have this information, if the bulb is unreachable. We therefore also check against
|
||||
// concrete model names.
|
||||
// Color light:
|
||||
// As the protocol does not distinguishes between color and full-color lights,
|
||||
// we check if the "CWS" or "CW/S" identifier is given in the model name
|
||||
ThingTypeUID thingType = null;
|
||||
if (model != null && Arrays.stream(COLOR_MODEL_IDENTIFIER_HINTS).anyMatch(model::contains)) {
|
||||
thingType = THING_TYPE_COLOR_LIGHT;
|
||||
}
|
||||
if (thingType == null && //
|
||||
(state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
|
||||
thingType = THING_TYPE_COLOR_TEMP_LIGHT;
|
||||
}
|
||||
if (thingType == null) {
|
||||
thingType = THING_TYPE_DIMMABLE_LIGHT;
|
||||
}
|
||||
thingId = new ThingUID(thingType, bridge, Integer.toString(id));
|
||||
} else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
|
||||
// Blinds
|
||||
thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
|
||||
} else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
|
||||
// Smart plug
|
||||
thingId = new ThingUID(THING_TYPE_ONOFF_PLUG, bridge, Integer.toString(id));
|
||||
} else if (TYPE_SWITCH.equals(type) && data.has(SWITCH)) {
|
||||
// Remote control and wireless dimmer
|
||||
// As protocol does not distinguishes between remote control and wireless dimmer,
|
||||
// we check for the whole model name
|
||||
ThingTypeUID thingType = (model != null && REMOTE_CONTROLLER_MODEL.equals(model))
|
||||
? THING_TYPE_REMOTE_CONTROL
|
||||
: THING_TYPE_DIMMER;
|
||||
thingId = new ThingUID(thingType, bridge, Integer.toString(id));
|
||||
} else if (TYPE_REMOTE.equals(type)) {
|
||||
thingId = new ThingUID(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL, bridge, Integer.toString(id));
|
||||
} else if (TYPE_SENSOR.equals(type) && data.has(SENSOR)) {
|
||||
// Motion sensor
|
||||
thingId = new ThingUID(THING_TYPE_MOTION_SENSOR, bridge, Integer.toString(id));
|
||||
}
|
||||
|
||||
if (thingId == null) {
|
||||
// we didn't identify any device, so let's quit
|
||||
logger.debug("Ignoring unknown device on TRADFRI gateway:\n\ttype : {}\n\tmodel: {}\n\tinfo : {}",
|
||||
type, model, deviceInfo.getAsString());
|
||||
return;
|
||||
}
|
||||
|
||||
String label = data.get(NAME).getAsString();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(1);
|
||||
properties.put("id", id);
|
||||
if (model != null) {
|
||||
properties.put(PROPERTY_MODEL_ID, model);
|
||||
}
|
||||
if (deviceInfo.get(DEVICE_VENDOR) != null) {
|
||||
properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
|
||||
}
|
||||
if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
|
||||
properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
|
||||
}
|
||||
|
||||
logger.debug("Adding device {} to inbox", thingId);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingId).withBridge(bridge)
|
||||
.withLabel(label).withProperties(properties).withRepresentationProperty("id").build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.debug("JSON error during discovery: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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.tradfri.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tradfri.internal.model.TradfriBlindData;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
set(new TradfriBlindData().setPosition(percent).getJsonString());
|
||||
}
|
||||
|
||||
private void triggerStop() {
|
||||
set(new TradfriBlindData().stop().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) {
|
||||
if (StopMoveType.STOP.equals(command)) {
|
||||
triggerStop();
|
||||
} else {
|
||||
logger.debug("Cannot handle command '{}' for channel '{}'", command, CHANNEL_POSITION);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.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.openhab.binding.tradfri.internal.model.TradfriControllerData;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriControllerHandler} is responsible for handling commands for individual controllers.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriControllerHandler extends TradfriThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriControllerHandler.class);
|
||||
|
||||
// keeps track of the current state for handling of increase/decrease
|
||||
private @Nullable TradfriControllerData state;
|
||||
|
||||
public TradfriControllerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(JsonElement data) {
|
||||
if (active && !(data.isJsonNull())) {
|
||||
state = new TradfriControllerData(data);
|
||||
updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
|
||||
final TradfriControllerData state = this.state;
|
||||
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
logger.debug(
|
||||
"Updating thing for controllerId {} to state {batteryLevel: {}, batteryLow: {}, firmwareVersion: {}, modelId: {}, vendor: {}}",
|
||||
state.getDeviceId(), batteryLevel, batteryLow, state.getFirmwareVersion(), state.getModelId(),
|
||||
state.getVendor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (active) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refreshing channel {}", channelUID);
|
||||
coapClient.asyncGet(this);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("The controller is a read-only device and cannot handle commands.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* 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.tradfri.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.californium.core.CoapClient;
|
||||
import org.eclipse.californium.core.CoapResponse;
|
||||
import org.eclipse.californium.core.network.CoapEndpoint;
|
||||
import org.eclipse.californium.elements.exception.ConnectorException;
|
||||
import org.eclipse.californium.scandium.DTLSConnector;
|
||||
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
|
||||
import org.eclipse.californium.scandium.dtls.pskstore.StaticPskStore;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tradfri.internal.CoapCallback;
|
||||
import org.openhab.binding.tradfri.internal.DeviceUpdateListener;
|
||||
import org.openhab.binding.tradfri.internal.TradfriBindingConstants;
|
||||
import org.openhab.binding.tradfri.internal.TradfriCoapClient;
|
||||
import org.openhab.binding.tradfri.internal.TradfriCoapHandler;
|
||||
import org.openhab.binding.tradfri.internal.config.TradfriGatewayConfig;
|
||||
import org.openhab.binding.tradfri.internal.discovery.TradfriDiscoveryService;
|
||||
import org.openhab.binding.tradfri.internal.model.TradfriVersion;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link TradfriGatewayHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriGatewayHandler extends BaseBridgeHandler implements CoapCallback {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final TradfriVersion MIN_SUPPORTED_VERSION = new TradfriVersion("1.2.42");
|
||||
|
||||
private @NonNullByDefault({}) TradfriCoapClient deviceClient;
|
||||
private @NonNullByDefault({}) String gatewayURI;
|
||||
private @NonNullByDefault({}) String gatewayInfoURI;
|
||||
private @NonNullByDefault({}) DTLSConnector dtlsConnector;
|
||||
private @Nullable CoapEndpoint endPoint;
|
||||
|
||||
private final Set<DeviceUpdateListener> deviceUpdateListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private @Nullable ScheduledFuture<?> scanJob;
|
||||
|
||||
public TradfriGatewayHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// there are no channels on the gateway yet
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
TradfriGatewayConfig configuration = getConfigAs(TradfriGatewayConfig.class);
|
||||
|
||||
if (isNullOrEmpty(configuration.host)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Host must be specified in the configuration!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNullOrEmpty(configuration.code)) {
|
||||
if (isNullOrEmpty(configuration.identity) || isNullOrEmpty(configuration.preSharedKey)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Either security code or identity and pre-shared key must be provided in the configuration!");
|
||||
return;
|
||||
} else {
|
||||
establishConnection();
|
||||
}
|
||||
} else {
|
||||
String currentFirmware = thing.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION);
|
||||
if (!isNullOrEmpty(currentFirmware)
|
||||
&& MIN_SUPPORTED_VERSION.compareTo(new TradfriVersion(currentFirmware)) > 0) {
|
||||
// older firmware not supported
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
String.format(
|
||||
"Gateway firmware version '%s' is too old! Minimum supported firmware version is '%s'.",
|
||||
currentFirmware, MIN_SUPPORTED_VERSION.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Running async operation to retrieve new <'identity','key'> pair
|
||||
scheduler.execute(() -> {
|
||||
boolean success = obtainIdentityAndPreSharedKey();
|
||||
if (success) {
|
||||
establishConnection();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(TradfriDiscoveryService.class);
|
||||
}
|
||||
|
||||
private void establishConnection() {
|
||||
TradfriGatewayConfig configuration = getConfigAs(TradfriGatewayConfig.class);
|
||||
|
||||
this.gatewayURI = "coaps://" + configuration.host + ":" + configuration.port + "/" + DEVICES;
|
||||
this.gatewayInfoURI = "coaps://" + configuration.host + ":" + configuration.port + "/" + GATEWAY + "/"
|
||||
+ GATEWAY_DETAILS;
|
||||
try {
|
||||
URI uri = new URI(gatewayURI);
|
||||
deviceClient = new TradfriCoapClient(uri);
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("Illegal gateway URI '{}': {}", gatewayURI, e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
|
||||
builder.setPskStore(new StaticPskStore(configuration.identity, configuration.preSharedKey.getBytes()));
|
||||
builder.setMaxConnections(100);
|
||||
builder.setStaleConnectionThreshold(60);
|
||||
dtlsConnector = new DTLSConnector(builder.build());
|
||||
endPoint = new CoapEndpoint.Builder().setConnector(dtlsConnector).build();
|
||||
deviceClient.setEndpoint(endPoint);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// schedule a new scan every minute
|
||||
scanJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates against the gateway with the security code in order to receive a pre-shared key for a newly
|
||||
* generated identity.
|
||||
* As this requires a remote request, this method might be long-running.
|
||||
*
|
||||
* @return true, if credentials were successfully obtained, false otherwise
|
||||
*/
|
||||
protected boolean obtainIdentityAndPreSharedKey() {
|
||||
TradfriGatewayConfig configuration = getConfigAs(TradfriGatewayConfig.class);
|
||||
|
||||
String identity = UUID.randomUUID().toString().replace("-", "");
|
||||
String preSharedKey = null;
|
||||
|
||||
CoapResponse gatewayResponse;
|
||||
String authUrl = null;
|
||||
String responseText = null;
|
||||
try {
|
||||
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
|
||||
builder.setPskStore(new StaticPskStore("Client_identity", configuration.code.getBytes()));
|
||||
|
||||
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
|
||||
CoapEndpoint.Builder authEndpointBuilder = new CoapEndpoint.Builder();
|
||||
authEndpointBuilder.setConnector(dtlsConnector);
|
||||
CoapEndpoint authEndpoint = authEndpointBuilder.build();
|
||||
authUrl = "coaps://" + configuration.host + ":" + configuration.port + "/15011/9063";
|
||||
|
||||
CoapClient deviceClient = new CoapClient(new URI(authUrl));
|
||||
deviceClient.setTimeout(TimeUnit.SECONDS.toMillis(10));
|
||||
deviceClient.setEndpoint(authEndpoint);
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty(CLIENT_IDENTITY_PROPOSED, identity);
|
||||
|
||||
gatewayResponse = deviceClient.post(json.toString(), 0);
|
||||
|
||||
authEndpoint.destroy();
|
||||
deviceClient.shutdown();
|
||||
|
||||
if (gatewayResponse == null) {
|
||||
// seems we ran in a timeout, which potentially also happens
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"No response from gateway. Might be due to an invalid security code.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gatewayResponse.isSuccess()) {
|
||||
responseText = gatewayResponse.getResponseText();
|
||||
json = new JsonParser().parse(responseText).getAsJsonObject();
|
||||
preSharedKey = json.get(NEW_PSK_BY_GW).getAsString();
|
||||
|
||||
if (isNullOrEmpty(preSharedKey)) {
|
||||
logger.error("Received pre-shared key is empty for thing {} on gateway at {}", getThing().getUID(),
|
||||
configuration.host);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Pre-shared key was not obtain successfully");
|
||||
return false;
|
||||
} else {
|
||||
logger.info("Received pre-shared key for gateway '{}'", configuration.host);
|
||||
logger.debug("Using identity '{}' with pre-shared key '{}'.", identity, preSharedKey);
|
||||
|
||||
Configuration editedConfig = editConfiguration();
|
||||
editedConfig.put(TradfriBindingConstants.GATEWAY_CONFIG_CODE, null);
|
||||
editedConfig.put(TradfriBindingConstants.GATEWAY_CONFIG_IDENTITY, identity);
|
||||
editedConfig.put(TradfriBindingConstants.GATEWAY_CONFIG_PRE_SHARED_KEY, preSharedKey);
|
||||
updateConfiguration(editedConfig);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
"Failed obtaining pre-shared key for identity '{}' (response code '{}', response text '{}')",
|
||||
identity, gatewayResponse.getCode(),
|
||||
isNullOrEmpty(gatewayResponse.getResponseText()) ? "<empty>"
|
||||
: gatewayResponse.getResponseText());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String
|
||||
.format("Failed obtaining pre-shared key with status code '%s'", gatewayResponse.getCode()));
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("Illegal gateway URI '{}'", authUrl, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("Invalid response received from gateway '{}'", responseText, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Invalid response received from gateway '%s'", responseText));
|
||||
} catch (ConnectorException | IOException e) {
|
||||
logger.debug("Error connecting to gateway ", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Error connecting to gateway."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (scanJob != null) {
|
||||
scanJob.cancel(true);
|
||||
scanJob = null;
|
||||
}
|
||||
if (endPoint != null) {
|
||||
endPoint.destroy();
|
||||
endPoint = null;
|
||||
}
|
||||
if (deviceClient != null) {
|
||||
deviceClient.shutdown();
|
||||
deviceClient = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a request to the gateway to list all available devices/services.
|
||||
* The response is received and processed by the method {@link onUpdate(JsonElement data)}.
|
||||
*/
|
||||
public void startScan() {
|
||||
if (endPoint != null) {
|
||||
requestGatewayInfo();
|
||||
deviceClient.get(new TradfriCoapHandler(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root URI of the gateway.
|
||||
*
|
||||
* @return root URI of the gateway with coaps scheme
|
||||
*/
|
||||
public String getGatewayURI() {
|
||||
return gatewayURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the coap endpoint that can be used within coap clients.
|
||||
*
|
||||
* @return the coap endpoint
|
||||
*/
|
||||
public @Nullable CoapEndpoint getEndpoint() {
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(JsonElement data) {
|
||||
logger.debug("onUpdate response: {}", data);
|
||||
if (endPoint != null) {
|
||||
try {
|
||||
JsonArray array = data.getAsJsonArray();
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
requestDeviceDetails(array.get(i).getAsString());
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.debug("JSON error: {}", e.getMessage());
|
||||
setStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void requestGatewayInfo() {
|
||||
// we are reusing our coap client and merely temporarily set a gateway info to call
|
||||
deviceClient.setURI(gatewayInfoURI);
|
||||
deviceClient.asyncGet().thenAccept(data -> {
|
||||
logger.debug("requestGatewayInfo response: {}", data);
|
||||
JsonObject json = new JsonParser().parse(data).getAsJsonObject();
|
||||
String firmwareVersion = json.get(VERSION).getAsString();
|
||||
getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
});
|
||||
// restore root URI
|
||||
deviceClient.setURI(gatewayURI);
|
||||
}
|
||||
|
||||
private synchronized void requestDeviceDetails(String instanceId) {
|
||||
// we are reusing our coap client and merely temporarily set a sub-URI to call
|
||||
deviceClient.setURI(gatewayURI + "/" + instanceId);
|
||||
deviceClient.asyncGet().thenAccept(data -> {
|
||||
logger.debug("requestDeviceDetails response: {}", data);
|
||||
JsonObject json = new JsonParser().parse(data).getAsJsonObject();
|
||||
deviceUpdateListeners.forEach(listener -> listener.onUpdate(instanceId, json));
|
||||
});
|
||||
// restore root URI
|
||||
deviceClient.setURI(gatewayURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(ThingStatus status, ThingStatusDetail statusDetail) {
|
||||
// to fix connection issues after a gateway reboot, a session resume is forced for the next command
|
||||
if (status == ThingStatus.OFFLINE && statusDetail == ThingStatusDetail.COMMUNICATION_ERROR) {
|
||||
logger.debug("Gateway communication error. Forcing a re-initialization!");
|
||||
dispose();
|
||||
initialize();
|
||||
}
|
||||
|
||||
// are we still connected at all?
|
||||
if (endPoint != null) {
|
||||
updateStatus(status, statusDetail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener, which is informed about device details.
|
||||
*
|
||||
* @param listener the listener to register
|
||||
*/
|
||||
public void registerDeviceUpdateListener(DeviceUpdateListener listener) {
|
||||
this.deviceUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a given listener.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
public void unregisterDeviceUpdateListener(DeviceUpdateListener listener) {
|
||||
this.deviceUpdateListeners.remove(listener);
|
||||
}
|
||||
|
||||
private boolean isNullOrEmpty(@Nullable String string) {
|
||||
return string == null || string.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
super.thingUpdated(thing);
|
||||
|
||||
logger.info("Bridge configuration updated. Updating paired things (if any).");
|
||||
for (Thing t : getThing().getThings()) {
|
||||
final ThingHandler thingHandler = t.getHandler();
|
||||
if (thingHandler != null) {
|
||||
thingHandler.thingUpdated(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 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.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.openhab.binding.tradfri.internal.model.TradfriLightData;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriLightHandler} is responsible for handling commands for individual lights.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Holger Reichert - Support for color bulbs
|
||||
* @author Christoph Weitkamp - Restructuring and refactoring of the binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriLightHandler extends TradfriThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriLightHandler.class);
|
||||
|
||||
// step size for increase/decrease commands
|
||||
private static final int STEP = 10;
|
||||
|
||||
// keeps track of the current state for handling of increase/decrease
|
||||
private @Nullable TradfriLightData state;
|
||||
|
||||
public TradfriLightHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(JsonElement data) {
|
||||
if (active && !(data.isJsonNull())) {
|
||||
TradfriLightData state = new TradfriLightData(data);
|
||||
updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
|
||||
if (!state.getOnOffState()) {
|
||||
logger.debug("Setting state to OFF");
|
||||
updateState(CHANNEL_BRIGHTNESS, PercentType.ZERO);
|
||||
if (lightHasColorSupport()) {
|
||||
updateState(CHANNEL_COLOR, HSBType.BLACK);
|
||||
}
|
||||
// if we are turned off, we do not set any brightness value
|
||||
return;
|
||||
}
|
||||
|
||||
PercentType dimmer = state.getBrightness();
|
||||
if (dimmer != null && !lightHasColorSupport()) { // color lights do not have brightness channel
|
||||
updateState(CHANNEL_BRIGHTNESS, dimmer);
|
||||
}
|
||||
|
||||
PercentType colorTemp = state.getColorTemperature();
|
||||
if (colorTemp != null) {
|
||||
updateState(CHANNEL_COLOR_TEMPERATURE, colorTemp);
|
||||
}
|
||||
|
||||
HSBType color = null;
|
||||
if (lightHasColorSupport()) {
|
||||
color = state.getColor();
|
||||
if (color != null) {
|
||||
updateState(CHANNEL_COLOR, color);
|
||||
}
|
||||
}
|
||||
|
||||
updateDeviceProperties(state);
|
||||
|
||||
this.state = state;
|
||||
|
||||
logger.debug(
|
||||
"Updating thing for lightId {} to state {dimmer: {}, colorTemp: {}, color: {}, firmwareVersion: {}, modelId: {}, vendor: {}}",
|
||||
state.getDeviceId(), dimmer, colorTemp, color, state.getFirmwareVersion(), state.getModelId(),
|
||||
state.getVendor());
|
||||
}
|
||||
}
|
||||
|
||||
private void setBrightness(PercentType percent) {
|
||||
TradfriLightData data = new TradfriLightData();
|
||||
data.setBrightness(percent).setTransitionTime(DEFAULT_DIMMER_TRANSITION_TIME);
|
||||
set(data.getJsonString());
|
||||
}
|
||||
|
||||
private void setState(OnOffType onOff) {
|
||||
TradfriLightData data = new TradfriLightData();
|
||||
data.setOnOffState(onOff == OnOffType.ON);
|
||||
set(data.getJsonString());
|
||||
}
|
||||
|
||||
private void setColorTemperature(PercentType percent) {
|
||||
TradfriLightData data = new TradfriLightData();
|
||||
data.setColorTemperature(percent).setTransitionTime(DEFAULT_DIMMER_TRANSITION_TIME);
|
||||
set(data.getJsonString());
|
||||
}
|
||||
|
||||
private void setColor(HSBType hsb) {
|
||||
TradfriLightData data = new TradfriLightData();
|
||||
data.setColor(hsb).setTransitionTime(DEFAULT_DIMMER_TRANSITION_TIME);
|
||||
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_BRIGHTNESS:
|
||||
handleBrightnessCommand(command);
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
handleColorTemperatureCommand(command);
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
handleColorCommand(command);
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown channel UID {}", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBrightnessCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
setBrightness((PercentType) command);
|
||||
} else if (command instanceof OnOffType) {
|
||||
setState(((OnOffType) command));
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
final TradfriLightData state = this.state;
|
||||
if (state != null && state.getBrightness() != null) {
|
||||
@SuppressWarnings("null")
|
||||
int current = state.getBrightness().intValue();
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command)) {
|
||||
setBrightness(new PercentType(Math.min(current + STEP, PercentType.HUNDRED.intValue())));
|
||||
} else {
|
||||
setBrightness(new PercentType(Math.max(current - STEP, PercentType.ZERO.intValue())));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot handle inc/dec as current state is not known.");
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot handle command {} for channel {}", command, CHANNEL_BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleColorTemperatureCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
setColorTemperature((PercentType) command);
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
final TradfriLightData state = this.state;
|
||||
if (state != null && state.getColorTemperature() != null) {
|
||||
@SuppressWarnings("null")
|
||||
int current = state.getColorTemperature().intValue();
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command)) {
|
||||
setColorTemperature(new PercentType(Math.min(current + STEP, PercentType.HUNDRED.intValue())));
|
||||
} else {
|
||||
setColorTemperature(new PercentType(Math.max(current - STEP, PercentType.ZERO.intValue())));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot handle inc/dec as current state is not known.");
|
||||
}
|
||||
} else {
|
||||
logger.debug("Can't handle command {} on channel {}", command, CHANNEL_COLOR_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleColorCommand(Command command) {
|
||||
if (command instanceof HSBType) {
|
||||
setColor((HSBType) command);
|
||||
setBrightness(((HSBType) command).getBrightness());
|
||||
} else if (command instanceof OnOffType) {
|
||||
setState(((OnOffType) command));
|
||||
} else if (command instanceof PercentType) {
|
||||
// PaperUI sends PercentType on color channel when changing Brightness
|
||||
setBrightness((PercentType) command);
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
final TradfriLightData state = this.state;
|
||||
// increase or decrease only the brightness, but keep color
|
||||
if (state != null && state.getBrightness() != null) {
|
||||
@SuppressWarnings("null")
|
||||
int current = state.getBrightness().intValue();
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command)) {
|
||||
setBrightness(new PercentType(Math.min(current + STEP, PercentType.HUNDRED.intValue())));
|
||||
} else {
|
||||
setBrightness(new PercentType(Math.max(current - STEP, PercentType.ZERO.intValue())));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot handle inc/dec for color as current brightness is not known.");
|
||||
}
|
||||
} else {
|
||||
logger.debug("Can't handle command {} on channel {}", command, CHANNEL_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this light supports full color.
|
||||
*
|
||||
* @return true if the light supports full color
|
||||
*/
|
||||
private boolean lightHasColorSupport() {
|
||||
return thing.getThingTypeUID().getId().equals(THING_TYPE_COLOR_LIGHT.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.tradfri.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.CHANNEL_POWER;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tradfri.internal.model.TradfriPlugData;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriPlugHandler} is responsible for handling commands for individual plugs.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriPlugHandler extends TradfriThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriPlugHandler.class);
|
||||
|
||||
public TradfriPlugHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(JsonElement data) {
|
||||
if (active && !(data.isJsonNull())) {
|
||||
TradfriPlugData state = new TradfriPlugData(data);
|
||||
updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
|
||||
updateState(CHANNEL_POWER, state.getOnOffState() ? OnOffType.ON : OnOffType.OFF);
|
||||
updateDeviceProperties(state);
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(OnOffType onOff) {
|
||||
TradfriPlugData data = new TradfriPlugData();
|
||||
data.setOnOffState(onOff == OnOffType.ON);
|
||||
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_POWER:
|
||||
if (command instanceof OnOffType) {
|
||||
setState(((OnOffType) command));
|
||||
} else {
|
||||
logger.debug("Cannot handle command '{}' for channel '{}'", command, CHANNEL_POWER);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown channel UID {}", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.tradfri.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tradfri.internal.model.TradfriSensorData;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriSensorHandler} is responsible for handling commands for individual sensors.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriSensorHandler extends TradfriThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriSensorHandler.class);
|
||||
|
||||
public TradfriSensorHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(JsonElement data) {
|
||||
if (active && !(data.isJsonNull())) {
|
||||
TradfriSensorData state = new TradfriSensorData(data);
|
||||
updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
|
||||
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);
|
||||
|
||||
logger.debug(
|
||||
"Updating thing for sensorId {} to state {batteryLevel: {}, batteryLow: {}, firmwareVersion: {}, modelId: {}, vendor: {}}",
|
||||
state.getDeviceId(), batteryLevel, batteryLow, state.getFirmwareVersion(), state.getModelId(),
|
||||
state.getVendor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (active) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refreshing channel {}", channelUID);
|
||||
coapClient.asyncGet(this);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("The sensor is a read-only device and cannot handle commands.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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.tradfri.internal.handler;
|
||||
|
||||
import static org.openhab.core.thing.Thing.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.californium.core.CoapObserveRelation;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tradfri.internal.CoapCallback;
|
||||
import org.openhab.binding.tradfri.internal.TradfriCoapClient;
|
||||
import org.openhab.binding.tradfri.internal.config.TradfriDeviceConfig;
|
||||
import org.openhab.binding.tradfri.internal.model.TradfriDeviceData;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link TradfriThingHandler} is the abstract base class for individual device handlers.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Christoph Weitkamp - Restructuring and refactoring of the binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class TradfriThingHandler extends BaseThingHandler implements CoapCallback {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriThingHandler.class);
|
||||
|
||||
// the unique instance id of the device
|
||||
protected @Nullable Integer id;
|
||||
|
||||
// used to check whether we have already been disposed when receiving data asynchronously
|
||||
protected volatile boolean active;
|
||||
|
||||
protected @NonNullByDefault({}) TradfriCoapClient coapClient;
|
||||
|
||||
private @Nullable CoapObserveRelation observeRelation;
|
||||
|
||||
public TradfriThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public synchronized void initialize() {
|
||||
Bridge tradfriGateway = getBridge();
|
||||
this.id = getConfigAs(TradfriDeviceConfig.class).id;
|
||||
TradfriGatewayHandler handler = (TradfriGatewayHandler) tradfriGateway.getHandler();
|
||||
|
||||
String uriString = handler.getGatewayURI() + "/" + id;
|
||||
try {
|
||||
URI uri = new URI(uriString);
|
||||
coapClient = new TradfriCoapClient(uri);
|
||||
coapClient.setEndpoint(handler.getEndpoint());
|
||||
} catch (URISyntaxException e) {
|
||||
logger.debug("Illegal device URI `{}`: {}", uriString, e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
switch (tradfriGateway.getStatus()) {
|
||||
case ONLINE:
|
||||
scheduler.schedule(() -> {
|
||||
observeRelation = coapClient.startObserve(this);
|
||||
}, 3, TimeUnit.SECONDS);
|
||||
break;
|
||||
case OFFLINE:
|
||||
default:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
String.format("Gateway offline '%s'", tradfriGateway.getStatusInfo()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
active = false;
|
||||
if (observeRelation != null) {
|
||||
observeRelation.reactiveCancel();
|
||||
observeRelation = null;
|
||||
}
|
||||
if (coapClient != null) {
|
||||
coapClient.shutdown();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public void setStatus(ThingStatus status, ThingStatusDetail statusDetail) {
|
||||
if (active && getBridge().getStatus() != ThingStatus.OFFLINE && status != ThingStatus.ONLINE) {
|
||||
updateStatus(status, statusDetail);
|
||||
// we are offline and lost our observe relation - let's try to establish the connection in 10 seconds again
|
||||
scheduler.schedule(() -> {
|
||||
if (observeRelation != null) {
|
||||
observeRelation.reactiveCancel();
|
||||
observeRelation = null;
|
||||
}
|
||||
observeRelation = coapClient.startObserve(this);
|
||||
}, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
super.bridgeStatusChanged(bridgeStatusInfo);
|
||||
// the status might have changed because the bridge is completely reconfigured - so we need to re-establish
|
||||
// our CoAP connection as well
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
dispose();
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
protected void set(String payload) {
|
||||
logger.debug("Sending payload: {}", payload);
|
||||
coapClient.asyncPut(payload, this, scheduler);
|
||||
}
|
||||
|
||||
protected void updateDeviceProperties(TradfriDeviceData state) {
|
||||
String firmwareVersion = state.getFirmwareVersion();
|
||||
if (firmwareVersion != null) {
|
||||
getThing().setProperty(PROPERTY_FIRMWARE_VERSION, firmwareVersion);
|
||||
}
|
||||
|
||||
String modelId = state.getModelId();
|
||||
if (modelId != null) {
|
||||
getThing().setProperty(PROPERTY_MODEL_ID, modelId);
|
||||
}
|
||||
|
||||
String vendor = state.getVendor();
|
||||
if (vendor != null) {
|
||||
getThing().setProperty(PROPERTY_VENDOR, vendor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.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.openhab.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(BLINDS);
|
||||
}
|
||||
|
||||
public TradfriBlindData(JsonElement json) {
|
||||
super(BLINDS, json);
|
||||
}
|
||||
|
||||
public TradfriBlindData setPosition(PercentType position) {
|
||||
attributes.add(POSITION, new JsonPrimitive(position.intValue()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradfriBlindData stop() {
|
||||
attributes.add(STOP_TRIGGER, new JsonPrimitive(0));
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable PercentType getPosition() {
|
||||
PercentType result = null;
|
||||
|
||||
JsonElement position = attributes.get(POSITION);
|
||||
if (position != null) {
|
||||
int percent = position.getAsInt();
|
||||
percent = Math.max(percent, 0);
|
||||
percent = Math.min(100, percent);
|
||||
result = new PercentType(percent);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getJsonString() {
|
||||
return root.toString();
|
||||
}
|
||||
}
|
||||
@@ -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.tradfri.internal.model;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.SWITCH;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriControllerData} class is a Java wrapper for the raw JSON data about the controller state.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriControllerData extends TradfriWirelessDeviceData {
|
||||
|
||||
public TradfriControllerData(JsonElement json) {
|
||||
super(SWITCH, json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link TradfriDeviceData} class is a Java wrapper for the raw JSON data about the device state.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Christoph Weitkamp - Restructuring and refactoring of the binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class TradfriDeviceData {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriDeviceData.class);
|
||||
|
||||
protected JsonObject root;
|
||||
protected JsonArray array;
|
||||
protected JsonObject attributes;
|
||||
protected JsonObject generalInfo;
|
||||
|
||||
public TradfriDeviceData(String attributesNodeName) {
|
||||
root = new JsonObject();
|
||||
array = new JsonArray();
|
||||
attributes = new JsonObject();
|
||||
array.add(attributes);
|
||||
root.add(attributesNodeName, array);
|
||||
generalInfo = new JsonObject();
|
||||
root.add(DEVICE, generalInfo);
|
||||
}
|
||||
|
||||
public TradfriDeviceData(String attributesNodeName, JsonElement json) {
|
||||
try {
|
||||
root = json.getAsJsonObject();
|
||||
if (root.has(attributesNodeName)) {
|
||||
array = root.getAsJsonArray(attributesNodeName);
|
||||
attributes = array.get(0).getAsJsonObject();
|
||||
} else {
|
||||
array = new JsonArray();
|
||||
attributes = new JsonObject();
|
||||
array.add(attributes);
|
||||
}
|
||||
generalInfo = root.getAsJsonObject(DEVICE);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("JSON error: {}", e.getMessage(), e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getDeviceId() {
|
||||
return root.get(INSTANCE_ID).getAsInt();
|
||||
}
|
||||
|
||||
public boolean getReachabilityStatus() {
|
||||
if (root.get(REACHABILITY_STATE) != null) {
|
||||
return root.get(REACHABILITY_STATE).getAsInt() == 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getFirmwareVersion() {
|
||||
if (generalInfo.get(DEVICE_FIRMWARE) != null) {
|
||||
return generalInfo.get(DEVICE_FIRMWARE).getAsString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getModelId() {
|
||||
if (generalInfo.get(DEVICE_MODEL) != null) {
|
||||
return generalInfo.get(DEVICE_MODEL).getAsString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getVendor() {
|
||||
if (generalInfo.get(DEVICE_VENDOR) != null) {
|
||||
return generalInfo.get(DEVICE_VENDOR).getAsString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.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.openhab.binding.tradfri.internal.TradfriColor;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* The {@link TradfriLightData} class is a Java wrapper for the raw JSON data about the light state.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Holger Reichert - Support for color bulbs
|
||||
* @author Christoph Weitkamp - Restructuring and refactoring of the binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriLightData extends TradfriDeviceData {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TradfriLightData.class);
|
||||
|
||||
public TradfriLightData() {
|
||||
super(LIGHT);
|
||||
}
|
||||
|
||||
public TradfriLightData(JsonElement json) {
|
||||
super(LIGHT, json);
|
||||
}
|
||||
|
||||
public TradfriLightData setBrightness(PercentType brightness) {
|
||||
attributes.add(DIMMER, new JsonPrimitive((int) Math.floor(brightness.doubleValue() * 2.54)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable PercentType getBrightness() {
|
||||
PercentType result = null;
|
||||
|
||||
JsonElement dimmer = attributes.get(DIMMER);
|
||||
if (dimmer != null) {
|
||||
result = TradfriColor.xyBrightnessToPercentType(dimmer.getAsInt());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public TradfriLightData setTransitionTime(int seconds) {
|
||||
attributes.add(TRANSITION_TIME, new JsonPrimitive(seconds));
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTransitionTime() {
|
||||
JsonElement transitionTime = attributes.get(TRANSITION_TIME);
|
||||
if (transitionTime != null) {
|
||||
return transitionTime.getAsInt();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public TradfriLightData setColorTemperature(PercentType c) {
|
||||
TradfriColor color = new TradfriColor(c);
|
||||
int x = color.xyX;
|
||||
int y = color.xyY;
|
||||
logger.debug("New color temperature: {},{} ({} %)", x, y, c.intValue());
|
||||
attributes.add(COLOR_X, new JsonPrimitive(x));
|
||||
attributes.add(COLOR_Y, new JsonPrimitive(y));
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable PercentType getColorTemperature() {
|
||||
JsonElement colorX = attributes.get(COLOR_X);
|
||||
JsonElement colorY = attributes.get(COLOR_Y);
|
||||
if (colorX != null && colorY != null) {
|
||||
TradfriColor color = new TradfriColor(colorX.getAsInt(), colorY.getAsInt(), null);
|
||||
return color.getColorTemperature();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public TradfriLightData setColor(HSBType hsb) {
|
||||
TradfriColor color = new TradfriColor(hsb);
|
||||
attributes.add(COLOR_X, new JsonPrimitive(color.xyX));
|
||||
attributes.add(COLOR_Y, new JsonPrimitive(color.xyY));
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable HSBType getColor() {
|
||||
// XY color coordinates plus brightness is needed for color calculation
|
||||
JsonElement colorX = attributes.get(COLOR_X);
|
||||
JsonElement colorY = attributes.get(COLOR_Y);
|
||||
JsonElement dimmer = attributes.get(DIMMER);
|
||||
if (colorX != null && colorY != null && dimmer != null) {
|
||||
int x = colorX.getAsInt();
|
||||
int y = colorY.getAsInt();
|
||||
int brightness = dimmer.getAsInt();
|
||||
TradfriColor color = new TradfriColor(x, y, brightness);
|
||||
return color.getHSB();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TradfriLightData setOnOffState(boolean on) {
|
||||
attributes.add(ONOFF, new JsonPrimitive(on ? 1 : 0));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getOnOffState() {
|
||||
JsonElement onOff = attributes.get(ONOFF);
|
||||
if (onOff != null) {
|
||||
return onOff.getAsInt() == 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getJsonString() {
|
||||
return root.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.tradfri.internal.model;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* The {@link TradfriPlugData} class is a Java wrapper for the raw JSON data about the plug state.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriPlugData extends TradfriDeviceData {
|
||||
|
||||
public TradfriPlugData() {
|
||||
super(PLUG);
|
||||
}
|
||||
|
||||
public TradfriPlugData(JsonElement json) {
|
||||
super(PLUG, json);
|
||||
}
|
||||
|
||||
public TradfriPlugData setTransitionTime(int seconds) {
|
||||
attributes.add(TRANSITION_TIME, new JsonPrimitive(seconds));
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTransitionTime() {
|
||||
JsonElement transitionTime = attributes.get(TRANSITION_TIME);
|
||||
if (transitionTime != null) {
|
||||
return transitionTime.getAsInt();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public TradfriPlugData setOnOffState(boolean on) {
|
||||
attributes.add(ONOFF, new JsonPrimitive(on ? 1 : 0));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getOnOffState() {
|
||||
JsonElement onOff = attributes.get(ONOFF);
|
||||
if (onOff != null) {
|
||||
return onOff.getAsInt() == 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getJsonString() {
|
||||
return root.toString();
|
||||
}
|
||||
}
|
||||
@@ -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.tradfri.internal.model;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.SENSOR;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriSensorData} class is a Java wrapper for the raw JSON data about the sensor state.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriSensorData extends TradfriWirelessDeviceData {
|
||||
|
||||
public TradfriSensorData(JsonElement json) {
|
||||
super(SENSOR, json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.tradfri.internal.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link TradfriVersion} class is a default implementation for comparing TRÅDFRI versions.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriVersion implements Comparable<TradfriVersion> {
|
||||
private static final String VERSION_PATTERN = "[0-9]+(\\.[0-9]+)*";
|
||||
private static final String VERSION_DELIMITER = "\\.";
|
||||
final List<Integer> parts;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param version the version string
|
||||
*/
|
||||
public TradfriVersion(final String version) {
|
||||
if (!version.matches(VERSION_PATTERN)) {
|
||||
throw new IllegalArgumentException("TradfriVersion cannot be created as version has invalid format.");
|
||||
}
|
||||
parts = Arrays.stream(version.split(VERSION_DELIMITER)).map(part -> Integer.parseInt(part))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final TradfriVersion other) {
|
||||
int minSize = Math.min(parts.size(), other.parts.size());
|
||||
for (int i = 0; i < minSize; ++i) {
|
||||
int diff = parts.get(i) - other.parts.get(i);
|
||||
if (diff == 0) {
|
||||
continue;
|
||||
} else if (diff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
for (int i = minSize; i < parts.size(); ++i) {
|
||||
if (parts.get(i) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
for (int i = minSize; i < other.parts.size(); ++i) {
|
||||
if (other.parts.get(i) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return compareTo((TradfriVersion) obj) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parts.stream().map(String::valueOf).collect(Collectors.joining("."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.tradfri.internal.model;
|
||||
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.DEVICE_BATTERY_LEVEL;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link TradfriWirelessDeviceData} class is a Java wrapper for the raw JSON data about wireless device state.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class TradfriWirelessDeviceData extends TradfriDeviceData {
|
||||
|
||||
public TradfriWirelessDeviceData(String attributesNodeName) {
|
||||
super(attributesNodeName);
|
||||
}
|
||||
|
||||
public TradfriWirelessDeviceData(String attributesNodeName, JsonElement json) {
|
||||
super(attributesNodeName, json);
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getBatteryLevel() {
|
||||
if (generalInfo.get(DEVICE_BATTERY_LEVEL) != null) {
|
||||
return new DecimalType(generalInfo.get(DEVICE_BATTERY_LEVEL).getAsInt());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable OnOffType getBatteryLow() {
|
||||
if (generalInfo.get(DEVICE_BATTERY_LEVEL) != null) {
|
||||
return generalInfo.get(DEVICE_BATTERY_LEVEL).getAsInt() <= 10 ? OnOffType.ON : OnOffType.OFF;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="tradfri" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>TRÅDFRI Binding</name>
|
||||
<description>This binding supports IKEA TRÅDFRI lighting devices through the IKEA gateway.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="bridge-type:tradfri:gateway">
|
||||
<parameter name="host" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Host</label>
|
||||
<description>Hostname or IP address of the IKEA TRÅDFRI gateway</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false">
|
||||
<label>Port</label>
|
||||
<description>Port for accessing the gateway</description>
|
||||
<advanced>true</advanced>
|
||||
<default>5684</default>
|
||||
</parameter>
|
||||
<parameter name="code" type="text" required="false">
|
||||
<context>password</context>
|
||||
<label>Security Code</label>
|
||||
<description>Security code printed on the label underneath the gateway.</description>
|
||||
</parameter>
|
||||
<parameter name="identity" type="text" required="false">
|
||||
<advanced>true</advanced>
|
||||
<label>Identity</label>
|
||||
<description>Unique identity used for communication with the gateway</description>
|
||||
</parameter>
|
||||
<parameter name="preSharedKey" type="text" required="false">
|
||||
<advanced>true</advanced>
|
||||
<context>password</context>
|
||||
<label>Pre-Shared Security Key</label>
|
||||
<description>Security key obtained during first initialization of the gateway</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:tradfri:device">
|
||||
<parameter name="id" type="integer" required="true">
|
||||
<label>ID</label>
|
||||
<description>The identifier of the device on the gateway.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,47 @@
|
||||
# binding
|
||||
binding.tradfri.name = TRÅDFRI Binding
|
||||
binding.tradfri.description = Dieses Binding integriert das IKEA TRÅDFRI System. Durch dieses können die TRÅDFRI Lampen und Leuchten gesteuert werden.
|
||||
|
||||
# bridge types
|
||||
thing-type.tradfri.gateway.label = TRÅDFRI Gateway
|
||||
thing-type.tradfri.gateway.description = IKEA TRÅDFRI Gateway/zentrale Steuereinheit.
|
||||
|
||||
# bridge type configuration
|
||||
bridge-type.config.tradfri.gateway.host.label = IP-Adresse
|
||||
bridge-type.config.tradfri.gateway.host.description = Lokale IP-Adresse oder Hostname des TRÅDFRI Gateway.
|
||||
bridge-type.config.tradfri.gateway.port.label = Port
|
||||
bridge-type.config.tradfri.gateway.port.description = Port des TRÅDFRI Gateway.
|
||||
bridge-type.config.tradfri.gateway.code.label = Security Code
|
||||
bridge-type.config.tradfri.gateway.code.description = Security Code zur Authentifizierung am TRÅDFRI Gateway. Befindet sich unterhalb des TRÅDFRI Gateway.
|
||||
|
||||
# thing types
|
||||
thing-type.tradfri.0100.label = Dimmbare Lampe (weiß)
|
||||
thing-type.tradfri.0100.description = Dimmbare Lampe mit fester Farbtemperatur.
|
||||
thing-type.tradfri.0210.label = Farbspektrum Lampe
|
||||
thing-type.tradfri.0210.description = Dimmbare Lampe mit einstellbarer Farbe und Farbtemperatur.
|
||||
thing-type.tradfri.0220.label = Farbtemperatur Lampe (weiß)
|
||||
thing-type.tradfri.0220.description = Dimmbare Lampe mit einstellbarer Farbtemperatur.
|
||||
thing-type.tradfri.0107.label = Funk-Bewegungsmelder
|
||||
thing-type.tradfri.0107.description = Der Funk-Bewegungsmelder liefert Daten wie z.B. die Batterieladung.
|
||||
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.0202.label = Rollo
|
||||
thing-type.tradfri.0202.description = Batteriebetriebenes Rollo mit einstellbarer Position. Liefert außerdem Daten wie z.B. die Batterieladung.
|
||||
thing-type.tradfri.0203.label = Rollo-Fernbedienung
|
||||
thing-type.tradfri.0203.description = Die Rollo-Fernbedienung liefert Daten wie z.B. die Batterieladung.
|
||||
|
||||
# thing types config
|
||||
thing-type.config.tradfri.device.id.label = ID des Gerätes
|
||||
thing-type.config.tradfri.device.id.description = ID zur Identifikation des Gerätes.
|
||||
|
||||
# channel types
|
||||
channel-type.tradfri.brightness.label = Helligkeit
|
||||
channel-type.tradfri.brightness.description = Ermöglicht die Steuerung der Helligkeit. Ermöglicht ebenfalls die Lampe ein- und auszuschalten.
|
||||
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).
|
||||
@@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="tradfri"
|
||||
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">
|
||||
|
||||
<bridge-type id="gateway">
|
||||
<label>TRÅDFRI Gateway</label>
|
||||
<description>IKEA TRÅDFRI IP Gateway</description>
|
||||
|
||||
<representation-property>host</representation-property>
|
||||
|
||||
<config-description-ref uri="bridge-type:tradfri:gateway"/>
|
||||
</bridge-type>
|
||||
|
||||
<!-- thing types for devices -->
|
||||
<!-- their IDs refer to the Zigbee Lightlink device ids (see chapter 2.2 in https://www.nxp.com/documents/user_manual/JN-UG-3091.pdf) -->
|
||||
<thing-type id="0010">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>On/Off Plug</label>
|
||||
<description>A plug that can be switched on and off.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0100">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Dimmable Light</label>
|
||||
<description>A light that has continuous brightness control.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="brightness" typeId="brightness"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0220">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Color Temperature Light</label>
|
||||
<description>A dimmable light that supports different color temperature settings.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="brightness" typeId="brightness"/>
|
||||
<channel id="color_temperature" typeId="color_temperature"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0210">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Color Light</label>
|
||||
<description>A dimmable light that supports full colors and color temperature settings.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="color_temperature" typeId="color_temperature"/>
|
||||
<channel id="color" typeId="color"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0107">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Occupancy Sensor</label>
|
||||
<description>This represents the motion sensor capable of reporting the battery level.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_level" typeId="system.battery-level"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0820" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Non-Colour Controller</label>
|
||||
<description>This represents the wireless dimmer sensor capable of reporting the battery level.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_level" typeId="system.battery-level"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0830">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Non-Colour Scene Controller</label>
|
||||
<description>This represents the remote control capable of reporting the battery level.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_level" typeId="system.battery-level"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0202">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Window Covering Device</label>
|
||||
<description>This represents a blind or curtain that can be moved up and down. Also reports current battery level.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="position" typeId="position"/>
|
||||
<channel id="battery_level" typeId="system.battery-level"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="0203" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Window Covering Controller</label>
|
||||
<description>This represents the wireless open/close remote capable of reporting the battery level.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_level" typeId="system.battery-level"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:tradfri:device"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="brightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>Control the brightness and switch the light on and off.</description>
|
||||
<category>DimmableLight</category>
|
||||
<tags>
|
||||
<tag>Lighting</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="position">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Position</label>
|
||||
<description>Control the position of the blind or curtain in percent from 0 (open) to 100 (closed).</description>
|
||||
<category>Blinds</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color_temperature">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Color Temperature</label>
|
||||
<description>Control the color temperature of the light.</description>
|
||||
<category>ColorLight</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>Color</label>
|
||||
<description>Control the color of the light.</description>
|
||||
<category>ColorLight</category>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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.tradfri.internal;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* Tests for {@link TradfriColor}.
|
||||
*
|
||||
* @author Holger Reichert - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriColorTest {
|
||||
|
||||
@Test
|
||||
public void testFromCieKnownGood1() {
|
||||
TradfriColor color = new TradfriColor(29577, 12294, 354);
|
||||
assertEquals(29577, (int) color.xyX);
|
||||
assertEquals(12294, (int) color.xyY);
|
||||
assertEquals(254, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(321, hsbType.getHue().intValue());
|
||||
assertEquals(100, hsbType.getSaturation().intValue());
|
||||
assertEquals(100, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCieKnownGood2() {
|
||||
TradfriColor color = new TradfriColor(19983, 37417, 84);
|
||||
assertEquals(19983, (int) color.xyX);
|
||||
assertEquals(37417, (int) color.xyY);
|
||||
assertEquals(84, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(115, hsbType.getHue().intValue());
|
||||
assertEquals(77, hsbType.getSaturation().intValue());
|
||||
assertEquals(34, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCieKnownGood3() {
|
||||
TradfriColor color = new TradfriColor(19983, 37417, 1);
|
||||
assertEquals(19983, (int) color.xyX);
|
||||
assertEquals(37417, (int) color.xyY);
|
||||
assertEquals(1, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(115, hsbType.getHue().intValue());
|
||||
assertEquals(77, hsbType.getSaturation().intValue());
|
||||
assertEquals(1, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCieKnownGood4() {
|
||||
TradfriColor color = new TradfriColor(11413, 31334, 181);
|
||||
assertEquals(11413, (int) color.xyX);
|
||||
assertEquals(31334, (int) color.xyY);
|
||||
assertEquals(181, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(158, hsbType.getHue().intValue());
|
||||
assertEquals(100, hsbType.getSaturation().intValue());
|
||||
assertEquals(72, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromHSBTypeKnownGood1() {
|
||||
TradfriColor color = new TradfriColor(HSBType.RED);
|
||||
assertEquals(41947, (int) color.xyX);
|
||||
assertEquals(21625, (int) color.xyY);
|
||||
assertEquals(254, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(0, hsbType.getHue().intValue());
|
||||
assertEquals(100, hsbType.getSaturation().intValue());
|
||||
assertEquals(100, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromHSBTypeKnownGood2() {
|
||||
TradfriColor color = new TradfriColor(new HSBType("0,100,1"));
|
||||
assertEquals(41947, (int) color.xyX);
|
||||
assertEquals(21625, (int) color.xyY);
|
||||
assertEquals(2, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(0, hsbType.getHue().intValue());
|
||||
assertEquals(100, hsbType.getSaturation().intValue());
|
||||
assertEquals(1, hsbType.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConversionReverse() {
|
||||
// convert from HSBType
|
||||
TradfriColor color = new TradfriColor(HSBType.GREEN);
|
||||
assertEquals(19660, (int) color.xyX);
|
||||
assertEquals(39321, (int) color.xyY);
|
||||
assertEquals(254, (int) color.brightness);
|
||||
HSBType hsbType = color.getHSB();
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(120, hsbType.getHue().intValue());
|
||||
assertEquals(100, hsbType.getSaturation().intValue());
|
||||
assertEquals(100, hsbType.getBrightness().intValue());
|
||||
// convert the result again based on the XY values
|
||||
TradfriColor reverse = new TradfriColor(color.xyX, color.xyY, color.brightness);
|
||||
assertEquals(19660, (int) reverse.xyX);
|
||||
assertEquals(39321, (int) reverse.xyY);
|
||||
assertEquals(254, (int) reverse.brightness);
|
||||
HSBType hsbTypeReverse = color.getHSB();
|
||||
assertNotNull(hsbTypeReverse);
|
||||
assertEquals(120, hsbTypeReverse.getHue().intValue());
|
||||
assertEquals(100, hsbTypeReverse.getSaturation().intValue());
|
||||
assertEquals(100, hsbTypeReverse.getBrightness().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromColorTemperatureMinMiddleMax() {
|
||||
// coldest color temperature -> preset 1
|
||||
TradfriColor colorMin = new TradfriColor(PercentType.ZERO);
|
||||
assertNotNull(colorMin);
|
||||
assertEquals(24933, (int) colorMin.xyX);
|
||||
assertEquals(24691, (int) colorMin.xyY);
|
||||
// middle color temperature -> preset 2
|
||||
TradfriColor colorMiddle = new TradfriColor(new PercentType(50));
|
||||
assertNotNull(colorMiddle);
|
||||
assertEquals(30138, (int) colorMiddle.xyX);
|
||||
assertEquals(26909, (int) colorMiddle.xyY);
|
||||
// warmest color temperature -> preset 3
|
||||
TradfriColor colorMax = new TradfriColor(PercentType.HUNDRED);
|
||||
assertNotNull(colorMax);
|
||||
assertEquals(33137, (int) colorMax.xyX);
|
||||
assertEquals(27211, (int) colorMax.xyY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromColorTemperatureInbetween() {
|
||||
// 30 percent must be between preset 1 and 2
|
||||
TradfriColor color2 = new TradfriColor(new PercentType(30));
|
||||
assertNotNull(color2);
|
||||
assertEquals(28056, (int) color2.xyX);
|
||||
assertEquals(26022, (int) color2.xyY);
|
||||
// 70 percent must be between preset 2 and 3
|
||||
TradfriColor color3 = new TradfriColor(new PercentType(70));
|
||||
assertNotNull(color3);
|
||||
assertEquals(31338, (int) color3.xyX);
|
||||
assertEquals(27030, (int) color3.xyY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalculateColorTemperature() {
|
||||
// preset 1 -> coldest -> 0 percent
|
||||
PercentType preset1 = new TradfriColor(24933, 24691, null).getColorTemperature();
|
||||
assertEquals(0, preset1.intValue());
|
||||
// preset 2 -> middle -> 50 percent
|
||||
PercentType preset2 = new TradfriColor(30138, 26909, null).getColorTemperature();
|
||||
assertEquals(50, preset2.intValue());
|
||||
// preset 3 -> warmest -> 100 percent
|
||||
PercentType preset3 = new TradfriColor(33137, 27211, null).getColorTemperature();
|
||||
assertEquals(100, preset3.intValue());
|
||||
// preset 3 -> warmest -> 100 percent
|
||||
PercentType colder = new TradfriColor(22222, 23333, null).getColorTemperature();
|
||||
assertEquals(0, colder.intValue());
|
||||
// preset 3 -> warmest -> 100 percent
|
||||
PercentType temp3 = new TradfriColor(34000, 34000, null).getColorTemperature();
|
||||
assertEquals(100, temp3.intValue());
|
||||
// mixed case 1
|
||||
PercentType mixed1 = new TradfriColor(0, 1000000, null).getColorTemperature();
|
||||
assertEquals(0, mixed1.intValue());
|
||||
// mixed case 1
|
||||
PercentType mixed2 = new TradfriColor(1000000, 0, null).getColorTemperature();
|
||||
assertEquals(100, mixed2.intValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* 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.tradfri.internal.discovery;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
|
||||
import static org.openhab.binding.tradfri.internal.config.TradfriDeviceConfig.CONFIG_ID;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultFlag;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Tests for {@link TradfriDiscoveryService}.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
|
||||
*/
|
||||
public class TradfriDiscoveryServiceTest {
|
||||
|
||||
private static final ThingUID GATEWAY_THING_UID = new ThingUID("tradfri:gateway:1");
|
||||
|
||||
@Mock
|
||||
private TradfriGatewayHandler handler;
|
||||
|
||||
private final DiscoveryListener listener = new DiscoveryListener() {
|
||||
@Override
|
||||
public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
|
||||
discoveryResult = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
|
||||
Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiscoveryResult discoveryResult;
|
||||
|
||||
private TradfriDiscoveryService discovery;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
initMocks(this);
|
||||
|
||||
when(handler.getThing()).thenReturn(BridgeBuilder.create(GATEWAY_TYPE_UID, "1").build());
|
||||
|
||||
discovery = new TradfriDiscoveryService();
|
||||
discovery.setThingHandler(handler);
|
||||
discovery.addDiscoveryListener(listener);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
discoveryResult = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctSupportedTypes() {
|
||||
assertThat(discovery.getSupportedThingTypes().size(), is(9));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_DIMMABLE_LIGHT));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_COLOR_TEMP_LIGHT));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_COLOR_LIGHT));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_DIMMER));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_MOTION_SENSOR));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_REMOTE_CONTROL));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_ONOFF_PLUG));
|
||||
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_BLINDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultWhiteLightW() {
|
||||
String json = "{\"9001\":\"TRADFRI bulb E27 W opal 1000lm\",\"9002\":1492856270,\"9020\":1507194357,\"9003\":65537,\"3311\":[{\"5850\":1,\"5851\":254,\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 W opal 1000lm\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":1}}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65537", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0100:1:65537")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_DIMMABLE_LIGHT));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65537));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultWhiteLightWS() {
|
||||
String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1507200447,\"9003\":65537,\"3311\":[{\"5710\":26909,\"5850\":1,\"5851\":203,\"5707\":0,\"5708\":0,\"5709\":30140,\"5711\":370,\"5706\":\"f1e0b5\",\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65537", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_COLOR_TEMP_LIGHT));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65537));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultWhiteLightWSWithIncompleteJson() {
|
||||
// We do not always receive a COLOR = "5706" attribute, even the light supports it - but the gateway does not
|
||||
// seem to have this information, if the bulb is unreachable.
|
||||
String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1506968670,\"9003\":65537,\"3311\":[{\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":0,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65537", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_COLOR_TEMP_LIGHT));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65537));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultColorLightCWS() {
|
||||
String json = "{\"9001\":\"TRADFRI bulb E27 CWS opal 600lm\",\"9002\":1505151864,\"9020\":1505433527,\"9003\":65550,\"9019\":1,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 CWS opal 600lm\",\"2\":\"\",\"3\":\"1.3.002\",\"6\":1},\"3311\":[{\"5850\":1,\"5708\":0,\"5851\":254,\"5707\":0,\"5709\":33137,\"5710\":27211,\"5711\":0,\"5706\":\"efd275\",\"9003\":0}]}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65550", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65550")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_COLOR_LIGHT));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65550));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultAlternativeColorLightCWS() {
|
||||
String json = "{\"3311\":[{\"5850\":1,\"5709\":32886,\"5851\":216,\"5707\":5309,\"5708\":52400,\"5710\":27217,\"5706\":\"efd275\",\"9003\":0}],\"9001\":\"Mushroom lamp\",\"9002\":1571036916,\"9020\":1571588312,\"9003\":65539,\"9054\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 C\\/WS opal 600\",\"2\":\"\",\"3\":\"1.3.009\",\"6\":1},\"5750\":2}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65539", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65539")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_COLOR_LIGHT));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65539));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultRemoteControl() {
|
||||
String json = "{\"9001\":\"TRADFRI remote control\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI remote control\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65536", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0830:1:65536")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_REMOTE_CONTROL));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65536));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultWirelessDimmer() {
|
||||
String json = "{\"9001\":\"TRADFRI wireless dimmer\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI wireless dimmer\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65536", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0820:1:65536")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_DIMMER));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65536));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDiscoveryResultMotionSensor() {
|
||||
String json = "{\"9001\":\"TRADFRI motion sensor\",\"9002\":1492955083,\"9020\":1507120083,\"9003\":65538,\"9054\":0,\"5750\":4,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI motion sensor\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":60},\"3300\":[{\"9003\":0}]}";
|
||||
JsonObject data = new JsonParser().parse(json).getAsJsonObject();
|
||||
|
||||
discovery.onUpdate("65538", data);
|
||||
|
||||
assertNotNull(discoveryResult);
|
||||
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
|
||||
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0107:1:65538")));
|
||||
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_MOTION_SENSOR));
|
||||
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
|
||||
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65538));
|
||||
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.tradfri.internal.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link TradfriVersion}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TradfriVersionTest {
|
||||
|
||||
private static final int LESS_THAN = -1;
|
||||
private static final int EQUAL_TO = 0;
|
||||
private static final int GREATER_THAN = 1;
|
||||
|
||||
private static final String VERSION_STRING = "1.2.42";
|
||||
private static final TradfriVersion VERSION = new TradfriVersion(VERSION_STRING);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIllegalArgumentException() throws IllegalArgumentException {
|
||||
new TradfriVersion("FAILURE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParts() {
|
||||
assertEquals(Arrays.asList(1, 2, 42), VERSION.parts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToEqualTo() {
|
||||
assertEquals(EQUAL_TO, VERSION.compareTo(VERSION));
|
||||
assertEquals(EQUAL_TO, VERSION.compareTo(new TradfriVersion(VERSION_STRING)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToLessThan() {
|
||||
assertEquals(LESS_THAN, VERSION.compareTo(new TradfriVersion("2")));
|
||||
assertEquals(LESS_THAN, VERSION.compareTo(new TradfriVersion("1.3")));
|
||||
assertEquals(LESS_THAN, VERSION.compareTo(new TradfriVersion("1.2.50")));
|
||||
assertEquals(LESS_THAN, VERSION.compareTo(new TradfriVersion("1.2.42.5")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToGreaterThan() {
|
||||
assertEquals(GREATER_THAN, VERSION.compareTo(new TradfriVersion("1")));
|
||||
assertEquals(GREATER_THAN, VERSION.compareTo(new TradfriVersion("1.1")));
|
||||
assertEquals(GREATER_THAN, VERSION.compareTo(new TradfriVersion("1.2.30")));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertTrue(VERSION.equals(VERSION));
|
||||
assertTrue(VERSION.equals(new TradfriVersion(VERSION_STRING)));
|
||||
|
||||
assertFalse(VERSION.equals((TradfriVersion) null));
|
||||
assertFalse(VERSION.equals(new Integer("1")));
|
||||
assertFalse(VERSION.equals(new TradfriVersion("1.2.5")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
assertEquals(VERSION_STRING, VERSION.toString());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user