added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user