added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.bigassfan</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,182 @@
# BigAssFan Binding
The [BigAssFan](https://www.bigassfans.com/) binding is used to enable communication between openHAB and Big Ass Fans' Haiku family of residential fans and lights that implement the SenseME technology.
## Overview
Fans, lights and controllers are discovered dynamically.
There is a single thing created for each fan, light, or controller that's connected to the local WiFi network.
Each thing has channels that allow control of the fan and light, as well as to monitor the status.
When a fan or light is controlled from the remote control, Wall Controller, or smartphone app, the openHAB items linked to the device's channels will be updated to reflect the status.
## Supported Things
The binding currently supports the following devices.
| Thing | ID | |
|-------------|-------------|--------------------|
| Fan | fan | BigAssFan consisting of fan and integrated LED light |
| Light | light | Standalone LED light |
| Controller | controller | Fan wall controller |
## Thing Configuration
| Parameter | Parameter ID | Required/Optional | Description |
|--------------|---------------|-------------------|-------------|
| Label | label | Required | Label given to device in the Haiku Smartphone app. |
| IP Address | ipAddress | Required | IP address of the device. |
| MAC Address | macAddress | Required | MAC address of the device. |
In the event that any of this information is changed on the device, the thing configuration must be updated manually, as it cannot be determined automatically by the binding.
### Manual Thing Creation
Fans and lights can be manually created in the *Paper UI* or *HABmin*, or by placing a *.things* file in the *conf/things* directory.
See example below.
## Device Discovery
The BigAssFan binding discovers Haiku fans and lights on the network, and creates an inbox entry for each discovered device.
Once added as a thing, the user can control the fan and light, similarly to how the device is controlled using the remote, Wall Controller, or smartphone app.
Background discovery polls the network every few minutes for devices.
Background discovery is **enabled** by default.
To **disable** background discovery, add the following line to the *conf/services/runtime.cfg* file:
```text
discovery.bigassfan:background=false
```
## Channels
The following channels are supported for fans:
| Channel Name | Item Type | Description |
|-------------------------|--------------|-------------------------------------------------------|
| fan-power | Switch | Power on/off the fan |
| fan-speed | Dimmer | Adjust the speed of the fan |
| fan-direction | String | Indicates the direction in which the fan is turning |
| fan-auto | Switch | Enable/disable fan auto mode |
| fan-whoosh | Switch | Enable/disable fan "whoosh" mode |
| fan-sleep | Switch | Enable/disable fan sleep mode |
| fan-smartmode | String | Set Smartmode to HEATING, COOLING, or OFF |
| fan-learn-minspeed | Dimmer | Set minimum fan speed for Smartmode COOLING |
| fan-learn-maxspeed | Dimmer | Set maximum fan speed for Smartmode COOLING |
| fan-wintermode | Switch | Enable/disable fan winter mode |
| fan-speed-min | Dimmer | Set minimum fan speed |
| fan-speed-max | Dimmer | Set maximum fan speed |
| light-power | Switch | Power on/off the light |
| light-level | Dimmer | Adjust the brightness of the light |
| light-auto | Switch | Enable/disable light auto mode |
| light-smarter | String | Enable/disable Smarter Lighting |
| light-level-min | Dimmer | Set minimum light level for Smarter Lighting |
| light-level-max | Dimmer | Set maximum light level for Smarter Lighting |
| light-present | String | Indicates is a light is installed in the fan |
| motion | Switch | Motion was detected |
| time | DateTime | Fan's date and time |
The following channels are supported for lights:
| Channel Name | Item Type | Description |
|-------------------------|--------------|-------------------------------------------------------|
| light-power | Switch | Power on/off the light |
| light-level | Dimmer | Adjust the brightness of the light |
| light-hue | Dimmer | Adjust the color temperature of the light |
| light-present | String | Indicates if a light is installed |
| light-color | String | Indicates if the light supports hue adjustment |
| motion | Switch | Motion was detected |
| time | DateTime | Light's date and time |
The following channels are supported for wall controllers:
| Channel Name | Item Type | Description |
|-------------------------|--------------|-------------------------------------------------------|
| motion | Switch | Motion was detected |
| time | DateTime | Wall controllers date and time |
## Fan Items
The following item definitions would be used to control the fan.
```java
Switch PorchFanPower { channel="bigassfan:fan:20F85EDAA56A:fan-power" }
Dimmer PorchFanSpeed { channel="bigassfan:fan:20F85EDAA56A:fan-speed" }
Switch PorchFanAuto { channel="bigassfan:fan:20F85EDAA56A:fan-auto" }
Switch PorchFanWhoosh { channel="bigassfan:fan:20F85EDAA56A:fan-whoosh" }
Switch PorchFanSleep { channel="bigassfan:fan:20F85EDAA56A:fan-sleep" }
String PorchFanSmartmode { channel="bigassfan:fan:20F85EDAA56A:fan-smartmode" }
Dimmer PorchFanSpeedMin { channel="bigassfan:fan:20F85EDAA56A:fan-learn-minspeed" }
Dimmer PorchFanSpeedMax { channel="bigassfan:fan:20F85EDAA56A:fan-learn-maxspeed" }
```
The following item definitions would be used to control the light.
```java
Switch PorchFanLightPower { channel="bigassfan:fan:20F85EDAA56A:light-power" }
Dimmer PorchFanLightLevel { channel="bigassfan:fan:20F85EDAA56A:light-level" }
Switch PorchFanLightAuto { channel="bigassfan:fan:20F85EDAA56A:light-auto" }
Switch PorchFanLightSmarter { channel="bigassfan:fan:20F85EDAA56A:light-smarter" }
Dimmer PorchFanLightLevelMin { channel="bigassfan:fan:20F85EDAA56A:light-level-min" }
Dimmer PorchFanLightLevelMax { channel="bigassfan:fan:20F85EDAA56A:light-level-max" }
```
The following read-only items are provided by the fan.
```java
String PorchFanLightPresent { channel="bigassfan:fan:20F85EDAA56A:light-present" }
Switch PorchFanMotionSensor { channel="bigassfan:fan:20F85EDAA56A:motion" }
DateTime PorchFanTime { channel="bigassfan:fan:20F85EDAA56A:time" }
```
## Light Items
```java
Switch KitchenLightPower { channel="bigassfan:light:20F85EDA87A0:light-power" }
Dimmer KitchenLightLevel { channel="bigassfan:light:20F85EDA87A0:light-level" }
Switch KitchenLightHue { channel="bigassfan:light:20F85EDA87A0:light-hue" }
```
The following read-only items are provided by the light.
```java
String KitchenLightPresent { channel="bigassfan:light:20F85EDA87A0:light-present" }
String KitchenLightColor { channel="bigassfan:light:20F85EDA87A0:light-color" }
Switch KitchenLightMotionSensor { channel="bigassfan:light:20F85EDA87A0:motion" }
DateTime KitchenLightTime { channel="bigassfan:light:20F85EDA87A0:time" }
```
## Wall Controller Items
The following read-only items are provided by the wall controller.
```java
Switch PorchControllerMotionSensor { channel="bigassfan:controller:20F85ED87F01:motion" }
DateTime PorchControllerTime { channel="bigassfan:controller:20F85ED87F01:time" }
```
### Sitemap
This is an example of how to set up your sitemap.
```perl
Frame label="Control My BigAssFan" {
Switch item=PorchFanPower label="Fan Power [%s]"
Slider item=PorchFanSpeed label="Fan Speed [%s %%]"
Switch item=PorchFanLightPower label="Light Power [%s]"
Slider item=PorchFanLightLevel label="Light Brightness [%s %%]"
}
Frame label="Control My Light" {
Switch item=KitchenLightPower label="Light Power [%s]"
Slider item=KitchenLightLevel label="Light Level [%s %%]"
Slider item=KitchenLightHue label="Light Hue [%s]"
}
```
### Manual Thing Creation
Place a file named *bigassfan.things* in the *conf/things* directory.
The file should contain lines formatted like this.
```java
bigassfan:fan:20F85EDAA56A [ label="Porch Fan", ipAddress="192.168.12.62", macAddress="20:F8:5E:DA:A5:6A" ]
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.bigassfan</artifactId>
<name>openHAB Add-ons :: Bundles :: BigAssFan Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.bigassfan-${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-bigassfan" description="Big Ass Fan Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bigassfan/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,92 @@
/**
* 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.bigassfan.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 BigAssFanBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class BigAssFanBindingConstants {
public static final String BINDING_ID = "bigassfan";
// Fans communicate on this port using both UDP (discovery) and TCP (commands)
public static final int BAF_PORT = 31415;
// Commands sent to/from fan are ASCII
public static final String CHARSET = "US-ASCII";
// BigAssFan Thing Type UIDs
public static final ThingTypeUID THING_TYPE_FAN = new ThingTypeUID(BINDING_ID, "fan");
public static final ThingTypeUID THING_TYPE_LIGHT = new ThingTypeUID(BINDING_ID, "light");
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_FAN, THING_TYPE_LIGHT, THING_TYPE_CONTROLLER).collect(Collectors.toSet()));
/*
* List of Channel Ids
*/
// Fan control channels
public static final String CHANNEL_FAN_POWER = "fan-power";
public static final String CHANNEL_FAN_SPEED = "fan-speed";
public static final String CHANNEL_FAN_DIRECTION = "fan-direction";
public static final String CHANNEL_FAN_AUTO = "fan-auto";
public static final String CHANNEL_FAN_WHOOSH = "fan-whoosh";
public static final String CHANNEL_FAN_SMARTMODE = "fan-smartmode";
public static final String CHANNEL_FAN_SPEED_MIN = "fan-speed-min";
public static final String CHANNEL_FAN_SPEED_MAX = "fan-speed-max";
public static final String CHANNEL_FAN_LEARN_MINSPEED = "fan-learn-speed-min";
public static final String CHANNEL_FAN_LEARN_MAXSPEED = "fan-learn-speed-max";
public static final String CHANNEL_FAN_WINTERMODE = "fan-wintermode";
public static final String CHANNEL_FAN_SLEEP = "fan-sleep";
// Light control channels
public static final String CHANNEL_LIGHT_POWER = "light-power";
public static final String CHANNEL_LIGHT_LEVEL = "light-level";
public static final String CHANNEL_LIGHT_AUTO = "light-auto";
public static final String CHANNEL_LIGHT_SMARTER = "light-smarter";
public static final String CHANNEL_LIGHT_LEVEL_MIN = "light-level-min";
public static final String CHANNEL_LIGHT_LEVEL_MAX = "light-level-max";
public static final String CHANNEL_LIGHT_PRESENT = "light-present";
// Standalone light channels
public static final String CHANNEL_LIGHT_HUE = "light-hue";
public static final String CHANNEL_LIGHT_COLOR = "light-color";
// Miscellaneous channels
public static final String CHANNEL_MOTION = "motion";
public static final String CHANNEL_TIME = "time";
/*
* BigAssFan thing configuration parameters
*/
// IP network address of the fan
public static final String THING_PROPERTY_IP = "ipAddress";
// MAC address of the fan
public static final String THING_PROPERTY_MAC = "macAddress";
// Friendly name given to the fan
public static final String THING_PROPERTY_LABEL = "label";
}

View File

@@ -0,0 +1,79 @@
/**
* 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.bigassfan.internal;
import org.apache.commons.lang.StringUtils;
/**
* The {@link BigAssFanConfig} is responsible for storing the BigAssFan thing configuration.
*
* @author Mark Hilbush - Initial contribution
*/
public class BigAssFanConfig {
/**
* Name of the device
*/
private String label;
/**
* IP address of the device
*/
private String ipAddress;
/**
* MAC address of the device
*/
private String macAddress;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getMacAddress() {
return macAddress;
}
public void setMacAddress(String macAddress) {
this.macAddress = macAddress;
}
public boolean isValid() {
if (StringUtils.isBlank(label)) {
return false;
}
if (StringUtils.isBlank(ipAddress)) {
return false;
}
if (StringUtils.isBlank(macAddress)) {
return false;
}
return true;
}
@Override
public String toString() {
return "BigAssFanConfig{label=" + label + ", ipAddress=" + ipAddress + ", macAddress=" + macAddress + "}";
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.bigassfan.internal;
import static org.openhab.binding.bigassfan.internal.BigAssFanBindingConstants.SUPPORTED_THING_TYPES_UIDS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bigassfan.internal.handler.BigAssFanHandler;
import org.openhab.core.net.NetworkAddressService;
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.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link BigAssFanHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bigassfan")
public class BigAssFanHandlerFactory extends BaseThingHandlerFactory {
private final NetworkAddressService networkAddressService;
@Activate
public BigAssFanHandlerFactory(@Reference NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
@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 (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new BigAssFanHandler(thing, networkAddressService.getPrimaryIpv4HostAddress());
}
return null;
}
}

View File

@@ -0,0 +1,125 @@
/**
* 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.bigassfan.internal.discovery;
/**
* The {@link BigAssFanDevice} is responsible for storing information about a fan.
*
* @author Mark Hilbush - Initial contribution
*/
public class BigAssFanDevice {
/**
* Name of device (e.g. Master Bedroom Fan)
*/
private String label;
/**
* IP address of the device extracted from UDP packet
*/
private String ipAddress;
/**
* MAC address of the device extracted from discovery message
*/
private String macAddress;
/**
* Type of device extracted from discovery message (e.g. FAN or SWITCH)
*/
private String type;
/**
* Model of device extracted from discovery message (e.g. HSERIES)
*/
private String model;
/**
* The raw discovery message
*/
private String discoveryMessage;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getMacAddress() {
return macAddress;
}
public void setMacAddress(String macAddress) {
this.macAddress = macAddress;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDiscoveryMessage() {
return discoveryMessage;
}
public void setDiscoveryMessage(String discoveryMessage) {
this.discoveryMessage = discoveryMessage;
}
public boolean isFan() {
return type.toUpperCase().contains("FAN") ? true : false;
}
public boolean isSwitch() {
return type.toUpperCase().contains("SWITCH") ? true : false;
}
public boolean isLight() {
return type.toUpperCase().contains("LIGHT") ? true : false;
}
public void reset() {
label = "";
ipAddress = "";
macAddress = "";
type = "";
model = "";
discoveryMessage = "";
}
@Override
public String toString() {
return "BigAssFanDevice{label=" + label + ", ipAddress=" + ipAddress + ", macAddress=" + macAddress + ", model="
+ model + ", type=" + type + "}";
}
}

View File

@@ -0,0 +1,264 @@
/**
* 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.bigassfan.internal.discovery;
import static org.openhab.binding.bigassfan.internal.BigAssFanBindingConstants.*;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BigAssFanDiscoveryService} class implements a service
* for discovering the Big Ass Fans.
*
* @author Mark Hilbush - Initial contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.bigassfan")
public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(BigAssFanDiscoveryService.class);
private static final boolean BACKGROUND_DISCOVERY_ENABLED = true;
private static final long BACKGROUND_DISCOVERY_DELAY = 8L;
// Our own thread pool for the long-running listener job
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> listenerJob;
DiscoveryListener discoveryListener;
private boolean terminate;
private final Pattern announcementPattern = Pattern.compile("[(](.*);DEVICE;ID;(.*);(.*)[)]");
private Runnable listenerRunnable = () -> {
try {
listen();
} catch (RuntimeException e) {
logger.warn("Discovery listener got unexpected exception: {}", e.getMessage(), e);
}
};
// Frequency (in seconds) with which we poll for new devices
private final long POLL_FREQ = 300L;
private final long POLL_DELAY = 12L;
private ScheduledFuture<?> pollJob;
public BigAssFanDiscoveryService() {
super(SUPPORTED_THING_TYPES_UIDS, 0, BACKGROUND_DISCOVERY_ENABLED);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_THING_TYPES_UIDS;
}
@Override
protected void activate(Map<String, Object> configProperties) {
super.activate(configProperties);
logger.trace("BigAssFan discovery service ACTIVATED");
}
@Override
protected void deactivate() {
super.deactivate();
logger.trace("BigAssFan discovery service DEACTIVATED");
}
@Override
@Modified
protected void modified(Map<String, Object> configProperties) {
super.modified(configProperties);
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting background discovery");
startListenerJob();
schedulePollJob();
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping background discovery");
cancelPollJob();
cancelListenerJob();
}
private void startListenerJob() {
if (listenerJob == null) {
terminate = false;
logger.debug("Starting discovery listener job in {} seconds", BACKGROUND_DISCOVERY_DELAY);
listenerJob = scheduledExecutorService.schedule(listenerRunnable, BACKGROUND_DISCOVERY_DELAY,
TimeUnit.SECONDS);
}
}
private void cancelListenerJob() {
if (listenerJob != null) {
logger.debug("Canceling discovery listener job");
listenerJob.cancel(true);
terminate = true;
listenerJob = null;
}
}
@Override
public void startScan() {
}
@Override
public void stopScan() {
}
private synchronized void listen() {
logger.info("BigAssFan discovery service is running");
try {
discoveryListener = new DiscoveryListener();
} catch (SocketException se) {
logger.warn("Got Socket exception creating multicast socket: {}", se.getMessage(), se);
return;
} catch (IOException ioe) {
logger.warn("Got IO exception creating multicast socket: {}", ioe.getMessage(), ioe);
return;
}
logger.debug("Waiting for discovery messages");
while (!terminate) {
try {
// Wait for a discovery message
processMessage(discoveryListener.waitForMessage());
} catch (SocketTimeoutException e) {
// Read on socket timed out; check for termination
continue;
} catch (IOException ioe) {
logger.warn("Got IO exception waiting for message: {}", ioe.getMessage(), ioe);
break;
}
}
discoveryListener.shutdown();
logger.debug("DiscoveryListener job is exiting");
}
private void processMessage(BigAssFanDevice device) {
if (device == null) {
return;
}
Matcher matcher = announcementPattern.matcher(device.getDiscoveryMessage());
if (matcher.find()) {
logger.debug("Match: grp1={}, grp2={}, grp(3)={}", matcher.group(1), matcher.group(2), matcher.group(3));
// Extract needed information from the discovery message
device.setLabel(matcher.group(1));
device.setMacAddress(matcher.group(2));
String[] modelParts = matcher.group(3).split(",");
switch (modelParts.length) {
case 2:
// L-Series fans
device.setType(modelParts[0]);
device.setModel(modelParts[1]);
deviceDiscovered(device);
break;
case 3:
// H-Series fans
device.setType(modelParts[0]);
device.setModel(modelParts[2]);
deviceDiscovered(device);
break;
default:
logger.info("Unable to extract device type from discovery message");
break;
}
}
}
private synchronized void deviceDiscovered(BigAssFanDevice device) {
logger.debug("Device discovered: {}", device);
ThingTypeUID thingTypeUid;
if (device.isSwitch()) {
logger.debug("Add controller with IP={}, MAC={}, MODEL={}", device.getIpAddress(), device.getMacAddress(),
device.getModel());
thingTypeUid = THING_TYPE_CONTROLLER;
} else if (device.isFan()) {
logger.debug("Add fan with IP={}, MAC={}, MODEL={}", device.getIpAddress(), device.getMacAddress(),
device.getModel());
thingTypeUid = THING_TYPE_FAN;
} else if (device.isLight()) {
logger.debug("Add light with IP={}, MAC={}, MODEL={}", device.getIpAddress(), device.getMacAddress(),
device.getModel());
thingTypeUid = THING_TYPE_LIGHT;
} else {
logger.info("Discovered unknown device type {} at IP={}", device.getModel(), device.getIpAddress());
return;
}
// We got a valid discovery message. Process it as a potential new thing
String serialNumber = device.getMacAddress().replace(":", "");
Map<String, Object> properties = new HashMap<>();
properties.put(THING_PROPERTY_MAC, device.getMacAddress());
properties.put(THING_PROPERTY_IP, device.getIpAddress());
properties.put(THING_PROPERTY_LABEL, device.getLabel());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
properties.put(Thing.PROPERTY_MODEL_ID, device.getModel());
properties.put(Thing.PROPERTY_VENDOR, "Haiku");
ThingUID uid = new ThingUID(thingTypeUid, serialNumber);
logger.debug("Creating discovery result for UID={}, IP={}", uid, device.getIpAddress());
thingDiscovered(
DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(device.getLabel()).build());
}
private void schedulePollJob() {
logger.debug("Scheduling discovery poll job to run every {} seconds starting in {} sec", POLL_FREQ, POLL_DELAY);
cancelPollJob();
pollJob = scheduler.scheduleWithFixedDelay(() -> {
try {
discoveryListener.pollForDevices();
} catch (RuntimeException e) {
logger.warn("Poll job got unexpected exception: {}", e.getMessage(), e);
}
}, POLL_DELAY, POLL_FREQ, TimeUnit.SECONDS);
}
private void cancelPollJob() {
if (pollJob != null) {
logger.debug("Canceling poll job");
pollJob.cancel(true);
pollJob = null;
}
}
}

View File

@@ -0,0 +1,108 @@
/**
* 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.bigassfan.internal.discovery;
import static org.openhab.binding.bigassfan.internal.BigAssFanBindingConstants.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DiscoveryListener} is responsible for listening on the UDP socket for fan discovery messages.
*
* @author Mark Hilbush - Initial contribution
*/
public class DiscoveryListener {
private final Logger logger = LoggerFactory.getLogger(DiscoveryListener.class);
private final String BCAST_ADDRESS = "255.255.255.255";
private final int SOCKET_RECEIVE_TIMEOUT = 500;
private final String POLL_MESSAGE = "<ALL;DEVICE;ID;GET>";
DatagramSocket dSocket;
DatagramPacket rcvPacket;
byte[] rcvBuffer;
InetAddress bcastAddress;
byte[] bcastBuffer;
DatagramPacket bcastPacket;
BigAssFanDevice device;
public DiscoveryListener() throws IOException, SocketException {
logger.debug("DiscoveryListener opening UDP broadcast socket");
dSocket = null;
device = new BigAssFanDevice();
try {
// Create a socket on the UDP port and get send & receive buffers
dSocket = new DatagramSocket(BAF_PORT);
dSocket.setSoTimeout(SOCKET_RECEIVE_TIMEOUT);
dSocket.setBroadcast(true);
rcvBuffer = new byte[256];
rcvPacket = new DatagramPacket(rcvBuffer, rcvBuffer.length);
bcastAddress = InetAddress.getByName(BCAST_ADDRESS);
bcastBuffer = POLL_MESSAGE.getBytes(CHARSET);
bcastPacket = new DatagramPacket(bcastBuffer, bcastBuffer.length, bcastAddress, BAF_PORT);
} catch (UnknownHostException uhe) {
logger.warn("UnknownHostException sending poll request for fans: {}", uhe.getMessage(), uhe);
} catch (UnsupportedEncodingException e) {
logger.warn("Unable to convert buffer to string using {} charset", CHARSET, e);
}
}
public BigAssFanDevice waitForMessage() throws IOException, SocketTimeoutException {
// Wait to receive a packet
rcvPacket.setLength(rcvBuffer.length);
dSocket.receive(rcvPacket);
// Process the received packet
device.reset();
device.setIpAddress(rcvPacket.getAddress().getHostAddress());
String message = (new String(rcvBuffer, 0, rcvPacket.getLength()));
device.setDiscoveryMessage(message);
logger.debug("RECEIVED packet of length {} from {}: {}", message.length(), device.getIpAddress(), message);
return device;
}
public void pollForDevices() {
if (dSocket == null) {
logger.debug("Socket is null in discoveryListener.pollForDevices()");
return;
}
logger.debug("Sending poll request for fans: {}", POLL_MESSAGE);
try {
dSocket.send(bcastPacket);
} catch (IOException ioe) {
logger.warn("IOException sending poll request for fans: {}", ioe.getMessage(), ioe);
}
}
public void shutdown() {
logger.debug("DiscoveryListener closing socket");
if (dSocket != null) {
dSocket.close();
dSocket = null;
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.bigassfan.internal.utils;
import org.openhab.core.library.types.PercentType;
/**
* The {@link BigAssFanConverter} is responsible for converting between
* Dimmer values and values used for fan speed, light brightness, and
* light color temperature.
*
* @author Mark Hilbush - Initial contribution
*/
public class BigAssFanConverter {
/*
* Conversion factor for fan range (0-7) to dimmer range (0-100).
*/
private static final double SPEED_CONVERSION_FACTOR = 14.2857;
/*
* Conversion factor for light range (0-16) to dimmer range (0-100).
*/
private static final double BRIGHTNESS_CONVERSION_FACTOR = 6.25;
/*
* Conversion factor for hue range (2200-5000) to dimmer range (0-100).
*/
private static final double HUE_CONVERSION_FACTOR = 28.0;
/*
* Dimmer item will produce PercentType value, which is 0-100
* Convert that value to what the fan expects, which is 0-7
*/
public static String percentToSpeed(PercentType command) {
return String.valueOf((int) Math.round(command.doubleValue() / SPEED_CONVERSION_FACTOR));
}
/*
* Fan will supply fan speed value in range of 0-7
* Convert that value to a PercentType in range 0-100, which is what Dimmer item expects
*/
public static PercentType speedToPercent(String speed) {
return new PercentType((int) Math.round(Integer.parseInt(speed) * SPEED_CONVERSION_FACTOR));
}
/*
* Dimmer item will produce PercentType value, which is 0-100
* Convert that value to what the light expects, which is 0-16
*/
public static String percentToLevel(PercentType command) {
return String.valueOf((int) Math.round(command.doubleValue() / BRIGHTNESS_CONVERSION_FACTOR));
}
/*
* Light will supply brightness value in range of 0-16
* Convert that value to a PercentType in range 0-100, which is what Dimmer item expects
*/
public static PercentType levelToPercent(String level) {
return new PercentType((int) Math.round(Integer.parseInt(level) * BRIGHTNESS_CONVERSION_FACTOR));
}
/*
* Dimmer item will produce PercentType value, which is 0-100
* Convert that value to what the light expects, which is 2200-5000
*/
public static String percentToHue(PercentType command) {
return String.valueOf(2200 + (int) Math.round(command.doubleValue() * HUE_CONVERSION_FACTOR));
}
/*
* Light will supply hue value in range of 2200-5000
* Convert that value to a PercentType in range 0-100, which is what Dimmer item expects
*/
public static PercentType hueToPercent(String hue) {
return new PercentType((int) Math.round((Integer.parseInt(hue) - 2200) / HUE_CONVERSION_FACTOR));
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="bigassfan" 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>BigAssFan Binding</name>
<description>This is the binding for BigAssFan.</description>
<author>Mark Hilbush</author>
</binding:binding>

View File

@@ -0,0 +1,23 @@
<?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:bigassfan:device">
<parameter name="label" type="text" required="true">
<label>Name of Device</label>
<description>Enter the name you've given to the device</description>
</parameter>
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>Enter the IP address of the device</description>
<context>network-address</context>
</parameter>
<parameter name="macAddress" type="text" required="true">
<label>MAC Address</label>
<description>Enter the MAC address of the device</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bigassfan"
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">
<!-- Thing Type for BigAssFan fan -->
<thing-type id="fan">
<label>BigAssFan</label>
<description>Big Ass Fan</description>
<channels>
<!-- Channels related to fan functions -->
<channel id="fan-power" typeId="fan-power"/>
<channel id="fan-speed" typeId="fan-speed"/>
<channel id="fan-direction" typeId="fan-direction"></channel>
<channel id="fan-auto" typeId="fan-auto"/>
<channel id="fan-whoosh" typeId="fan-whoosh"/>
<channel id="fan-smartmode" typeId="fan-smartmode"/>
<channel id="fan-learn-speed-min" typeId="fan-learn-speed-min"/>
<channel id="fan-learn-speed-max" typeId="fan-learn-speed-max"/>
<channel id="fan-speed-min" typeId="fan-speed-min"/>
<channel id="fan-speed-max" typeId="fan-speed-max"/>
<channel id="fan-wintermode" typeId="fan-wintermode"/>
<channel id="fan-sleep" typeId="fan-sleep"/>
<!-- Channels related to light functions -->
<channel id="light-power" typeId="light-power"/>
<channel id="light-level" typeId="light-level"/>
<channel id="light-auto" typeId="light-auto"/>
<channel id="light-smarter" typeId="light-smarter"/>
<channel id="light-level-min" typeId="light-level-min"/>
<channel id="light-level-max" typeId="light-level-max"/>
<channel id="light-present" typeId="light-present"/>
<!-- Channels related to motion sensor -->
<channel id="motion" typeId="motion"/>
<!-- Miscellaneous channels -->
<channel id="time" typeId="time"/>
</channels>
<config-description-ref uri="thing-type:bigassfan:device"/>
</thing-type>
<!-- Thing Type for BigAssFan light -->
<thing-type id="light">
<label>Light</label>
<description>Light</description>
<channels>
<!-- Channels related to light functions -->
<channel id="light-power" typeId="light-power"/>
<channel id="light-level" typeId="light-level"/>
<channel id="light-hue" typeId="light-hue"/>
<channel id="light-present" typeId="light-present"/>
<channel id="light-color" typeId="light-color"/>
<!-- Channels related to motion sensor -->
<channel id="motion" typeId="motion"/>
<!-- Miscellaneous channels -->
<channel id="time" typeId="time"/>
</channels>
<config-description-ref uri="thing-type:bigassfan:device"/>
</thing-type>
<!-- Thing Type for BigAssFan controller -->
<thing-type id="controller">
<label>Controller</label>
<description>Wall controller for Big Ass Fan</description>
<channels>
<channel id="motion" typeId="motion"/>
<channel id="time" typeId="time"/>
</channels>
<config-description-ref uri="thing-type:bigassfan:device"/>
</thing-type>
<!-- Channel types -->
<channel-type id="fan-power">
<item-type>Switch</item-type>
<label>Fan Power</label>
<description>Turn the fan on and off</description>
<category>Switch</category>
</channel-type>
<channel-type id="fan-speed">
<item-type>Dimmer</item-type>
<label>Fan Speed</label>
<description>Control the speed of the fan</description>
</channel-type>
<channel-type id="fan-direction">
<item-type>String</item-type>
<label>Fan Direction</label>
<description>Forward or reverse</description>
<state readOnly="true">
<options>
<option value="FWD">Forward</option>
<option value="REV">Reverse</option>
</options>
</state>
</channel-type>
<channel-type id="fan-auto">
<item-type>Switch</item-type>
<label>Fan Auto Mode</label>
<description>Enable or disable fan auto mode</description>
<category>Switch</category>
</channel-type>
<channel-type id="fan-whoosh">
<item-type>Switch</item-type>
<label>Fan Whoosh Mode</label>
<description>Enable or disable fan whoosh mode</description>
<category>Switch</category>
</channel-type>
<channel-type id="fan-smartmode">
<item-type>String</item-type>
<label>Fan Smartmode</label>
<description>Set Smartmode to OFF, COOLING, or HEATING</description>
<state>
<options>
<option value="OFF">OFF</option>
<option value="COOLING">COOLING</option>
<option value="HEATING">HEATING</option>
</options>
</state>
</channel-type>
<channel-type id="fan-learn-speed-min">
<item-type>Dimmer</item-type>
<label>Minimum Fan Speed</label>
<description>Set the minimum fan speed when in Smart Cooling mode</description>
</channel-type>
<channel-type id="fan-learn-speed-max">
<item-type>Dimmer</item-type>
<label>Maximum Fan Speed</label>
<description>Set the maximum fan speed when in Smart Cooling mode</description>
</channel-type>
<channel-type id="fan-speed-min">
<item-type>Dimmer</item-type>
<label>Minimum Fan Speed</label>
<description>Set the minimum fan speed</description>
</channel-type>
<channel-type id="fan-speed-max">
<item-type>Dimmer</item-type>
<label>Maximum Fan Speed</label>
<description>Set the maximum fan speed</description>
</channel-type>
<channel-type id="fan-wintermode">
<item-type>Switch</item-type>
<label>Fan Winter Mode</label>
<description>Enable or disable fan winter mode</description>
<category>Switch</category>
</channel-type>
<channel-type id="fan-sleep">
<item-type>Switch</item-type>
<label>Sleep Mode</label>
<description>Enable or disable fan sleep mode</description>
<category>Switch</category>
</channel-type>
<channel-type id="light-power">
<item-type>Switch</item-type>
<label>Light Power</label>
<description>Turn the fan's light on and off</description>
<category>Switch</category>
</channel-type>
<channel-type id="light-level">
<item-type>Dimmer</item-type>
<label>Light Brightness</label>
<description>The brightness level of the fan's light</description>
<category>DimmableLight</category>
</channel-type>
<channel-type id="light-auto">
<item-type>Switch</item-type>
<label>Light Auto Mode</label>
<description>Enable or disable light auto mode</description>
<category>Switch</category>
</channel-type>
<channel-type id="light-smarter">
<item-type>Switch</item-type>
<label>Smarter Lighting</label>
<description>Enable or disable Smarter Lighting</description>
<category>Switch</category>
</channel-type>
<channel-type id="light-level-min">
<item-type>Dimmer</item-type>
<label>Minimum Brightness</label>
<description>Set the minimum brightness level when using Smarter Lighting</description>
</channel-type>
<channel-type id="light-level-max">
<item-type>Dimmer</item-type>
<label>Maximum Brightness</label>
<description>Set the maximum brightness level when using Smarter Lighting</description>
</channel-type>
<channel-type id="light-hue">
<item-type>Dimmer</item-type>
<label>Hue</label>
<description>Set the hue of the light</description>
</channel-type>
<channel-type id="light-present">
<item-type>String</item-type>
<label>Light Present</label>
<description>Fan has the integrated light installed</description>
<state readOnly="true">
<options>
<option value="PRESENT">Present</option>
<option value="NOTPRESENT">Not Present</option>
</options>
</state>
</channel-type>
<channel-type id="light-color">
<item-type>String</item-type>
<label>Light Color</label>
<description>Light allows hue to be adjusted</description>
<state readOnly="true">
<options>
<option value="COLOR">Color</option>
<option value="NOCOLOR">No Color</option>
</options>
</state>
</channel-type>
<channel-type id="motion">
<item-type>Switch</item-type>
<label>Motion Sensor</label>
<description>The fan's motion sensor has detected motion</description>
<state readOnly="true">
<options>
<option value="ON">Triggered</option>
<option value="OFF">Untriggered</option>
</options>
</state>
</channel-type>
<channel-type id="time" advanced="true">
<item-type>DateTime</item-type>
<label>Time</label>
<description>Time reported by the fan</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>