| 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
| 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
--- /dev/null
+/**
+ * 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();
+}
* used across the whole binding.
*
* @author Paul Frank - Initial contribution
+ * @author Frederic Chastagnol - Add constants for myStrom bulb support
*/
@NonNullByDefault
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";
}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
+++ /dev/null
-/**
- * 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_POWER;
-import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_SWITCH;
-import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_TEMPERATURE;
-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;
-import org.openhab.core.library.types.QuantityType;
-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
- * sent to one of the channels.
- *
- * @author Paul Frank - Initial contribution
- */
-@NonNullByDefault
-public class MyStromHandler extends BaseThingHandler {
-
- private static class MyStromReport {
-
- public float power;
- public boolean relay;
- 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(MyStromHandler.class);
-
- 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;
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- try {
- if (command instanceof RefreshType) {
- pollDevice();
- } else {
- if (command instanceof OnOffType && CHANNEL_SWITCH.equals(channelUID.getId())) {
- sendHttpGet("relay?state=" + (command == OnOffType.ON ? "1" : "0"));
- scheduler.schedule(this::pollDevice, 500, TimeUnit.MILLISECONDS);
- }
- }
- } catch (MyStromException e) {
- logger.warn("Error while handling command {}", e.getMessage());
- }
- }
-
- private @Nullable MyStromReport getReport() {
- try {
- String returnContent = sendHttpGet("report");
- MyStromReport report = gson.fromJson(returnContent, MyStromReport.class);
- updateStatus(ThingStatus.ONLINE);
- return report;
- } catch (MyStromException e) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
- return null;
- }
- }
-
- private void pollDevice() {
- MyStromReport report = cache.getValue();
- if (report != null) {
- updateState(CHANNEL_SWITCH, report.relay ? OnOffType.ON : OnOffType.OFF);
- updateState(CHANNEL_POWER, QuantityType.valueOf(report.power, WATT));
- 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();
- }
-}
*/
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;
* 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) {
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;
--- /dev/null
+/**
+ * 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_POWER;
+import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_SWITCH;
+import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_TEMPERATURE;
+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.TimeUnit;
+
+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.openhab.core.cache.ExpiringCache;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+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;
+
+/**
+ * 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 MyStromPlugHandler extends AbstractMyStromHandler {
+
+ private static class MyStromReport {
+
+ public float power;
+ public boolean relay;
+ public float temperature;
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(MyStromPlugHandler.class);
+
+ private final ExpiringCache<MyStromReport> cache = new ExpiringCache<>(Duration.ofSeconds(3), this::getReport);
+
+ public MyStromPlugHandler(Thing thing, HttpClient httpClient) {
+ super(thing, httpClient);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ try {
+ if (command instanceof RefreshType) {
+ pollDevice();
+ } else {
+ if (command instanceof OnOffType && CHANNEL_SWITCH.equals(channelUID.getId())) {
+ sendHttpRequest(HttpMethod.GET, "/relay?state=" + (command == OnOffType.ON ? "1" : "0"), null);
+ scheduler.schedule(this::pollDevice, 500, TimeUnit.MILLISECONDS);
+ }
+ }
+ } catch (MyStromException e) {
+ logger.warn("Error while handling command {}", e.getMessage());
+ }
+ }
+
+ private @Nullable MyStromReport getReport() {
+ try {
+ String returnContent = sendHttpRequest(HttpMethod.GET, "/report", null);
+ MyStromReport report = gson.fromJson(returnContent, MyStromReport.class);
+ updateStatus(ThingStatus.ONLINE);
+ return report;
+ } catch (MyStromException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ protected void pollDevice() {
+ MyStromReport report = cache.getValue();
+ if (report != null) {
+ updateState(CHANNEL_SWITCH, report.relay ? OnOffType.ON : OnOffType.OFF);
+ updateState(CHANNEL_POWER, QuantityType.valueOf(report.power, WATT));
+ updateState(CHANNEL_TEMPERATURE, QuantityType.valueOf(report.temperature, CELSIUS));
+ }
+ }
+}
<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>
</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">
<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>