[espmilighthub] Initial contribution (#9218)

* espmilighthub inital

Signed-off-by: Matthew Skinner <matt@pcmus.com>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
Co-authored-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
This commit is contained in:
Matthew Skinner
2021-01-30 07:05:55 +11:00
committed by GitHub
parent 80c52f4fc0
commit b2bb9176e5
17 changed files with 1244 additions and 1 deletions

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.mqtt.espmilighthub-${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-mqtt-espmilighthub" description="MQTT Binding EspMilightHub" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mqtt</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt/${project.version}</bundle>
<bundle start-level="81">mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.espmilighthub/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,31 @@
/**
* 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.mqtt.espmilighthub.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ConfigOptions} Holds the config for the settings.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class ConfigOptions {
public int whiteThreshold = -1;
public int whiteSat = 32;
public int whiteHue = 35;
public int favouriteWhite = 200;
public boolean oneTriggersNightMode = false;
public boolean powerFailsToMinimum = false;
public int dimmedCT = -1;
}

View File

@@ -0,0 +1,53 @@
/**
* 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.mqtt.espmilighthub.internal;
import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID;
import java.math.BigDecimal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link EspMilightHubBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class EspMilightHubBindingConstants {
public static final String STATES_BASE_TOPIC = "milight/states/";
public static final String COMMANDS_BASE_TOPIC = "milight/commands/";
public static final BigDecimal BIG_DECIMAL_100 = new BigDecimal(100);
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_RGB_CCT = new ThingTypeUID(BINDING_ID, "rgb_cct");
public static final ThingTypeUID THING_TYPE_CCT = new ThingTypeUID(BINDING_ID, "cct");
public static final ThingTypeUID THING_TYPE_RGBW = new ThingTypeUID(BINDING_ID, "rgbw");
public static final ThingTypeUID THING_TYPE_RGB = new ThingTypeUID(BINDING_ID, "rgb");
public static final ThingTypeUID THING_TYPE_FUT089 = new ThingTypeUID(BINDING_ID, "fut089");
public static final ThingTypeUID THING_TYPE_FUT091 = new ThingTypeUID(BINDING_ID, "fut091");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_RGBW, THING_TYPE_RGB_CCT,
THING_TYPE_FUT089, THING_TYPE_FUT091, THING_TYPE_CCT, THING_TYPE_RGB);
// Channels
public static final String CHANNEL_LEVEL = "level";
public static final String CHANNEL_COLOUR = "colour";
public static final String CHANNEL_COLOURTEMP = "colourTemperature";
public static final String CHANNEL_DISCO_MODE = "discoMode";
public static final String CHANNEL_BULB_MODE = "bulbMode";
public static final String CHANNEL_COMMAND = "command";
}

View File

@@ -0,0 +1,60 @@
/**
* 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.mqtt.espmilighthub.internal;
import static org.openhab.binding.mqtt.espmilighthub.internal.EspMilightHubBindingConstants.SUPPORTED_THING_TYPES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.espmilighthub.internal.handler.EspMilightHubHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
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 EspMilightHubHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Matthew Skinner - Initial contribution
*/
@Component(service = ThingHandlerFactory.class)
@NonNullByDefault
public class EspMilightHubHandlerFactory extends BaseThingHandlerFactory {
private final ThingRegistry thingRegistry;
@Activate
public EspMilightHubHandlerFactory(final @Reference ThingRegistry thingRegistry) {
this.thingRegistry = thingRegistry;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return new EspMilightHubHandler(thing, thingRegistry);
}
return null;
}
}

View File

@@ -0,0 +1,65 @@
/**
* 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.mqtt.espmilighthub.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Helper} Removes the need for any external JSON libs
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class Helper {
/**
* resolveJSON will return a value from any key/path that you give and the string can be terminated by any ,}"
* characters.
*
*/
public static String resolveJSON(String messageJSON, String jsonPath, int resultMaxLength) {
String result = "";
int index = 0;
index = messageJSON.indexOf(jsonPath);
if (index != -1) {
if ((index + jsonPath.length() + resultMaxLength) > messageJSON.length()) {
result = (messageJSON.substring(index + jsonPath.length(), messageJSON.length()));
} else {
result = (messageJSON.substring(index + jsonPath.length(),
index + jsonPath.length() + resultMaxLength));
}
index = result.indexOf(',');
if (index == -1) {
index = result.indexOf('"');
if (index == -1) {
index = result.indexOf('}');
if (index == -1) {
return result;
} else {
return result.substring(0, index);
}
} else {
return result.substring(0, index);
}
} else {
result = result.substring(0, index);
index = result.indexOf('"');
if (index == -1) {
return result;
} else {
return result.substring(0, index);
}
}
}
return "";
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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.mqtt.espmilighthub.internal.discovery;
import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID;
import static org.openhab.binding.mqtt.espmilighthub.internal.EspMilightHubBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mqtt.discovery.AbstractMQTTDiscovery;
import org.openhab.binding.mqtt.discovery.MQTTTopicDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
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;
/**
* The {@link EspMilightHubDiscoveryService} is responsible for finding globes
* and setting them up for the handlers.
*
* @author Matthew Skinner - Initial contribution
*/
@Component(service = DiscoveryService.class, configurationPid = "discovery.mqttespmilighthub")
@NonNullByDefault
public class EspMilightHubDiscoveryService extends AbstractMQTTDiscovery {
protected final MQTTTopicDiscoveryService discoveryService;
@Activate
public EspMilightHubDiscoveryService(@Reference MQTTTopicDiscoveryService discoveryService) {
super(SUPPORTED_THING_TYPES, 3, true, STATES_BASE_TOPIC + "#");
this.discoveryService = discoveryService;
}
@Override
protected MQTTTopicDiscoveryService getDiscoveryService() {
return discoveryService;
}
@Override
public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection connection, String topic,
byte[] payload) {
resetTimeout();
if (topic.startsWith(STATES_BASE_TOPIC)) {
String cutTopic = topic.replace(STATES_BASE_TOPIC, "");
int index = cutTopic.indexOf("/");
if (index != -1) // -1 means "not found"
{
String remoteCode = (cutTopic.substring(0, index)); // Store the remote code for use later
cutTopic = topic.replace(STATES_BASE_TOPIC + remoteCode + "/", "");
index = cutTopic.indexOf("/");
if (index != -1) {
String globeType = (cutTopic.substring(0, index));
String remoteGroupID = (cutTopic.substring(index + 1, index + 2));
// openHAB's framework has better code for handling groups then the firmware does
if (!remoteGroupID.equals("0")) {// Users can manually add group 0 things if they wish
publishDevice(connectionBridge, connection, topic, remoteCode, globeType, remoteGroupID);
}
}
}
}
}
void publishDevice(ThingUID connectionBridge, MqttBrokerConnection connection, String topic, String remoteCode,
String globeType, String remoteGroupID) {
Map<String, Object> properties = new HashMap<>();
properties.put("deviceid", remoteCode + remoteGroupID);
properties.put("basetopic", STATES_BASE_TOPIC + remoteCode + "/" + globeType + "/" + remoteGroupID);
thingDiscovered(DiscoveryResultBuilder
.create(new ThingUID(new ThingTypeUID(BINDING_ID, globeType), connectionBridge,
remoteCode + remoteGroupID))
.withProperties(properties).withRepresentationProperty("deviceid").withBridge(connectionBridge)
.withLabel("Milight " + globeType).build());
}
@Override
public void topicVanished(ThingUID connectionBridge, MqttBrokerConnection connection, String topic) {
}
}

View File

@@ -0,0 +1,387 @@
/**
* 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.mqtt.espmilighthub.internal.handler;
import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID;
import static org.openhab.binding.mqtt.espmilighthub.internal.EspMilightHubBindingConstants.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.espmilighthub.internal.ConfigOptions;
import org.openhab.binding.mqtt.espmilighthub.internal.Helper;
import org.openhab.binding.mqtt.handler.AbstractBrokerHandler;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EspMilightHubHandler} is responsible for handling commands of the globes, which are then
* sent to one of the bridges to be sent out by MQTT.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class EspMilightHubHandler extends BaseThingHandler implements MqttConnectionObserver, MqttMessageSubscriber {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private @Nullable MqttBrokerConnection connection;
private ThingRegistry thingRegistry;
private String globeType = "";
private String bulbMode = "";
private String remotesGroupID = "";
private String channelPrefix = "";
private String fullCommandTopic = "";
private String fullStatesTopic = "";
private BigDecimal maxColourTemp = BigDecimal.ZERO;
private BigDecimal minColourTemp = BigDecimal.ZERO;
private BigDecimal savedLevel = BIG_DECIMAL_100;
private ConfigOptions config = new ConfigOptions();
public EspMilightHubHandler(Thing thing, ThingRegistry thingRegistry) {
super(thing);
this.thingRegistry = thingRegistry;
}
void changeChannel(String channel, State state) {
updateState(new ChannelUID(channelPrefix + channel), state);
// Remote code of 0 means that all channels need to follow this change.
if (remotesGroupID.equals("0")) {
switch (globeType) {
// These two are 8 channel remotes
case "fut091":
case "fut089":
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "5:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "6:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "7:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "8:" + channel),
state);
default:
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "1:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "2:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "3:" + channel),
state);
updateState(new ChannelUID(channelPrefix.substring(0, channelPrefix.length() - 2) + "4:" + channel),
state);
}
}
}
private void processIncomingState(String messageJSON) {
// Need to handle State and Level at the same time to process level=0 as off//
BigDecimal tempBulbLevel = BigDecimal.ZERO;
String bulbState = Helper.resolveJSON(messageJSON, "\"state\":\"", 3);
String bulbLevel = Helper.resolveJSON(messageJSON, "\"level\":", 3);
if (!bulbLevel.isEmpty()) {
if (bulbLevel.equals("0") || bulbState.equals("OFF")) {
changeChannel(CHANNEL_LEVEL, OnOffType.OFF);
tempBulbLevel = BigDecimal.ZERO;
} else {
tempBulbLevel = new BigDecimal(bulbLevel);
changeChannel(CHANNEL_LEVEL, new PercentType(tempBulbLevel));
}
} else if (bulbState.equals("ON") || bulbState.equals("OFF")) { // NOTE: Level is missing when this runs
changeChannel(CHANNEL_LEVEL, OnOffType.valueOf(bulbState));
}
bulbMode = Helper.resolveJSON(messageJSON, "\"bulb_mode\":\"", 5);
switch (bulbMode) {
case "white":
if (!globeType.equals("cct") && !globeType.equals("fut091")) {
changeChannel(CHANNEL_BULB_MODE, new StringType("white"));
changeChannel(CHANNEL_COLOUR, new HSBType("0,0," + tempBulbLevel));
changeChannel(CHANNEL_DISCO_MODE, new StringType("None"));
}
String bulbCTemp = Helper.resolveJSON(messageJSON, "\"color_temp\":", 3);
if (!bulbCTemp.isEmpty()) {
int ibulbCTemp = (int) Math.round(((Float.valueOf(bulbCTemp) / 2.17) - 171) * -1);
changeChannel(CHANNEL_COLOURTEMP, new PercentType(ibulbCTemp));
}
break;
case "color":
changeChannel(CHANNEL_BULB_MODE, new StringType("color"));
changeChannel(CHANNEL_DISCO_MODE, new StringType("None"));
String bulbHue = Helper.resolveJSON(messageJSON, "\"hue\":", 3);
String bulbSaturation = Helper.resolveJSON(messageJSON, "\"saturation\":", 3);
if (bulbHue.isEmpty()) {
logger.warn("Milight MQTT message came in as being a colour mode, but was missing a HUE value.");
} else {
if (bulbSaturation.isEmpty()) {
bulbSaturation = "100";
}
changeChannel(CHANNEL_COLOUR, new HSBType(bulbHue + "," + bulbSaturation + "," + tempBulbLevel));
}
break;
case "scene":
if (!globeType.equals("cct") && !globeType.equals("fut091")) {
changeChannel(CHANNEL_BULB_MODE, new StringType("scene"));
}
String bulbDiscoMode = Helper.resolveJSON(messageJSON, "\"mode\":", 1);
if (!bulbDiscoMode.isEmpty()) {
changeChannel(CHANNEL_DISCO_MODE, new StringType(bulbDiscoMode));
}
break;
case "night":
if (!globeType.equals("cct") && !globeType.equals("fut091")) {
changeChannel(CHANNEL_BULB_MODE, new StringType("night"));
if (config.oneTriggersNightMode) {
changeChannel(CHANNEL_LEVEL, new PercentType("1"));
}
}
break;
}
}
/*
* Used to calculate the colour temp for a globe if you want the light to get warmer as it is dimmed like a
* traditional halogen globe
*/
private int autoColourTemp(int brightness) {
return minColourTemp.subtract(
minColourTemp.subtract(maxColourTemp).divide(BIG_DECIMAL_100).multiply(new BigDecimal(brightness)))
.intValue();
}
void turnOff() {
if (config.powerFailsToMinimum) {
sendMQTT("{\"state\":\"OFF\",\"level\":0}");
} else {
sendMQTT("{\"state\":\"OFF\"}");
}
}
void handleLevelColour(Command command) {
if (command instanceof OnOffType) {
if (OnOffType.ON.equals(command)) {
sendMQTT("{\"state\":\"ON\",\"level\":" + savedLevel + "}");
return;
} else {
turnOff();
}
} else if (command instanceof IncreaseDecreaseType) {
if (IncreaseDecreaseType.INCREASE.equals(command)) {
if (savedLevel.intValue() <= 90) {
savedLevel = savedLevel.add(BigDecimal.TEN);
}
} else {
if (savedLevel.intValue() >= 10) {
savedLevel = savedLevel.subtract(BigDecimal.TEN);
}
}
sendMQTT("{\"state\":\"ON\",\"level\":" + savedLevel.intValue() + "}");
return;
} else if (command instanceof HSBType) {
HSBType hsb = (HSBType) command;
// This feature allows google home or Echo to trigger white mode when asked to turn color to white.
if (hsb.getHue().intValue() == config.whiteHue && hsb.getSaturation().intValue() == config.whiteSat) {
if ("rgb_cct".equals(globeType) || "fut089".equals(globeType)) {
sendMQTT("{\"state\":\"ON\",\"color_temp\":" + config.favouriteWhite + "}");
} else {// globe must only have 1 type of white
sendMQTT("{\"command\":\"set_white\"}");
}
return;
} else if (PercentType.ZERO.equals(hsb.getBrightness())) {
turnOff();
return;
} else if (config.whiteThreshold != -1 && hsb.getSaturation().intValue() <= config.whiteThreshold
&& "rgbw".equals(globeType)) {
sendMQTT("{\"command\":\"set_white\"}");
return;
}
sendMQTT("{\"state\":\"ON\",\"level\":" + hsb.getBrightness().intValue() + ",\"hue\":"
+ hsb.getHue().intValue() + ",\"saturation\":" + hsb.getSaturation().intValue() + "}");
savedLevel = hsb.getBrightness().toBigDecimal();
return;
} else if (command instanceof PercentType) {
PercentType percentType = (PercentType) command;
if (percentType.intValue() == 0) {
turnOff();
return;
} else if (percentType.intValue() == 1 && config.oneTriggersNightMode) {
sendMQTT("{\"command\":\"night_mode\"}");
return;
}
sendMQTT("{\"state\":\"ON\",\"level\":" + command + "}");
savedLevel = percentType.toBigDecimal();
if (globeType.equals("rgb_cct") || globeType.equals("fut089")) {
if (config.dimmedCT > 0 && bulbMode.equals("white")) {
sendMQTT("{\"state\":\"ON\",\"color_temp\":" + autoColourTemp(savedLevel.intValue()) + "}");
}
}
return;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
return;
}
switch (channelUID.getId()) {
case CHANNEL_LEVEL:
handleLevelColour(command);
return;
case CHANNEL_BULB_MODE:
bulbMode = command.toString();
break;
case CHANNEL_COLOURTEMP:
int scaledCommand = (int) Math.round((370 - (2.17 * Float.valueOf(command.toString()))));
sendMQTT("{\"state\":\"ON\",\"level\":" + savedLevel + ",\"color_temp\":" + scaledCommand + "}");
break;
case CHANNEL_COMMAND:
sendMQTT("{\"command\":\"" + command + "\"}");
break;
case CHANNEL_DISCO_MODE:
sendMQTT("{\"mode\":\"" + command + "\"}");
break;
case CHANNEL_COLOUR:
handleLevelColour(command);
}
}
@Override
public void initialize() {
config = getConfigAs(ConfigOptions.class);
if (config.dimmedCT > 0) {
maxColourTemp = new BigDecimal(config.favouriteWhite);
minColourTemp = new BigDecimal(config.dimmedCT);
if (minColourTemp.intValue() <= maxColourTemp.intValue()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The dimmedCT config value must be greater than the favourite White value.");
return;
}
}
Bridge localBridge = getBridge();
if (localBridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"Globe must have a valid bridge selected before it can come online.");
return;
} else {
globeType = thing.getThingTypeUID().getId();// eg rgb_cct
String globeLocation = this.getThing().getUID().getId();// eg 0x014
remotesGroupID = globeLocation.substring(globeLocation.length() - 1, globeLocation.length());// eg 4
String remotesIDCode = globeLocation.substring(0, globeLocation.length() - 1);// eg 0x01
fullCommandTopic = COMMANDS_BASE_TOPIC + remotesIDCode + "/" + globeType + "/" + remotesGroupID;
fullStatesTopic = STATES_BASE_TOPIC + remotesIDCode + "/" + globeType + "/" + remotesGroupID;
// Need to remove the lowercase x from 0x12AB in case it contains all numbers
String caseCheck = globeLocation.substring(2, globeLocation.length() - 1);
if (!caseCheck.equals(caseCheck.toUpperCase())) {
logger.warn(
"The milight globe {}{} is using lowercase for the remote code when the hub needs UPPERCASE",
remotesIDCode, remotesGroupID);
}
channelPrefix = BINDING_ID + ":" + globeType + ":" + localBridge.getUID().getId() + ":" + remotesIDCode
+ remotesGroupID + ":";
connectMQTT();
}
}
private void sendMQTT(String payload) {
MqttBrokerConnection localConnection = connection;
if (localConnection != null) {
localConnection.publish(fullCommandTopic, payload.getBytes(), 1, false);
}
}
@Override
public void processMessage(String topic, byte[] payload) {
String state = new String(payload, StandardCharsets.UTF_8);
logger.trace("Recieved the following new Milight state:{}:{}", topic, state);
processIncomingState(state);
}
@Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
logger.debug("MQTT brokers state changed to:{}", state);
switch (state) {
case CONNECTED:
updateStatus(ThingStatus.ONLINE);
break;
case CONNECTING:
case DISCONNECTED:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Bridge (broker) is not connected to your MQTT broker.");
}
}
public void connectMQTT() {
Bridge localBridge = this.getBridge();
if (localBridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Bridge is missing or offline, you need to setup a working MQTT broker first.");
return;
}
ThingUID thingUID = localBridge.getBridgeUID();
if (thingUID == null) {
return;
}
Thing thing = thingRegistry.get(thingUID);
if (thing == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Bridge is missing or offline, you need to setup a working MQTT broker first.");
return;
}
ThingHandler handler = thing.getHandler();
if (handler instanceof AbstractBrokerHandler) {
AbstractBrokerHandler abh = (AbstractBrokerHandler) handler;
MqttBrokerConnection localConnection = abh.getConnection();
if (localConnection != null) {
localConnection.setKeepAliveInterval(20);
localConnection.setQos(1);
localConnection.setUnsubscribeOnStop(true);
localConnection.addConnectionObserver(this);
localConnection.start();
localConnection.subscribe(fullStatesTopic + "/#", this);
connection = localConnection;
if (localConnection.connectionState().compareTo(MqttConnectionState.CONNECTED) == 0) {
updateStatus(ThingStatus.ONLINE);
}
}
}
return;
}
@Override
public void dispose() {
MqttBrokerConnection localConnection = connection;
if (localConnection != null) {
localConnection.unsubscribe(fullStatesTopic + "/#", this);
}
}
}

