added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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