mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mystrom] Add support for myStrom Bulb (#9910)
* Add support to myStrom Bulb Add properties to myStrom devices and an action to refresh the properties. Signed-off-by: Frederic Chastagnol <fchastagnol@fredoware.ch> * Fixes according to review comments Signed-off-by: Frederic Chastagnol <fchastagnol@fredoware.ch> * Update bundles/org.openhab.binding.mystrom/README.md Co-authored-by: J-N-K <J-N-K@users.noreply.github.com> * Fixes according to review comments Signed-off-by: Frederic Chastagnol <fchastagnol@fredoware.ch> * Use system color temperature channel type channel type system.color-temperature is used and values mapped from 1-18 to 0-100% Signed-off-by: Frederic Chastagnol <fchastagnol@fredoware.ch> * Better tracking of colour and brightness values Format power state Signed-off-by: Frederic Chastagnol <fchastagnol@fredoware.ch> Co-authored-by: J-N-K <J-N-K@users.noreply.github.com>
This commit is contained in:
parent
fd1f7ebe75
commit
7050a1478e
@ -9,6 +9,9 @@ This bundle adds the following thing types:
|
||||
| Thing | ThingTypeID | Description |
|
||||
| ------------------ | ----------- | -------------------------------------------------- |
|
||||
| myStrom Smart Plug | mystromplug | A myStrom smart plug |
|
||||
| myStrom Bulb | mystrombulb | A myStrom bulb |
|
||||
|
||||
According to the myStrom API documentation all request specific to the myStrom Bulb are also work on the LED strip.
|
||||
|
||||
## Discovery
|
||||
|
||||
@ -24,13 +27,37 @@ The following parameters are valid for all thing types:
|
||||
| hostname | string | yes | localhost | The IP address or hostname of the myStrom smart plug |
|
||||
| refresh | integer | no | 10 | Poll interval in seconds. Increase this if you encounter connection errors |
|
||||
|
||||
## Properties
|
||||
|
||||
In addition to the configuration a myStrom thing has the following properties.
|
||||
The properties are updated during initialize.
|
||||
Disabling/enabling the thing can be used to update the properties.
|
||||
|
||||
| Property-Name | Description |
|
||||
| ------------- | --------------------------------------------------------------------- |
|
||||
| version | Current firmware version |
|
||||
| type | The type of the device (i.e. bulb = 102) |
|
||||
| ssid | SSID of the currently connected network |
|
||||
| ip | Current ip address |
|
||||
| mask | Mask of the current network |
|
||||
| gateway | Gateway of the current network |
|
||||
| dns | DNS of the current network |
|
||||
| static | Whether or not the ip address is static |
|
||||
| connected | Whether or not the device is connected to the internet |
|
||||
| mac | The mac address of the bridge in upper case letters without delimiter |
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel ID | Item Type | Read only | Description |
|
||||
| ---------------- | -------------------- | --------- | ------------------------------------------------------------- |
|
||||
| switch | Switch | false | Turn the smart plug on or off |
|
||||
| power | Number:Power | true | The currently delivered power |
|
||||
| temperature | Number:Temperature | true | The temperature at the plug |
|
||||
| Channel ID | Item Type | Read only | Description | Thing types supporting this channel |
|
||||
| ---------------- | -------------------- | --------- | --------------------------------------------------------------------- |-------------------------------------|
|
||||
| switch | Switch | false | Turn the device on or off | mystromplug, mystrombulb |
|
||||
| power | Number:Power | true | The currently delivered power | mystromplug, mystrombulb |
|
||||
| temperature | Number:Temperature | true | The temperature at the plug | mystromplug |
|
||||
| color | Color | false | The color we set the bulb to (mode 'hsv') | mystrombulb |
|
||||
| colorTemperature | Dimmer | false | The color temperature of the bulb in mode 'mono' (percentage) | mystrombulb |
|
||||
| brightness | Dimmer | false | The brightness of the bulb in mode 'mono' | mystrombulb |
|
||||
| ramp | Number:Time | false | Transition time from the light’s current state to the new state. [ms] | mystrombulb |
|
||||
| mode | String | false | The color mode we want the Bulb to set to (rgb, hsv or mono) | mystrombulb |
|
||||
|
||||
## Full Example
|
||||
|
||||
|
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 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.mystrom.internal;
|
||||
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_CONNECTED;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_DNS;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_GW;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_IP;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_LAST_REFRESH;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_MAC;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_MASK;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_SSID;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_STATIC;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_TYPE;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.PROPERTY_VERSION;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMyStromHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Frederic Chastagnol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractMyStromHandler extends BaseThingHandler {
|
||||
protected static final String COMMUNICATION_ERROR = "Error while communicating to the myStrom plug: ";
|
||||
protected static final String HTTP_REQUEST_URL_PREFIX = "http://";
|
||||
|
||||
protected final HttpClient httpClient;
|
||||
protected String hostname = "";
|
||||
protected String mac = "";
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
protected final Gson gson = new Gson();
|
||||
|
||||
public AbstractMyStromHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void initialize() {
|
||||
MyStromConfiguration config = getConfigAs(MyStromConfiguration.class);
|
||||
this.hostname = HTTP_REQUEST_URL_PREFIX + config.hostname;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.schedule(this::initializeInternal, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void dispose() {
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void updateProperties() throws MyStromException {
|
||||
String json = sendHttpRequest(HttpMethod.GET, "/api/v1/info", null);
|
||||
MyStromDeviceInfo deviceInfo = gson.fromJson(json, MyStromDeviceInfo.class);
|
||||
if (deviceInfo == null) {
|
||||
throw new MyStromException("Cannot retrieve device info from myStrom device " + getThing().getUID());
|
||||
}
|
||||
this.mac = deviceInfo.mac;
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_MAC, deviceInfo.mac);
|
||||
properties.put(PROPERTY_VERSION, deviceInfo.version);
|
||||
properties.put(PROPERTY_TYPE, Long.toString(deviceInfo.type));
|
||||
properties.put(PROPERTY_SSID, deviceInfo.ssid);
|
||||
properties.put(PROPERTY_IP, deviceInfo.ip);
|
||||
properties.put(PROPERTY_MASK, deviceInfo.mask);
|
||||
properties.put(PROPERTY_GW, deviceInfo.gw);
|
||||
properties.put(PROPERTY_DNS, deviceInfo.dns);
|
||||
properties.put(PROPERTY_STATIC, Boolean.toString(deviceInfo.staticState));
|
||||
properties.put(PROPERTY_CONNECTED, Boolean.toString(deviceInfo.connected));
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Locale.getDefault());
|
||||
properties.put(PROPERTY_LAST_REFRESH, formatter.format(calendar.getTime()));
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the API with the given http method, request path and actual data.
|
||||
*
|
||||
* @param method the http method to make the call with
|
||||
* @param path The path of the API endpoint
|
||||
* @param requestData the actual raw data to send in the request body, may be {@code null}
|
||||
* @return String contents of the response for the GET request.
|
||||
* @throws MyStromException Throws on communication error
|
||||
*/
|
||||
protected final String sendHttpRequest(HttpMethod method, String path, @Nullable String requestData)
|
||||
throws MyStromException {
|
||||
String url = hostname + path;
|
||||
try {
|
||||
Request request = httpClient.newRequest(url).timeout(10, TimeUnit.SECONDS).method(method);
|
||||
if (requestData != null) {
|
||||
request = request.content(new StringContentProvider(requestData)).header(HttpHeader.CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded");
|
||||
}
|
||||
ContentResponse response = request.send();
|
||||
if (response.getStatus() != HttpStatus.OK_200) {
|
||||
throw new MyStromException("Error sending HTTP " + method + " request to " + url
|
||||
+ ". Got response code: " + response.getStatus());
|
||||
}
|
||||
return response.getContentAsString();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new MyStromException(COMMUNICATION_ERROR + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeInternal() {
|
||||
try {
|
||||
updateProperties();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
MyStromConfiguration config = getConfigAs(MyStromConfiguration.class);
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevice, 0, config.refresh, TimeUnit.SECONDS);
|
||||
} catch (MyStromException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void pollDevice();
|
||||
}
|
@ -20,6 +20,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Paul Frank - Initial contribution
|
||||
* @author Frederic Chastagnol - Add constants for myStrom bulb support
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyStromBindingConstants {
|
||||
@ -30,9 +31,36 @@ public class MyStromBindingConstants {
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_PLUG = new ThingTypeUID(BINDING_ID, "mystromplug");
|
||||
public static final ThingTypeUID THING_TYPE_BULB = new ThingTypeUID(BINDING_ID, "mystrombulb");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_SWITCH = "switch";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_COLOR = "color";
|
||||
public static final String CHANNEL_RAMP = "ramp";
|
||||
public static final String CHANNEL_MODE = "mode";
|
||||
public static final String CHANNEL_COLOR_TEMPERATURE = "colorTemperature";
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
|
||||
// Config
|
||||
public static final String CONFIG_MAC = "mac";
|
||||
|
||||
// List of all Properties
|
||||
public static final String PROPERTY_MAC = "mac";
|
||||
public static final String PROPERTY_VERSION = "version";
|
||||
public static final String PROPERTY_TYPE = "type";
|
||||
public static final String PROPERTY_SSID = "ssid";
|
||||
public static final String PROPERTY_IP = "ip";
|
||||
public static final String PROPERTY_MASK = "mask";
|
||||
public static final String PROPERTY_GW = "gw";
|
||||
public static final String PROPERTY_DNS = "dns";
|
||||
public static final String PROPERTY_STATIC = "static";
|
||||
public static final String PROPERTY_CONNECTED = "connected";
|
||||
public static final String PROPERTY_LAST_REFRESH = "lastRefresh";
|
||||
|
||||
// myStrom Bulb modes
|
||||
public static final String RGB = "rgb";
|
||||
public static final String HSV = "hsv";
|
||||
public static final String MONO = "mono";
|
||||
}
|
||||
|
@ -0,0 +1,297 @@
|
||||
/**
|
||||
* 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.mystrom.internal;
|
||||
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_BRIGHTNESS;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_COLOR;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_COLOR_TEMPERATURE;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_MODE;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_POWER;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_RAMP;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_SWITCH;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.HSV;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.MONO;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.RGB;
|
||||
import static org.openhab.core.library.unit.Units.SECOND;
|
||||
import static org.openhab.core.library.unit.Units.WATT;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* The {@link MyStromBulbHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Frederic Chastagnol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyStromBulbHandler extends AbstractMyStromHandler {
|
||||
|
||||
private static final Type DEVICE_INFO_MAP_TYPE = new TypeToken<HashMap<String, MyStromDeviceSpecificInfo>>() {
|
||||
}.getType();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyStromBulbHandler.class);
|
||||
|
||||
private final ExpiringCache<Map<String, MyStromDeviceSpecificInfo>> cache = new ExpiringCache<>(
|
||||
Duration.ofSeconds(3), this::getReport);
|
||||
|
||||
private PercentType lastBrightness = PercentType.HUNDRED;
|
||||
private PercentType lastColorTemperature = new PercentType(50);
|
||||
private String lastMode = MONO;
|
||||
private HSBType lastColor = HSBType.WHITE;
|
||||
|
||||
public MyStromBulbHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
pollDevice();
|
||||
} else {
|
||||
String sResp = null;
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SWITCH:
|
||||
if (command instanceof OnOffType) {
|
||||
sResp = sendToBulb(command == OnOffType.ON ? "on" : "off", null, null, null);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
if (command instanceof HSBType) {
|
||||
if (Objects.equals(((HSBType) command).as(OnOffType.class), OnOffType.OFF)) {
|
||||
sResp = sendToBulb("off", null, null, null);
|
||||
} else {
|
||||
String hsv = command.toString().replaceAll(",", ";");
|
||||
sResp = sendToBulb("on", hsv, null, HSV);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_BRIGHTNESS:
|
||||
if (command instanceof PercentType) {
|
||||
if (Objects.equals(((PercentType) command).as(OnOffType.class), OnOffType.OFF)) {
|
||||
sResp = sendToBulb("off", null, null, null);
|
||||
} else {
|
||||
if (lastMode.equals(MONO)) {
|
||||
String mono = convertPercentageToMyStromCT(lastColorTemperature) + ";"
|
||||
+ command.toString();
|
||||
sResp = sendToBulb("on", mono, null, MONO);
|
||||
} else {
|
||||
String hsv = lastColor.getHue().intValue() + ";" + lastColor.getSaturation() + ";"
|
||||
+ command.toString();
|
||||
sResp = sendToBulb("on", hsv, null, HSV);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
if (command instanceof PercentType) {
|
||||
String mono = convertPercentageToMyStromCT((PercentType) command) + ";"
|
||||
+ lastBrightness.toString();
|
||||
sResp = sendToBulb("on", mono, null, MONO);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_RAMP:
|
||||
if (command instanceof DecimalType) {
|
||||
sResp = sendToBulb(null, null, command.toString(), null);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
if (command instanceof StringType) {
|
||||
sResp = sendToBulb(null, null, null, command.toString());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (sResp != null) {
|
||||
Map<String, MyStromDeviceSpecificInfo> report = gson.fromJson(sResp, DEVICE_INFO_MAP_TYPE);
|
||||
if (report != null) {
|
||||
report.entrySet().stream().filter(e -> e.getKey().equals(mac)).findFirst()
|
||||
.ifPresent(info -> updateDevice(info.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MyStromException e) {
|
||||
logger.warn("Error while handling command {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Map<String, MyStromDeviceSpecificInfo> getReport() {
|
||||
try {
|
||||
String returnContent = sendHttpRequest(HttpMethod.GET, "/api/v1/device", null);
|
||||
Map<String, MyStromDeviceSpecificInfo> report = gson.fromJson(returnContent, DEVICE_INFO_MAP_TYPE);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return report;
|
||||
} catch (MyStromException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pollDevice() {
|
||||
Map<String, MyStromDeviceSpecificInfo> report = cache.getValue();
|
||||
if (report != null) {
|
||||
report.entrySet().stream().filter(e -> e.getKey().equals(mac)).findFirst()
|
||||
.ifPresent(info -> updateDevice(info.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDevice(@Nullable MyStromBulbResponse deviceInfo) {
|
||||
if (deviceInfo != null) {
|
||||
updateState(CHANNEL_SWITCH, deviceInfo.on ? OnOffType.ON : OnOffType.OFF);
|
||||
updateState(CHANNEL_RAMP, QuantityType.valueOf(deviceInfo.ramp, MetricPrefix.MILLI(SECOND)));
|
||||
if (deviceInfo instanceof MyStromDeviceSpecificInfo) {
|
||||
updateState(CHANNEL_POWER, QuantityType.valueOf(((MyStromDeviceSpecificInfo) deviceInfo).power, WATT));
|
||||
}
|
||||
if (deviceInfo.on) {
|
||||
try {
|
||||
lastMode = deviceInfo.mode;
|
||||
long numSemicolon = deviceInfo.color.chars().filter(c -> c == ';').count();
|
||||
if (numSemicolon == 1 && deviceInfo.mode.equals(MONO)) {
|
||||
String[] xy = deviceInfo.color.split(";");
|
||||
lastColorTemperature = new PercentType(convertMyStromCTToPercentage(xy[0]));
|
||||
lastBrightness = PercentType.valueOf(xy[1]);
|
||||
lastColor = new HSBType(lastColor.getHue() + ",0," + lastBrightness);
|
||||
updateState(CHANNEL_COLOR_TEMPERATURE, lastColorTemperature);
|
||||
} else if (numSemicolon == 2 && deviceInfo.mode.equals(HSV)) {
|
||||
lastColor = HSBType.valueOf(deviceInfo.color.replaceAll(";", ","));
|
||||
lastBrightness = lastColor.getBrightness();
|
||||
} else if (!deviceInfo.color.equals("") && deviceInfo.mode.equals(RGB)) {
|
||||
int r = Integer.parseInt(deviceInfo.color.substring(2, 4), 16);
|
||||
int g = Integer.parseInt(deviceInfo.color.substring(4, 6), 16);
|
||||
int b = Integer.parseInt(deviceInfo.color.substring(6, 8), 16);
|
||||
lastColor = HSBType.fromRGB(r, g, b);
|
||||
lastBrightness = lastColor.getBrightness();
|
||||
}
|
||||
updateState(CHANNEL_COLOR, lastColor);
|
||||
updateState(CHANNEL_BRIGHTNESS, lastBrightness);
|
||||
updateState(CHANNEL_MODE, StringType.valueOf(lastMode));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Error while updating {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL and a set parameters, send a HTTP POST request to the URL location
|
||||
* created by the URL and parameters.
|
||||
*
|
||||
* @param action The action we want to take (on,off or toggle)
|
||||
* @param color The color we set the bulb to (When using RGBW mode the first two hex numbers are used for the
|
||||
* white channel! hsv is of form <UINT 0..360>;<UINT 0..100>;<UINT 0..100>)
|
||||
* @param ramp Transition time from the light’s current state to the new state. [ms]
|
||||
* @param mode The color mode we want the Bulb to set to (rgb or hsv or mono)
|
||||
* @return String contents of the response for the GET request.
|
||||
* @throws MyStromException Throws on communication error
|
||||
*/
|
||||
private String sendToBulb(@Nullable String action, @Nullable String color, @Nullable String ramp,
|
||||
@Nullable String mode) throws MyStromException {
|
||||
Fields fields = new Fields();
|
||||
if (action != null) {
|
||||
fields.put("action", action);
|
||||
}
|
||||
if (color != null) {
|
||||
fields.put("color", color);
|
||||
}
|
||||
if (ramp != null) {
|
||||
fields.put("ramp", ramp);
|
||||
}
|
||||
if (mode != null) {
|
||||
fields.put("mode", mode);
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(fields.getSize() * 32);
|
||||
for (Fields.Field field : fields) {
|
||||
for (String value : field.getValues()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
builder.append(field.getName()).append("=").append(value);
|
||||
}
|
||||
}
|
||||
return sendHttpRequest(HttpMethod.POST, "/api/v1/device/" + mac, builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the color temperature from myStrom (1-18) to openHAB (percentage)
|
||||
*
|
||||
* @param ctValue Color temperature in myStrom: "1" = warm to "18" = cold.
|
||||
* @return Color temperature (0-100%). 0% is the coldest setting.
|
||||
* @throws NumberFormatException if the argument is not an integer
|
||||
*/
|
||||
private int convertMyStromCTToPercentage(String ctValue) throws NumberFormatException {
|
||||
int ct = Integer.parseInt(ctValue);
|
||||
return Math.round((18 - limitColorTemperature(ct)) / 17F * 100F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the color temperature from openHAB (percentage) to myStrom (1-18)
|
||||
*
|
||||
* @param colorTemperature Color temperature from openHab. 0 = coldest, 100 = warmest
|
||||
* @return Color temperature from myStrom. 1 = warmest, 18 = coldest
|
||||
*/
|
||||
private String convertPercentageToMyStromCT(PercentType colorTemperature) {
|
||||
int ct = 18 - Math.round(colorTemperature.floatValue() * 17F / 100F);
|
||||
return Integer.toString(limitColorTemperature(ct));
|
||||
}
|
||||
|
||||
private int limitColorTemperature(int colorTemperature) {
|
||||
return Math.max(1, Math.min(colorTemperature, 18));
|
||||
}
|
||||
|
||||
private static class MyStromBulbResponse {
|
||||
public boolean on;
|
||||
public String color = "";
|
||||
public String mode = "";
|
||||
public long ramp;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MyStromBulbResponse{" + "on=" + on + ", color='" + color + '\'' + ", mode='" + mode + '\''
|
||||
+ ", ramp=" + ramp + '}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyStromDeviceSpecificInfo extends MyStromBulbResponse {
|
||||
public double power;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.mystrom.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link MyStromDeviceInfo} class contains fields mapping thing thing properties
|
||||
*
|
||||
* @author Frederic Chastagnol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyStromDeviceInfo {
|
||||
public String version = "";
|
||||
public String mac = "";
|
||||
public long type;
|
||||
public String ssid = "";
|
||||
public String ip = "";
|
||||
public String mask = "";
|
||||
public String gw = "";
|
||||
public String dns = "";
|
||||
@SerializedName("static")
|
||||
public boolean staticState = false;
|
||||
public boolean connected = false;
|
||||
}
|
@ -12,9 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.mystrom.internal;
|
||||
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.THING_TYPE_BULB;
|
||||
import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.THING_TYPE_PLUG;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -34,14 +34,15 @@ import org.osgi.service.component.annotations.Reference;
|
||||
* handlers.
|
||||
*
|
||||
* @author Paul Frank - Initial contribution
|
||||
* @author Frederic Chastagnol - Add support for myStrom bulb
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mystrom", service = ThingHandlerFactory.class)
|
||||
public class MyStromHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_PLUG);
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLUG, THING_TYPE_BULB);
|
||||
|
||||
private HttpClientFactory httpClientFactory;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public MyStromHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
@ -58,7 +59,9 @@ public class MyStromHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_PLUG.equals(thingTypeUID)) {
|
||||
return new MyStromHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||
return new MyStromPlugHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||
} else if (THING_TYPE_BULB.equals(thingTypeUID)) {
|
||||
return new MyStromBulbHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -19,15 +19,11 @@ import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||
import static org.openhab.core.library.unit.Units.WATT;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -36,22 +32,20 @@ import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link MyStromHandler} is responsible for handling commands, which are
|
||||
* The {@link MyStromPlugHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Paul Frank - Initial contribution
|
||||
* @author Frederic Chastagnol - Extends from new abstract class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyStromHandler extends BaseThingHandler {
|
||||
public class MyStromPlugHandler extends AbstractMyStromHandler {
|
||||
|
||||
private static class MyStromReport {
|
||||
|
||||
@ -60,22 +54,12 @@ public class MyStromHandler extends BaseThingHandler {
|
||||
public float temperature;
|
||||
}
|
||||
|
||||
private static final int HTTP_OK_CODE = 200;
|
||||
private static final String COMMUNICATION_ERROR = "Error while communicating to the myStrom plug: ";
|
||||
private static final String HTTP_REQUEST_URL_PREFIX = "http://";
|
||||
private final Logger logger = LoggerFactory.getLogger(MyStromPlugHandler.class);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyStromHandler.class);
|
||||
private final ExpiringCache<MyStromReport> cache = new ExpiringCache<>(Duration.ofSeconds(3), this::getReport);
|
||||
|
||||
private HttpClient httpClient;
|
||||
private String hostname = "";
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private ExpiringCache<MyStromReport> cache = new ExpiringCache<>(Duration.ofSeconds(3), this::getReport);
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public MyStromHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
public MyStromPlugHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,7 +69,7 @@ public class MyStromHandler extends BaseThingHandler {
|
||||
pollDevice();
|
||||
} else {
|
||||
if (command instanceof OnOffType && CHANNEL_SWITCH.equals(channelUID.getId())) {
|
||||
sendHttpGet("relay?state=" + (command == OnOffType.ON ? "1" : "0"));
|
||||
sendHttpRequest(HttpMethod.GET, "/relay?state=" + (command == OnOffType.ON ? "1" : "0"), null);
|
||||
scheduler.schedule(this::pollDevice, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@ -96,7 +80,7 @@ public class MyStromHandler extends BaseThingHandler {
|
||||
|
||||
private @Nullable MyStromReport getReport() {
|
||||
try {
|
||||
String returnContent = sendHttpGet("report");
|
||||
String returnContent = sendHttpRequest(HttpMethod.GET, "/report", null);
|
||||
MyStromReport report = gson.fromJson(returnContent, MyStromReport.class);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return report;
|
||||
@ -106,7 +90,8 @@ public class MyStromHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void pollDevice() {
|
||||
@Override
|
||||
protected void pollDevice() {
|
||||
MyStromReport report = cache.getValue();
|
||||
if (report != null) {
|
||||
updateState(CHANNEL_SWITCH, report.relay ? OnOffType.ON : OnOffType.OFF);
|
||||
@ -114,46 +99,4 @@ public class MyStromHandler extends BaseThingHandler {
|
||||
updateState(CHANNEL_TEMPERATURE, QuantityType.valueOf(report.temperature, CELSIUS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
MyStromConfiguration config = getConfigAs(MyStromConfiguration.class);
|
||||
this.hostname = HTTP_REQUEST_URL_PREFIX + config.hostname;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevice, 0, config.refresh, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL and a set parameters, send a HTTP GET request to the URL location
|
||||
* created by the URL and parameters.
|
||||
*
|
||||
* @param url The URL to send a GET request to.
|
||||
* @return String contents of the response for the GET request.
|
||||
* @throws Exception
|
||||
*/
|
||||
public String sendHttpGet(String action) throws MyStromException {
|
||||
String url = hostname + "/" + action;
|
||||
ContentResponse response = null;
|
||||
try {
|
||||
response = httpClient.newRequest(url).timeout(10, TimeUnit.SECONDS).method(HttpMethod.GET).send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new MyStromException(COMMUNICATION_ERROR + e.getMessage());
|
||||
}
|
||||
|
||||
if (response.getStatus() != HTTP_OK_CODE) {
|
||||
throw new MyStromException(
|
||||
"Error sending HTTP GET request to " + url + ". Got response code: " + response.getStatus());
|
||||
}
|
||||
return response.getContentAsString();
|
||||
}
|
||||
}
|
@ -14,6 +14,21 @@
|
||||
<channel id="temperature" typeId="temperature-channel"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="mac"/>
|
||||
<property name="version"/>
|
||||
<property name="type"/>
|
||||
<property name="ssid"/>
|
||||
<property name="ip"/>
|
||||
<property name="mask"/>
|
||||
<property name="gw"/>
|
||||
<property name="dns"/>
|
||||
<property name="static"/>
|
||||
<property name="connected"/>
|
||||
</properties>
|
||||
|
||||
<representation-property>mac</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text">
|
||||
<label>Hostname</label>
|
||||
@ -30,11 +45,59 @@
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="mystrombulb">
|
||||
<label>myStrom Bulb</label>
|
||||
<description>Controls the myStrom bulb</description>
|
||||
|
||||
<channels>
|
||||
<channel id="switch" typeId="system.power"/>
|
||||
<channel id="power" typeId="power-channel"/>
|
||||
<channel id="color" typeId="system.color"/>
|
||||
<channel id="colorTemperature" typeId="system.color-temperature"/>
|
||||
<channel id="brightness" typeId="system.brightness"/>
|
||||
<channel id="ramp" typeId="ramp-channel"/>
|
||||
<channel id="mode" typeId="mode-channel"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="mac"/>
|
||||
<property name="version"/>
|
||||
<property name="type"/>
|
||||
<property name="ssid"/>
|
||||
<property name="ip"/>
|
||||
<property name="mask"/>
|
||||
<property name="gw"/>
|
||||
<property name="dns"/>
|
||||
<property name="static"/>
|
||||
<property name="connected"/>
|
||||
</properties>
|
||||
|
||||
<representation-property>mac</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text">
|
||||
<label>Hostname</label>
|
||||
<description>The host name or IP address of the myStrom bulb.</description>
|
||||
<context>network-address</context>
|
||||
<default>localhost</default>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s" min="1">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in seconds.</description>
|
||||
<default>10</default>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
|
||||
<channel-type id="power-channel">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power Consumption</label>
|
||||
<description>The current power delivered by the plug</description>
|
||||
<state readOnly="true"/>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature-channel">
|
||||
@ -43,4 +106,25 @@
|
||||
<description>The current temperature at the plug</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ramp-channel">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Ramp</label>
|
||||
<description>Transition time from the light’s current state to the new state.</description>
|
||||
<state pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mode-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Mode</label>
|
||||
<description>The color mode we want the Bulb to set to</description>
|
||||
<command>
|
||||
<options>
|
||||
<option value="rgb">RGB</option>
|
||||
<option value="hsv">HSB (HSV)</option>
|
||||
<option value="mono">MONO</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
Loading…
Reference in New Issue
Block a user