]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Broadlinkthermostat] Initial contribution (#9260)
authorFlorian Mueller <46089838+flo-02-mu@users.noreply.github.com>
Mon, 8 Feb 2021 22:08:09 +0000 (23:08 +0100)
committerGitHub <noreply@github.com>
Mon, 8 Feb 2021 22:08:09 +0000 (23:08 +0100)
Signed-off-by: Florian Mueller <f.l.o.mueller@web.de>
16 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.broadlinkthermostat/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/README.md [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatBindingConstants.java [new file with mode: 0755]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatHandlerFactory.java [new file with mode: 0755]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/discovery/BroadlinkThermostatDiscoveryService.java [new file with mode: 0755]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/BroadlinkThermostatHandler.java [new file with mode: 0755]
bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/FloureonThermostatHandler.java [new file with mode: 0755]
bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/config/config.xml [new file with mode: 0644]
bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 4a69d5e57644e3854c9d54cd6e02412cb1eb352b..0eac39f7f3850233a042296920aa4c4c4da186af 100644 (file)
@@ -38,6 +38,7 @@
 /bundles/org.openhab.binding.boschindego/ @jofleck
 /bundles/org.openhab.binding.boschshc/ @stefan-kaestle @coeing @GerdZanker
 /bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho
+/bundles/org.openhab.binding.broadlinkthermostat/ @flo_02_mu
 /bundles/org.openhab.binding.bsblan/ @hypetsch
 /bundles/org.openhab.binding.bticinosmarther/ @MrRonfo
 /bundles/org.openhab.binding.buienradar/ @gedejong
index 8569f1c6e39e9e7b2311640144418c86be58c3d3..2190e4f6ad7c8087f68d85eca79d2e53c7eac8fa 100644 (file)
       <artifactId>org.openhab.binding.bosesoundtouch</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.broadlinkthermostat</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.bsblan</artifactId>
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/NOTICE b/bundles/org.openhab.binding.broadlinkthermostat/NOTICE
new file mode 100644 (file)
index 0000000..33ded0d
--- /dev/null
@@ -0,0 +1,20 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
+
+== Third-party Content
+
+broadlink-java-api
+* License: MIT License
+* Project: https://github.com/mob41/broadlink-java-api
+* Source: https://github.com/mob41/broadlink-java-api
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/README.md b/bundles/org.openhab.binding.broadlinkthermostat/README.md
new file mode 100644 (file)
index 0000000..6935e4e
--- /dev/null
@@ -0,0 +1,67 @@
+# Broadlink Thermostat Binding
+
+The binding integrates devices based on Broadlinkthermostat controllers.
+As the binding uses the [broadlink-java-api](https://github.com/mob41/broadlink-java-api), theoretically all devices supported by the api can be integrated with this binding.
+
+## Supported Things
+
+*Note:* So far only the Floureon Thermostat has been tested! The other things are "best guess" implementations.
+
+| Things                  | Description                                                   | Thing Type           |
+|-------------------------|---------------------------------------------------------------|----------------------|
+| Floureon Thermostat     | Broadlinkthermostat based Thermostat sold with the branding Floureon    | floureonthermostat   |
+| Hysen Thermostat        | Broadlinkthermostat based Thermostat sold with the branding Hysen       | hysenthermostat      |
+
+## Discovery
+
+Broadlinkthermostat devices are discovered on the network by sending a specific broadcast message.
+Authentication is automatically sent after creating the thing.
+
+## Thing Configuration
+
+Two parameter are required for creating things:
+
+- `host`: The hostname or IP address of the device.
+- `mac` : The network MAC of the device.
+
+The autodiscovery process finds both parts automatically.
+
+## Channels
+
+### Floureon-/Hysenthermostat
+
+| Channel Type ID               | Item Type          | Description                                                                                                                                                                           |
+|-------------------------------|--------------------|----------------------------------------------------------------------|
+| power                         | Switch             | Switch display on/off and enable/disables heating                    |
+| mode                          | String             | Current mode of the thermostat (`auto` or `manual`)                  |
+| sensor                        | String             | The sensor (`internal`/`external`) used for triggering the thermostat|
+| roomtemperature               | Number:Temperature | Room temperature, measured directly at the device                    |
+| roomtemperatureexternalsensor | Number:Temperature | Room temperature, measured by an external sensor                     |
+| active                        | Switch             | Show if thermostat is currently actively heating                     |
+| setpoint                      | Number:Temperature | Temperature setpoint that open/close valve                           |
+| temperatureoffset             | Number:Temperature | Manual temperature adjustment                                        |
+| remotelock                    | Switch             | Locks the device to only allow remote actions                        |
+| time                          | DateTime           | The time and day of week of the device                               |
+
+## Full Example
+
+demo.things:
+
+```
+Thing broadlinkthermostat:floureonthermostat:bathroomthermostat "Bathroom Thermostat" [ host="192.168.0.23", mac="00:10:FA:6E:38:4A"]
+```
+
+demo.items:
+
+```
+Number:Temperature  Bathroom_Thermostat_Temperature      "Room temperature [%.1f %unit%]"        <temperature>  { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:roomtemperature"}
+Number:Temperature  Bathroom_Thermostat_Temperature_Ext  "Room temperature (ext) [%.1f %unit%]"  <temperature>  { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:roomtemperature"}
+Number:Temperature  Bathroom_Thermostat_Setpoint         "Setpoint [%.1f %unit%]"                <temperature>  { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:setpoint"}
+Switch              Bathroom_Thermostat_Power            "Power"                                                { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:power"}
+Switch              Bathroom_Thermostat_Active           "Active"                                               { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:active"}
+String              Bathroom_Thermostat_Mode             "Mode"                                                 { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:mode"}
+String              Bathroom_Thermostat_Sensor           "Sensor"                                               { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:sensor"}
+Switch              Bathroom_Thermostat_Lock             "Lock"                                  <lock>         { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:remotelock"}
+DateTime            Bathroom_Thermostat_Time             "Time [%1$tm/%1$td %1$tH:%1$tM]"        <time>         { channel="broadlinkthermostat:floureonthermostat:bathroomthermostat:time"}
+
+```
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/pom.xml b/bundles/org.openhab.binding.broadlinkthermostat/pom.xml
new file mode 100644 (file)
index 0000000..86e7ed1
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>3.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.broadlinkthermostat</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Broadlink Thermostat Binding</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.github.mob41.blapi</groupId>
+      <artifactId>broadlink-java-api</artifactId>
+      <version>1.0.1</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/feature/feature.xml b/bundles/org.openhab.binding.broadlinkthermostat/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..90d2320
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.broadlinkthermostat-${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-broadlinkthermostat" description="Broadlink Thermostat Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <feature dependency="true">openhab.tp-jaxb</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.broadlinkthermostat/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatBindingConstants.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatBindingConstants.java
new file mode 100755 (executable)
index 0000000..027e519
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link BroadlinkThermostatBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+@NonNullByDefault
+public class BroadlinkThermostatBindingConstants {
+
+    private static final String BINDING_ID = "broadlinkthermostat";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID FLOUREON_THERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID,
+            "floureonthermostat");
+    public static final ThingTypeUID HYSEN_THERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, "hysenthermostat");
+    public static final ThingTypeUID UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID,
+            "unknownbroadlinkthermostatdevice");
+
+    // List of all Channel ids
+    public static final String ROOM_TEMPERATURE = "roomtemperature";
+    public static final String ROOM_TEMPERATURE_EXTERNAL_SENSOR = "roomtemperatureexternalsensor";
+    public static final String SETPOINT = "setpoint";
+    public static final String POWER = "power";
+    public static final String MODE = "mode";
+    public static final String SENSOR = "sensor";
+    public static final String TEMPERATURE_OFFSET = "temperatureoffset";
+    public static final String ACTIVE = "active";
+    public static final String REMOTE_LOCK = "remotelock";
+    public static final String TIME = "time";
+
+    // Config properties
+    public static final String HOST = "host";
+    public static final String DESCRIPTION = "description";
+
+    public static final String MODE_AUTO = "auto";
+    public static final String SENSOR_INTERNAL = "internal";
+    public static final String SENSOR_EXTERNAL = "external";
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatConfig.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatConfig.java
new file mode 100644 (file)
index 0000000..0c707aa
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link BroadlinkThermostatConfig} class holds the configuration properties of the thing.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+
+@NonNullByDefault
+public class BroadlinkThermostatConfig {
+    private String host;
+    private String macAddress;
+
+    public BroadlinkThermostatConfig() {
+        this.host = "0.0.0.0";
+        this.macAddress = "00:00:00:00";
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getMacAddress() {
+        return macAddress;
+    }
+
+    public void setMacAddress(String macAddress) {
+        this.macAddress = macAddress;
+    }
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatHandlerFactory.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/BroadlinkThermostatHandlerFactory.java
new file mode 100755 (executable)
index 0000000..34efd4d
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal;
+
+import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.broadlinkthermostat.internal.handler.FloureonThermostatHandler;
+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 BroadlinkThermostatHandlerFactory} is responsible for creating things and thing handlers.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+@Component(configurationPid = "binding.broadlinkthermostat", service = ThingHandlerFactory.class)
+@NonNullByDefault
+public class BroadlinkThermostatHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(FLOUREON_THERMOSTAT_THING_TYPE,
+            HYSEN_THERMOSTAT_THING_TYPE, UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE);
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (FLOUREON_THERMOSTAT_THING_TYPE.equals(thingTypeUID) || HYSEN_THERMOSTAT_THING_TYPE.equals(thingTypeUID)) {
+            return new FloureonThermostatHandler(thing);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/discovery/BroadlinkThermostatDiscoveryService.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/discovery/BroadlinkThermostatDiscoveryService.java
new file mode 100755 (executable)
index 0000000..1cd3962
--- /dev/null
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal.discovery;
+
+import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants.*;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants;
+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.net.NetworkAddressService;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.mob41.blapi.BLDevice;
+
+/**
+ * The {@link BroadlinkThermostatDiscoveryService} is responsible for discovering Broadlinkthermostat devices through
+ * Broadcast.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+@Component(service = DiscoveryService.class, configurationPid = "discovery.broadlinkthermostat")
+@NonNullByDefault
+public class BroadlinkThermostatDiscoveryService extends AbstractDiscoveryService {
+
+    private final Logger logger = LoggerFactory.getLogger(BroadlinkThermostatDiscoveryService.class);
+
+    private final NetworkAddressService networkAddressService;
+
+    private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Set.of(FLOUREON_THERMOSTAT_THING_TYPE,
+            UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE);
+    private static final int DISCOVERY_TIMEOUT_SECONDS = 30;
+    private @Nullable ScheduledFuture<?> backgroundDiscoveryFuture;
+
+    @Activate
+    public BroadlinkThermostatDiscoveryService(@Reference NetworkAddressService networkAddressService) {
+        super(DISCOVERABLE_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS);
+        this.networkAddressService = networkAddressService;
+    }
+
+    private void createScanner() {
+
+        long timestampOfLastScan = getTimestampOfLastScan();
+        BLDevice[] blDevices = new BLDevice[0];
+        try {
+            @Nullable
+            InetAddress sourceAddress = getIpAddress();
+            if (sourceAddress != null) {
+                logger.debug("Using source address {} for sending out broadcast request.", sourceAddress);
+                blDevices = BLDevice.discoverDevices(sourceAddress, 0, DISCOVERY_TIMEOUT_SECONDS * 1000);
+            } else {
+                blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
+            }
+        } catch (IOException e) {
+            logger.debug("Error while trying to discover broadlinkthermostat devices: {}", e.getMessage());
+        }
+        logger.debug("Discovery service found {} broadlinkthermostat devices.", blDevices.length);
+
+        for (BLDevice dev : blDevices) {
+            logger.debug("Broadlinkthermostat device {} of type {} with Host {} and MAC {}", dev.getDeviceDescription(),
+                    Integer.toHexString(dev.getDeviceType()), dev.getHost(), dev.getMac());
+
+            ThingUID thingUID;
+            String id = dev.getHost().replaceAll("\\.", "-");
+            logger.debug("Device ID with IP address replacement: {}", id);
+            try {
+                id = getHostnameWithoutDomain(InetAddress.getByName(dev.getHost()).getHostName());
+                logger.debug("Device ID with DNS name: {}", id);
+            } catch (UnknownHostException e) {
+                logger.debug("Discovered device with IP {} does not have a DNS name, using IP as thing UID.",
+                        dev.getHost());
+            }
+
+            switch (dev.getDeviceDescription()) {
+                case "Floureon Thermostat":
+                    thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
+                    break;
+                case "Hysen Thermostat":
+                    thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
+                    break;
+                default:
+                    thingUID = new ThingUID(UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE, id);
+            }
+
+            Map<String, Object> properties = new HashMap<>();
+            properties.put(BroadlinkThermostatBindingConstants.HOST, dev.getHost());
+            properties.put(Thing.PROPERTY_MAC_ADDRESS, dev.getMac().getMacString());
+            properties.put(BroadlinkThermostatBindingConstants.DESCRIPTION, dev.getDeviceDescription());
+
+            logger.debug("Property map: {}", properties);
+
+            DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+                    .withLabel(dev.getDeviceDescription() + " (" + id + ")")
+                    .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
+
+            thingDiscovered(discoveryResult);
+        }
+        removeOlderResults(timestampOfLastScan);
+    }
+
+    @Override
+    protected void startScan() {
+        scheduler.execute(this::createScanner);
+    }
+
+    @Override
+    protected void startBackgroundDiscovery() {
+        logger.trace("Starting background scan for Broadlinkthermostat devices");
+        ScheduledFuture<?> currentBackgroundDiscoveryFuture = backgroundDiscoveryFuture;
+        if (currentBackgroundDiscoveryFuture != null) {
+            currentBackgroundDiscoveryFuture.cancel(true);
+        }
+        backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
+    }
+
+    @Override
+    protected void stopBackgroundDiscovery() {
+        logger.trace("Stopping background scan for Broadlinkthermostat devices");
+        @Nullable
+        ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
+        if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
+            if (backgroundDiscoveryFuture.cancel(true)) {
+                this.backgroundDiscoveryFuture = null;
+            }
+        }
+        stopScan();
+    }
+
+    private @Nullable InetAddress getIpAddress() {
+        return getIpFromNetworkAddressService().orElse(null);
+    }
+
+    /**
+     * Uses openHAB's NetworkAddressService to determine the local primary network interface.
+     *
+     * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
+     */
+    private Optional<InetAddress> getIpFromNetworkAddressService() {
+        String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
+        if (ipAddress == null) {
+            logger.warn("No network interface could be found.");
+            return Optional.empty();
+        }
+        try {
+            return Optional.of(InetAddress.getByName(ipAddress));
+        } catch (UnknownHostException e) {
+            logger.warn("Configured primary IP cannot be parsed: {} Details: {}", ipAddress, e.getMessage());
+            return Optional.empty();
+        }
+    }
+
+    private String getHostnameWithoutDomain(String hostname) {
+        String broadlinkthermostatRegex = "BroadLink-OEM[-A-Za-z0-9]{12}.*";
+        if (hostname.matches(broadlinkthermostatRegex)) {
+            String[] dotSeparatedString = hostname.split("\\.");
+            logger.debug("Found original broadlink DNS name {}, removing domain", hostname);
+            return dotSeparatedString[0].replaceAll("\\.", "-");
+        } else {
+            logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
+                    hostname);
+            return hostname.replaceAll("\\.", "-");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/BroadlinkThermostatHandler.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/BroadlinkThermostatHandler.java
new file mode 100755 (executable)
index 0000000..4890f1e
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal.handler;
+
+import java.io.IOException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatConfig;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.mob41.blapi.BLDevice;
+
+/**
+ * The {@link BroadlinkThermostatHandler} is the device handler class for a broadlinkthermostat device.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+@NonNullByDefault
+public abstract class BroadlinkThermostatHandler extends BaseThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(BroadlinkThermostatHandler.class);
+
+    @Nullable
+    BLDevice blDevice;
+    private @Nullable ScheduledFuture<?> scanJob;
+    @Nullable
+    String host;
+    @Nullable
+    String macAddress;
+
+    /**
+     * Creates a new instance of this class for the {@link Thing}.
+     *
+     * @param thing the thing that should be handled, not null
+     */
+    BroadlinkThermostatHandler(Thing thing) {
+        super(thing);
+    }
+
+    void authenticate(boolean reauth) {
+        logger.debug("Authenticating with broadlinkthermostat device {}...", thing.getLabel());
+        try {
+            BLDevice blDevice = this.blDevice;
+            if (blDevice != null && blDevice.auth(reauth)) {
+                updateStatus(ThingStatus.ONLINE);
+            }
+        } catch (IOException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                    "Error while authenticating broadlinkthermostat device " + thing.getLabel() + ":" + e.getMessage());
+        }
+    }
+
+    @Override
+    public void initialize() {
+        BroadlinkThermostatConfig config = getConfigAs(BroadlinkThermostatConfig.class);
+        host = config.getHost();
+        macAddress = config.getMacAddress();
+
+        // schedule a new scan every minute
+        scanJob = scheduler.scheduleWithFixedDelay(this::refreshData, 0, 1, TimeUnit.MINUTES);
+    }
+
+    protected abstract void refreshData();
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> currentScanJob = scanJob;
+        if (currentScanJob != null) {
+            currentScanJob.cancel(true);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/FloureonThermostatHandler.java b/bundles/org.openhab.binding.broadlinkthermostat/src/main/java/org/openhab/binding/broadlinkthermostat/internal/handler/FloureonThermostatHandler.java
new file mode 100755 (executable)
index 0000000..7f61599
--- /dev/null
@@ -0,0 +1,279 @@
+/**
+ * Copyright (c) 2010-2021 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.broadlinkthermostat.internal.handler;
+
+import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants.*;
+
+import java.io.IOException;
+import java.time.LocalTime;
+import java.time.ZonedDateTime;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.cache.ExpiringCache;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+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.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.mob41.blapi.FloureonDevice;
+import com.github.mob41.blapi.dev.hysen.AdvancedStatusInfo;
+import com.github.mob41.blapi.dev.hysen.BaseStatusInfo;
+import com.github.mob41.blapi.dev.hysen.SensorControl;
+import com.github.mob41.blapi.mac.Mac;
+import com.github.mob41.blapi.pkt.cmd.hysen.SetTimeCommand;
+
+/**
+ * The {@link FloureonThermostatHandler} is responsible for handling thermostats labeled as Floureon Thermostat.
+ *
+ * @author Florian Mueller - Initial contribution
+ */
+@NonNullByDefault
+public class FloureonThermostatHandler extends BroadlinkThermostatHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(FloureonThermostatHandler.class);
+    private @Nullable FloureonDevice floureonDevice;
+
+    private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toSeconds(3);
+    private final ExpiringCache<AdvancedStatusInfo> advancedStatusInfoExpiringCache = new ExpiringCache<>(CACHE_EXPIRY,
+            this::refreshAdvancedStatus);
+
+    /**
+     * Creates a new instance of this class for the {@link FloureonThermostatHandler}.
+     *
+     * @param thing the thing that should be handled, not null
+     */
+    public FloureonThermostatHandler(Thing thing) {
+        super(thing);
+    }
+
+    /**
+     * Initializes a new instance of a {@link FloureonThermostatHandler}.
+     */
+    @Override
+    public void initialize() {
+        super.initialize();
+        if (host != null && macAddress != null) {
+            try {
+                blDevice = new FloureonDevice(host, new Mac(macAddress));
+                this.floureonDevice = (FloureonDevice) blDevice;
+                updateStatus(ThingStatus.ONLINE);
+            } catch (IOException e) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "Could not find broadlinkthermostat device at host" + host + "with MAC+" + macAddress + ": "
+                                + e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("Command: {}", command.toFullString());
+        authenticate(false);
+
+        if (command == RefreshType.REFRESH) {
+            refreshData();
+            return;
+        }
+
+        switch (channelUID.getIdWithoutGroup()) {
+            case SETPOINT:
+                handleSetpointCommand(channelUID, command);
+                break;
+            case POWER:
+                handlePowerCommand(channelUID, command);
+                break;
+            case MODE:
+                handleModeCommand(channelUID, command);
+                break;
+            case SENSOR:
+                handleSensorCommand(channelUID, command);
+                break;
+            case REMOTE_LOCK:
+                handleRemoteLockCommand(channelUID, command);
+                break;
+            case TIME:
+                handleSetTimeCommand(channelUID, command);
+                break;
+            default:
+                logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handlePowerCommand(ChannelUID channelUID, Command command) {
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (command instanceof OnOffType && floureonDevice != null) {
+            try {
+                floureonDevice.setPower(command == OnOffType.ON);
+            } catch (Exception e) {
+                logger.warn("Error while setting power of {} to {}: {}", thing.getUID(), command, e.getMessage());
+            }
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handleModeCommand(ChannelUID channelUID, Command command) {
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (command instanceof StringType && floureonDevice != null) {
+            try {
+                if (MODE_AUTO.equals(command.toFullString())) {
+                    floureonDevice.switchToAuto();
+                } else {
+                    floureonDevice.switchToManual();
+                }
+            } catch (Exception e) {
+                logger.warn("Error while setting power off {} to {}: {}", thing.getUID(), command, e.getMessage());
+            }
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handleSetpointCommand(ChannelUID channelUID, Command command) {
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (command instanceof QuantityType && floureonDevice != null) {
+            try {
+                QuantityType<?> temperatureQuantityType = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
+                if (temperatureQuantityType != null) {
+                    floureonDevice.setThermostatTemp(temperatureQuantityType.doubleValue());
+                } else {
+                    logger.warn("Could not convert {} to °C", command);
+                }
+            } catch (Exception e) {
+                logger.warn("Error while setting setpoint of {} to {}: {}", thing.getUID(), command, e.getMessage());
+            }
+
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handleSensorCommand(ChannelUID channelUID, Command command) {
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (command instanceof StringType && floureonDevice != null) {
+            try {
+                BaseStatusInfo statusInfo = floureonDevice.getBasicStatus();
+                if (SENSOR_INTERNAL.equals(command.toFullString())) {
+                    floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.INTERNAL);
+                } else if (SENSOR_EXTERNAL.equals(command.toFullString())) {
+                    floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.EXTERNAL);
+                } else {
+                    floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(),
+                            SensorControl.INTERNAL_TEMP_EXTERNAL_LIMIT);
+                }
+            } catch (Exception e) {
+                logger.warn("Error while trying to set sensor mode {}: {}", command, e.getMessage());
+            }
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handleRemoteLockCommand(ChannelUID channelUID, Command command) {
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (command instanceof OnOffType && floureonDevice != null) {
+            try {
+                floureonDevice.setLock(command == OnOffType.ON);
+            } catch (Exception e) {
+                logger.warn("Error while setting remote lock of {} to {}: {}", thing.getUID(), command, e.getMessage());
+            }
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    private void handleSetTimeCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof DateTimeType) {
+            ZonedDateTime zonedDateTime = ((DateTimeType) command).getZonedDateTime();
+            try {
+                new SetTimeCommand(tob(zonedDateTime.getHour()), tob(zonedDateTime.getMinute()),
+                        tob(zonedDateTime.getSecond()), tob(zonedDateTime.getDayOfWeek().getValue()))
+                                .execute(floureonDevice);
+            } catch (Exception e) {
+                logger.warn("Error while setting time of {} to {}: {}", thing.getUID(), command, e.getMessage());
+            }
+        } else {
+            logger.warn("Channel {} does not support command {}", channelUID, command);
+        }
+    }
+
+    @Nullable
+    private AdvancedStatusInfo refreshAdvancedStatus() {
+        if (ThingStatus.ONLINE != thing.getStatus()) {
+            return null;
+        }
+
+        FloureonDevice floureonDevice = this.floureonDevice;
+        if (floureonDevice != null) {
+            try {
+                AdvancedStatusInfo advancedStatusInfo = floureonDevice.getAdvancedStatus();
+                if (advancedStatusInfo == null) {
+                    logger.warn("Device {} did not return any data. Trying to reauthenticate...", thing.getUID());
+                    authenticate(true);
+                    advancedStatusInfo = floureonDevice.getAdvancedStatus();
+                }
+                if (advancedStatusInfo == null) {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device not responding.");
+                    return null;
+                }
+                return advancedStatusInfo;
+            } catch (Exception e) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "Error while retrieving data for " + thing.getUID() + ":  " + e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void refreshData() {
+
+        AdvancedStatusInfo advancedStatusInfo = advancedStatusInfoExpiringCache.getValue();
+        if (advancedStatusInfo == null) {
+            return;
+        }
+        logger.trace("Retrieved data from device {}: {}", thing.getUID(), advancedStatusInfo);
+        updateState(ROOM_TEMPERATURE, new QuantityType<>(advancedStatusInfo.getRoomTemp(), SIUnits.CELSIUS));
+        updateState(ROOM_TEMPERATURE_EXTERNAL_SENSOR,
+                new QuantityType<>(advancedStatusInfo.getExternalTemp(), SIUnits.CELSIUS));
+        updateState(SETPOINT, new QuantityType<>(advancedStatusInfo.getThermostatTemp(), SIUnits.CELSIUS));
+        updateState(POWER, OnOffType.from(advancedStatusInfo.getPower()));
+        updateState(MODE, StringType.valueOf(advancedStatusInfo.getAutoMode() ? "auto" : "manual"));
+        updateState(SENSOR, StringType.valueOf(advancedStatusInfo.getSensorControl().name()));
+        updateState(TEMPERATURE_OFFSET, new QuantityType<>(advancedStatusInfo.getDif(), SIUnits.CELSIUS));
+        updateState(ACTIVE, OnOffType.from(advancedStatusInfo.getActive()));
+        updateState(REMOTE_LOCK, OnOffType.from(advancedStatusInfo.getRemoteLock()));
+        updateState(TIME, new DateTimeType(getTimestamp(advancedStatusInfo)));
+    }
+
+    private ZonedDateTime getTimestamp(AdvancedStatusInfo advancedStatusInfo) {
+        ZonedDateTime now = ZonedDateTime.now();
+        return now.with(
+                LocalTime.of(advancedStatusInfo.getHour(), advancedStatusInfo.getMin(), advancedStatusInfo.getSec()));
+    }
+
+    private static byte tob(int in) {
+        return (byte) (in & 0xff);
+    }
+}
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..1b07d3e
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="broadlinkthermostat" 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>Broadlinkthermostat Binding</name>
+       <description>This is the binding for Broadlinkthermostat devices.</description>
+</binding:binding>
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/config/config.xml
new file mode 100644 (file)
index 0000000..ae22ca0
--- /dev/null
@@ -0,0 +1,20 @@
+<?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="thing-type:broadlinkthermostat:floureonandhysenthermostat">
+               <parameter name="host" type="text" required="true">
+                       <label>Hostname</label>
+                       <description>The hostname/IP address the device is bound to, e.g. 192.168.0.2</description>
+                       <context>network-address</context>
+               </parameter>
+               <parameter name="macAddress" type="text" required="true">
+                       <label>MAC Address</label>
+                       <description>The unique MAC address of the device, e.g. 00:10:FA:6E:38:4A</description>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.broadlinkthermostat/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..b5ede01
--- /dev/null
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="broadlinkthermostat"
+       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">
+
+       <!-- Floureon Thermostat Thing Type -->
+       <thing-type id="floureonthermostat">
+               <label>Floureon Thermostat</label>
+               <description>A heating device thermostat</description>
+
+               <channels>
+                       <channel id="power" typeId="power"/>
+                       <channel id="mode" typeId="mode"/>
+                       <channel id="sensor" typeId="sensor"/>
+                       <channel id="roomtemperature" typeId="roomtemperature"/>
+                       <channel id="roomtemperatureexternalsensor" typeId="roomtemperatureexternalsensor"/>
+                       <channel id="active" typeId="active"/>
+                       <channel id="setpoint" typeId="setpoint"/>
+                       <channel id="temperatureoffset" typeId="temperatureoffset"/>
+                       <channel id="remotelock" typeId="remotelock"/>
+                       <channel id="time" typeId="time"/>
+               </channels>
+
+               <representation-property>host</representation-property>
+
+               <config-description-ref uri="thing-type:broadlinkthermostat:floureonandhysenthermostat"/>
+       </thing-type>
+       <thing-type id="hysenthermostat">
+               <label>Hysen Thermostat</label>
+               <description>A heating device thermostat</description>
+
+               <channels>
+                       <channel id="power" typeId="power"/>
+                       <channel id="mode" typeId="mode"/>
+                       <channel id="sensor" typeId="sensor"/>
+                       <channel id="roomtemperature" typeId="roomtemperature"/>
+                       <channel id="roomtemperatureexternalsensor" typeId="roomtemperatureexternalsensor"/>
+                       <channel id="active" typeId="active"/>
+                       <channel id="setpoint" typeId="setpoint"/>
+                       <channel id="temperatureoffset" typeId="temperatureoffset"/>
+                       <channel id="remotelock" typeId="remotelock"/>
+                       <channel id="time" typeId="time"/>
+               </channels>
+
+               <representation-property>host</representation-property>
+
+               <config-description-ref uri="thing-type:broadlinkthermostat:floureonandhysenthermostat"/>
+       </thing-type>
+
+       <channel-type id="power">
+               <item-type>Switch</item-type>
+               <label>Power</label>
+               <description>Switch display on/off and enable/disables heating</description>
+               <category>Switch</category>
+       </channel-type>
+       <channel-type id="mode">
+               <item-type>String</item-type>
+               <label>Mode</label>
+               <description>Current mode of the thermostat</description>
+               <state>
+                       <options>
+                               <option value="auto">auto</option>
+                               <option value="manual">manual</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="sensor">
+               <item-type>String</item-type>
+               <label>Sensor</label>
+               <description>The sensor (internal/external) used for triggering the thermostat</description>
+               <category>Sensor</category>
+               <state>
+                       <options>
+                               <option value="internal">internal</option>
+                               <option value="external">external</option>
+                               <option value="internal_temp_external_limit">internal control temperature; external limit temperature</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="active">
+               <item-type>Switch</item-type>
+               <label>Active</label>
+               <description>Shows if thermostat is currently actively heating</description>
+               <category>Switch</category>
+               <state readOnly="true"/>
+       </channel-type>
+       <channel-type id="roomtemperature">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature</label>
+               <description>Room temperature, measured directly at the device</description>
+               <category>Temperature</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="roomtemperatureexternalsensor">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature Ext. Sensor</label>
+               <description>Room temperature, measured by the external sensor</description>
+               <category>Temperature</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="setpoint">
+               <item-type>Number:Temperature</item-type>
+               <label>Setpoint</label>
+               <description>Temperature setpoint that open/close valve</description>
+               <category>Temperature</category>
+               <state pattern="%.1f %unit%" step="0.5"/>
+       </channel-type>
+       <channel-type id="temperatureoffset">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature Offset</label>
+               <description>Manual temperature adjustment</description>
+               <category>Temperature</category>
+               <state pattern="%.1f %unit%" step="0.5" min="-2.5" max="2.5"/>
+       </channel-type>
+       <channel-type id="temperature">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature</label>
+               <description>Temperature</description>
+               <category>Temperature</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="remotelock">
+               <item-type>Switch</item-type>
+               <label>Remote Lock</label>
+               <description>Locks the device to only allow remote actions</description>
+               <category>Lock</category>
+       </channel-type>
+       <channel-type id="time">
+               <item-type>DateTime</item-type>
+               <label>Time</label>
+               <description>The time and day of week</description>
+               <category>Time</category>
+       </channel-type>
+
+</thing:thing-descriptions>
index 9722fb7f97cc80b2a732a95b7a7ad95132a46ceb..eda37e90ca5cbf207e979aacede1bb7400233540 100644 (file)
@@ -69,6 +69,7 @@
     <module>org.openhab.binding.boschindego</module>
     <module>org.openhab.binding.boschshc</module>
     <module>org.openhab.binding.bosesoundtouch</module>
+    <module>org.openhab.binding.broadlinkthermostat</module>
     <module>org.openhab.binding.bsblan</module>
     <module>org.openhab.binding.bticinosmarther</module>
     <module>org.openhab.binding.buienradar</module>