View File

@@ -0,0 +1,111 @@
<?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:mqtt:rgb">
<parameter name="oneTriggersNightMode" type="boolean" required="true">
<label>1% Triggers Night Mode</label>
<description>1% on a slider will trigger the Night Mode.</description>
<default>false</default>
</parameter>
<parameter name="powerFailsToMinimum" type="boolean" required="true">
<label>Dimmed on Power Fail</label>
<description>If lights loose power when soft off, the lights will default back to the minimum brightness.</description>
<default>false</default>
</parameter>
</config-description>
<config-description uri="thing-type:mqtt:cct">
<parameter name="dimmedCT" type="integer" required="false" min="153" max="370">
<label>Dimmed Colour Temp</label>
<description>Traditional globes grow warmer the more they are dimmed. Set this to 370, or leave blank to disable.
</description>
</parameter>
<parameter name="oneTriggersNightMode" type="boolean" required="true">
<label>1% Triggers Night Mode</label>
<description>1% on a slider will trigger the Night Mode.</description>
<default>false</default>
</parameter>
</config-description>
<config-description uri="thing-type:mqtt:rgbandcct">
<parameter name="whiteHue" type="integer" required="true" min="-1" max="360">
<label>White Hue</label>
<description>When both the whiteHue and whiteSat values are seen by the binding it will trigger the white LEDS.
</description>
<default>35</default>
</parameter>
<parameter name="whiteSat" type="integer" required="true" min="-1" max="100">
<label>White Saturation</label>
<description>When both the whiteHue and whiteSat values are seen by the binding it will trigger the white LEDS.
</description>
<default>32</default>
</parameter>
<parameter name="favouriteWhite" type="integer" required="true" min="153" max="370">
<label>Favourite White</label>
<description>When a shortcut triggers white mode, use this for the colour white.</description>
<default>200</default>
</parameter>
<parameter name="dimmedCT" type="integer" required="false" min="153" max="370">
<label>Dimmed Colour Temp</label>
<description>Traditional globes grow warmer the more they are dimmed. Set this to 370, or leave blank to disable.
</description>
</parameter>
<parameter name="oneTriggersNightMode" type="boolean" required="true">
<label>1% Triggers Night Mode</label>
<description>1% on a slider will trigger the Night Mode.</description>
<default>false</default>
</parameter>
<parameter name="powerFailsToMinimum" type="boolean" required="true">
<label>Dimmed on Power Fail</label>
<description>If lights loose power, the lights will turn on to the minimum brightness.</description>
<default>true</default>
</parameter>
</config-description>
<config-description uri="thing-type:mqtt:rgbw">
<parameter name="whiteHue" type="integer" required="true" min="-1" max="360">
<label>White Hue</label>
<description>When both the whiteHue and whiteSat values are seen by the binding it will trigger the white LEDS.
</description>
<default>35</default>
</parameter>
<parameter name="whiteSat" type="integer" required="true" min="-1" max="100">
<label>White Saturation</label>
<description>When both the whiteHue and whiteSat values are seen by the binding it will trigger the white LEDS.
</description>
<default>32</default>
</parameter>
<parameter name="oneTriggersNightMode" type="boolean" required="true">
<label>1% Triggers Night Mode</label>
<description>1% on a slider will trigger the Night Mode.</description>
<default>false</default>
</parameter>
<parameter name="powerFailsToMinimum" type="boolean" required="true">
<label>Dimmed on Power Fail</label>
<description>If lights loose power, the lights will turn on to the minimum brightness.</description>
<default>false</default>
</parameter>
<parameter name="whiteThreshold" type="integer" required="true" min="-1" max="99">
<label>White Threshold</label>
<description>RGBW saturation changes, will trigger the white mode. -1 will disable this feature.
</description>
<default>12</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mqtt"
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 id="rgb_cct">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight RGBCCT</label>
<description>Led globe with full Colour, and both cool and warm whites.</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colourTemperature" typeId="colourTemperature"/>
<channel id="colour" typeId="colour"/>
<channel id="discoMode" typeId="discoMode"/>
<channel id="bulbMode" typeId="bulbMode"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:rgbandcct"/>
</thing-type>
<thing-type id="fut089">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight FUT089</label>
<description>Use this when your remote is the newer 8 group type called FUT089 and your globes are rgb_cct</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colourTemperature" typeId="colourTemperature"/>
<channel id="colour" typeId="colour"/>
<channel id="discoMode" typeId="discoMode"/>
<channel id="bulbMode" typeId="bulbMode"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:rgbandcct"/>
</thing-type>
<thing-type id="fut091">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight FUT091</label>
<description>Use this when your remote is the newer fut091 and your globes are cct</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colourTemperature" typeId="colourTemperature"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:cct"/>
</thing-type>
<thing-type id="cct">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight CCT</label>
<description>Led globe with both cool and warm white controls</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colourTemperature" typeId="colourTemperature"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:cct"/>
</thing-type>
<thing-type id="rgbw">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight RGBW</label>
<description>RGB Globe with a fixed white</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colour" typeId="colour"/>
<channel id="discoMode" typeId="discoMode"/>
<channel id="bulbMode" typeId="bulbMode"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:rgbw"/>
</thing-type>
<thing-type id="rgb">
<supported-bridge-type-refs>
<bridge-type-ref id="broker"/>
<bridge-type-ref id="systemBroker"/>
</supported-bridge-type-refs>
<label>Milight RGB</label>
<description>RGB Globe with no white</description>
<category>Lightbulb</category>
<channels>
<channel id="level" typeId="level"/>
<channel id="colour" typeId="colour"/>
<channel id="discoMode" typeId="discoMode"/>
<channel id="bulbMode" typeId="bulbMode"/>
<channel id="command" typeId="command"/>
</channels>
<config-description-ref uri="thing-type:mqtt:rgb"/>
</thing-type>
<channel-type id="level">
<item-type>Dimmer</item-type>
<label>Level</label>
<description>Level changes the brightness of the globe.</description>
<category>Slider</category>
</channel-type>
<channel-type id="colourTemperature">
<item-type>Dimmer</item-type>
<label>Colour Temperature</label>
<description>Change from cool to warm white with this control.</description>
<category>Slider</category>
</channel-type>
<channel-type id="colour">
<item-type>Color</item-type>
<label>Colour</label>
<description>Allows you to change the colour, brightness and saturation of the globe.</description>
<category>ColorLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<channel-type id="command" advanced="true">
<item-type>String</item-type>
<label>Command</label>
<description>Send a raw command to the globe/s.</description>
<state>
<options>
<option value="next_mode">Next Mode</option>
<option value="previous_mode">Previous Mode</option>
<option value="mode_speed_up">Mode Speed Up</option>
<option value="mode_speed_down">Mode Speed Down</option>
<option value="set_white">Set White</option>
<option value="level_down">Level Down</option>
<option value="level_up">Level Up</option>
<option value="temperature_down">Temperature Down</option>
<option value="temperature_up">Temperature Up</option>
<option value="night_mode">Night Mode</option>
</options>
</state>
</channel-type>
<channel-type id="bulbMode" advanced="true">
<item-type>String</item-type>
<label>Bulb Mode</label>
<description>Displays the mode the bulb is currently in.</description>
<state readOnly="true">
<options>
<option value="white">white</option>
<option value="color">color</option>
<option value="scene">scene</option>
<option value="night">night</option>
</options>
</state>
</channel-type>
<channel-type id="discoMode" advanced="true">
<item-type>String</item-type>
<label>Disco Mode</label>
<description>Switch to a Disco mode directly.</description>
<state>
<options>
<option value="0">Disco 0</option>
<option value="1">Disco 1</option>
<option value="2">Disco 2</option>
<option value="3">Disco 3</option>
<option value="4">Disco 4</option>
<option value="5">Disco 5</option>
<option value="6">Disco 6</option>
<option value="7">Disco 7</option>
<option value="8">Disco 8</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>