]> git.basschouten.com Git - openhab-addons.git/blob
aad1dad8e15f5fe96cd612a6dae92e3136bcaed9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mystrom.internal;
14
15 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_BRIGHTNESS;
16 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_COLOR;
17 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_COLOR_TEMPERATURE;
18 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_MODE;
19 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_POWER;
20 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_RAMP;
21 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.CHANNEL_SWITCH;
22 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.HSV;
23 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.MONO;
24 import static org.openhab.binding.mystrom.internal.MyStromBindingConstants.RGB;
25 import static org.openhab.core.library.unit.Units.SECOND;
26 import static org.openhab.core.library.unit.Units.WATT;
27
28 import java.lang.reflect.Type;
29 import java.time.Duration;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.Objects;
33
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.eclipse.jetty.client.HttpClient;
37 import org.eclipse.jetty.http.HttpMethod;
38 import org.eclipse.jetty.util.Fields;
39 import org.openhab.core.cache.ExpiringCache;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.HSBType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.PercentType;
44 import org.openhab.core.library.types.QuantityType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.library.unit.MetricPrefix;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import com.google.gson.reflect.TypeToken;
57
58 /**
59  * The {@link MyStromBulbHandler} is responsible for handling commands, which are
60  * sent to one of the channels.
61  *
62  * @author Frederic Chastagnol - Initial contribution
63  */
64 @NonNullByDefault
65 public class MyStromBulbHandler extends AbstractMyStromHandler {
66
67     private static final Type DEVICE_INFO_MAP_TYPE = new TypeToken<HashMap<String, MyStromDeviceSpecificInfo>>() {
68     }.getType();
69
70     private final Logger logger = LoggerFactory.getLogger(MyStromBulbHandler.class);
71
72     private final ExpiringCache<Map<String, MyStromDeviceSpecificInfo>> cache = new ExpiringCache<>(
73             Duration.ofSeconds(3), this::getReport);
74
75     private PercentType lastBrightness = PercentType.HUNDRED;
76     private PercentType lastColorTemperature = new PercentType(50);
77     private String lastMode = MONO;
78     private HSBType lastColor = HSBType.WHITE;
79
80     public MyStromBulbHandler(Thing thing, HttpClient httpClient) {
81         super(thing, httpClient);
82     }
83
84     @Override
85     public void handleCommand(ChannelUID channelUID, Command command) {
86         try {
87             if (command instanceof RefreshType) {
88                 pollDevice();
89             } else {
90                 String sResp = null;
91                 switch (channelUID.getId()) {
92                     case CHANNEL_SWITCH:
93                         if (command instanceof OnOffType) {
94                             sResp = sendToBulb(command == OnOffType.ON ? "on" : "off", null, null, null);
95                         }
96                         break;
97                     case CHANNEL_COLOR:
98                         if (command instanceof HSBType) {
99                             if (Objects.equals(((HSBType) command).as(OnOffType.class), OnOffType.OFF)) {
100                                 sResp = sendToBulb("off", null, null, null);
101                             } else {
102                                 String hsv = command.toString().replaceAll(",", ";");
103                                 sResp = sendToBulb("on", hsv, null, HSV);
104                             }
105                         }
106                         break;
107                     case CHANNEL_BRIGHTNESS:
108                         if (command instanceof PercentType) {
109                             if (Objects.equals(((PercentType) command).as(OnOffType.class), OnOffType.OFF)) {
110                                 sResp = sendToBulb("off", null, null, null);
111                             } else {
112                                 if (lastMode.equals(MONO)) {
113                                     String mono = convertPercentageToMyStromCT(lastColorTemperature) + ";"
114                                             + command.toString();
115                                     sResp = sendToBulb("on", mono, null, MONO);
116                                 } else {
117                                     String hsv = lastColor.getHue().intValue() + ";" + lastColor.getSaturation() + ";"
118                                             + command.toString();
119                                     sResp = sendToBulb("on", hsv, null, HSV);
120                                 }
121                             }
122                         }
123                         break;
124                     case CHANNEL_COLOR_TEMPERATURE:
125                         if (command instanceof PercentType) {
126                             String mono = convertPercentageToMyStromCT((PercentType) command) + ";"
127                                     + lastBrightness.toString();
128                             sResp = sendToBulb("on", mono, null, MONO);
129                         }
130                         break;
131                     case CHANNEL_RAMP:
132                         if (command instanceof DecimalType) {
133                             sResp = sendToBulb(null, null, command.toString(), null);
134                         }
135                         break;
136                     case CHANNEL_MODE:
137                         if (command instanceof StringType) {
138                             sResp = sendToBulb(null, null, null, command.toString());
139                         }
140                         break;
141                     default:
142                 }
143
144                 if (sResp != null) {
145                     Map<String, MyStromDeviceSpecificInfo> report = gson.fromJson(sResp, DEVICE_INFO_MAP_TYPE);
146                     if (report != null) {
147                         report.entrySet().stream().filter(e -> e.getKey().equals(mac)).findFirst()
148                                 .ifPresent(info -> updateDevice(info.getValue()));
149                     }
150                 }
151             }
152         } catch (MyStromException e) {
153             logger.warn("Error while handling command {}", e.getMessage());
154         }
155     }
156
157     @Override
158     protected void checkRequiredInfo() throws MyStromException {
159         if (mac.isBlank()) {
160             throw new MyStromException("Cannot retrieve MAC info from myStrom device " + getThing().getUID());
161         }
162     }
163
164     private @Nullable Map<String, MyStromDeviceSpecificInfo> getReport() {
165         try {
166             String returnContent = sendHttpRequest(HttpMethod.GET, "/api/v1/device", null);
167             Map<String, MyStromDeviceSpecificInfo> report = gson.fromJson(returnContent, DEVICE_INFO_MAP_TYPE);
168             updateStatus(ThingStatus.ONLINE);
169             return report;
170         } catch (MyStromException e) {
171             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
172             return null;
173         }
174     }
175
176     @Override
177     protected void pollDevice() {
178         Map<String, MyStromDeviceSpecificInfo> report = cache.getValue();
179         if (report != null) {
180             report.entrySet().stream().filter(e -> e.getKey().equals(mac)).findFirst()
181                     .ifPresent(info -> updateDevice(info.getValue()));
182         }
183     }
184
185     private void updateDevice(@Nullable MyStromBulbResponse deviceInfo) {
186         if (deviceInfo != null) {
187             updateState(CHANNEL_SWITCH, deviceInfo.on ? OnOffType.ON : OnOffType.OFF);
188             updateState(CHANNEL_RAMP, QuantityType.valueOf(deviceInfo.ramp, MetricPrefix.MILLI(SECOND)));
189             if (deviceInfo instanceof MyStromDeviceSpecificInfo) {
190                 updateState(CHANNEL_POWER, QuantityType.valueOf(((MyStromDeviceSpecificInfo) deviceInfo).power, WATT));
191             }
192             if (deviceInfo.on) {
193                 try {
194                     lastMode = deviceInfo.mode;
195                     long numSemicolon = deviceInfo.color.chars().filter(c -> c == ';').count();
196                     if (numSemicolon == 1 && deviceInfo.mode.equals(MONO)) {
197                         String[] xy = deviceInfo.color.split(";");
198                         lastColorTemperature = new PercentType(convertMyStromCTToPercentage(xy[0]));
199                         lastBrightness = PercentType.valueOf(xy[1]);
200                         lastColor = new HSBType(lastColor.getHue() + ",0," + lastBrightness);
201                         updateState(CHANNEL_COLOR_TEMPERATURE, lastColorTemperature);
202                     } else if (numSemicolon == 2 && deviceInfo.mode.equals(HSV)) {
203                         lastColor = HSBType.valueOf(deviceInfo.color.replaceAll(";", ","));
204                         lastBrightness = lastColor.getBrightness();
205                     } else if (!deviceInfo.color.equals("") && deviceInfo.mode.equals(RGB)) {
206                         int r = Integer.parseInt(deviceInfo.color.substring(2, 4), 16);
207                         int g = Integer.parseInt(deviceInfo.color.substring(4, 6), 16);
208                         int b = Integer.parseInt(deviceInfo.color.substring(6, 8), 16);
209                         lastColor = HSBType.fromRGB(r, g, b);
210                         lastBrightness = lastColor.getBrightness();
211                     }
212                     updateState(CHANNEL_COLOR, lastColor);
213                     updateState(CHANNEL_BRIGHTNESS, lastBrightness);
214                     updateState(CHANNEL_MODE, StringType.valueOf(lastMode));
215                 } catch (IllegalArgumentException e) {
216                     logger.warn("Error while updating {}", e.getMessage());
217                 }
218             }
219         }
220     }
221
222     /**
223      * Given a URL and a set parameters, send a HTTP POST request to the URL location
224      * created by the URL and parameters.
225      *
226      * @param action The action we want to take (on,off or toggle)
227      * @param color The color we set the bulb to (When using RGBW mode the first two hex numbers are used for the
228      *            white channel! hsv is of form <UINT 0..360>;<UINT 0..100>;<UINT 0..100>)
229      * @param ramp Transition time from the light’s current state to the new state. [ms]
230      * @param mode The color mode we want the Bulb to set to (rgb or hsv or mono)
231      * @return String contents of the response for the GET request.
232      * @throws MyStromException Throws on communication error
233      */
234     private String sendToBulb(@Nullable String action, @Nullable String color, @Nullable String ramp,
235             @Nullable String mode) throws MyStromException {
236         Fields fields = new Fields();
237         if (action != null) {
238             fields.put("action", action);
239         }
240         if (color != null) {
241             fields.put("color", color);
242         }
243         if (ramp != null) {
244             fields.put("ramp", ramp);
245         }
246         if (mode != null) {
247             fields.put("mode", mode);
248         }
249         StringBuilder builder = new StringBuilder(fields.getSize() * 32);
250         for (Fields.Field field : fields) {
251             for (String value : field.getValues()) {
252                 if (builder.length() > 0) {
253                     builder.append("&");
254                 }
255                 builder.append(field.getName()).append("=").append(value);
256             }
257         }
258         return sendHttpRequest(HttpMethod.POST, "/api/v1/device/" + mac, builder.toString());
259     }
260
261     /**
262      * Convert the color temperature from myStrom (1-18) to openHAB (percentage)
263      *
264      * @param ctValue Color temperature in myStrom: "1" = warm to "18" = cold.
265      * @return Color temperature (0-100%). 0% is the coldest setting.
266      * @throws NumberFormatException if the argument is not an integer
267      */
268     private int convertMyStromCTToPercentage(String ctValue) throws NumberFormatException {
269         int ct = Integer.parseInt(ctValue);
270         return Math.round((18 - limitColorTemperature(ct)) / 17F * 100F);
271     }
272
273     /**
274      * Convert the color temperature from openHAB (percentage) to myStrom (1-18)
275      *
276      * @param colorTemperature Color temperature from openHab. 0 = coldest, 100 = warmest
277      * @return Color temperature from myStrom. 1 = warmest, 18 = coldest
278      */
279     private String convertPercentageToMyStromCT(PercentType colorTemperature) {
280         int ct = 18 - Math.round(colorTemperature.floatValue() * 17F / 100F);
281         return Integer.toString(limitColorTemperature(ct));
282     }
283
284     private int limitColorTemperature(int colorTemperature) {
285         return Math.max(1, Math.min(colorTemperature, 18));
286     }
287
288     private static class MyStromBulbResponse {
289         public boolean on;
290         public String color = "";
291         public String mode = "";
292         public long ramp;
293
294         @Override
295         public String toString() {
296             return "MyStromBulbResponse{" + "on=" + on + ", color='" + color + '\'' + ", mode='" + mode + '\''
297                     + ", ramp=" + ramp + '}';
298         }
299     }
300
301     private static class MyStromDeviceSpecificInfo extends MyStromBulbResponse {
302         public double power;
303     }
304 }