]> git.basschouten.com Git - openhab-addons.git/blob
88ab86932e295f017d3c3bb9815cf4df35bc679e
[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.homematic.internal.communicator.virtual;
14
15 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
16
17 import java.io.IOException;
18 import java.nio.charset.StandardCharsets;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Objects;
25
26 import org.openhab.binding.homematic.internal.misc.HomematicClientException;
27 import org.openhab.binding.homematic.internal.misc.MiscUtils;
28 import org.openhab.binding.homematic.internal.model.HmChannel;
29 import org.openhab.binding.homematic.internal.model.HmDatapoint;
30 import org.openhab.binding.homematic.internal.model.HmDatapointConfig;
31 import org.openhab.binding.homematic.internal.model.HmDatapointInfo;
32 import org.openhab.binding.homematic.internal.model.HmDevice;
33 import org.openhab.binding.homematic.internal.model.HmParamsetType;
34 import org.openhab.binding.homematic.internal.model.HmValueType;
35
36 /**
37  * The {@link DisplayTextVirtualDatapoint} adds multiple virtual datapoints to the HM-Dis-WM55 and HM-Dis-EP-WM55
38  * devices to easily handle colored text, icons, the led and the beeper of the display.
39  *
40  * @author Gerhard Riegler - Initial contribution
41  */
42
43 public class DisplayTextVirtualDatapoint extends AbstractVirtualDatapointHandler {
44
45     private static final String DATAPOINT_NAME_DISPLAY_LINE = "DISPLAY_LINE_";
46     private static final String DATAPOINT_NAME_DISPLAY_COLOR = "DISPLAY_COLOR_";
47     private static final String DATAPOINT_NAME_DISPLAY_ICON = "DISPLAY_ICON_";
48     private static final String DATAPOINT_NAME_DISPLAY_LED = "DISPLAY_LED";
49     private static final String DATAPOINT_NAME_DISPLAY_BEEPER = "DISPLAY_BEEPER";
50     private static final String DATAPOINT_NAME_DISPLAY_BEEPCOUNT = "DISPLAY_BEEPCOUNT";
51     private static final String DATAPOINT_NAME_DISPLAY_BEEPINTERVAL = "DISPLAY_BEEPINTERVAL";
52     private static final String DATAPOINT_NAME_DISPLAY_SUBMIT = "DISPLAY_SUBMIT";
53
54     private static final String START = "0x02";
55     private static final String STOP = "0x03";
56     private static final String LINE = "0x12";
57     private static final String COLOR = "0x11";
58     private static final String LF = "0x0a";
59     private static final String ICON = "0x13";
60
61     private static final String BEEPER_START = "0x14";
62     private static final String BEEPER_END = "0x1c";
63     private static final String BEEPCOUNT_END = "0x1D";
64     private static final String BEEPINTERVAL_END = "0x16";
65
66     private static Map<String, String> replaceMap = new HashMap<>();
67
68     // replace special chars while encoding
69     static {
70         replaceMap.put("d6", "23");
71         replaceMap.put("dc", "24");
72         replaceMap.put("3d", "27");
73         replaceMap.put("c4", "5b");
74         replaceMap.put("df", "5f");
75         replaceMap.put("e4", "7b");
76         replaceMap.put("f6", "7c");
77         replaceMap.put("fc", "7d");
78     }
79
80     /**
81      * Available text colors.
82      */
83     private enum Color {
84         NONE(""),
85         WHITE("0x80"),
86         RED("0x81"),
87         ORANGE("0x82"),
88         YELLOW("0x83"),
89         GREEN("0x84"),
90         BLUE("0x85");
91
92         private final String code;
93
94         private Color(String code) {
95             this.code = code;
96         }
97
98         protected String getCode() {
99             return code;
100         }
101
102         /**
103          * Returns the color code.
104          */
105         public static String getCode(String name) {
106             try {
107                 return valueOf(name).getCode();
108             } catch (Exception ex) {
109                 return null;
110             }
111         }
112     }
113
114     /**
115      * Available icons.
116      */
117     private enum Icon {
118         NONE(""),
119         OFF("0x80"),
120         ON("0x81"),
121         OPEN("0x82"),
122         CLOSED("0x83"),
123         ERROR("0x84"),
124         OK("0x85"),
125         INFO("0x86"),
126         NEW_MESSAGE("0x87"),
127         SERVICE("0x88"),
128         SIGNAL_GREEN("0x89"),
129         SIGNAL_YELLOW("0x8a"),
130         SIGNAL_RED("0x8b");
131
132         private final String code;
133
134         private Icon(String code) {
135             this.code = code;
136         }
137
138         protected String getCode() {
139             return code;
140         }
141
142         /**
143          * Returns the icon code.
144          */
145         public static String getCode(String name) {
146             try {
147                 return valueOf(name).getCode();
148             } catch (Exception ex) {
149                 return null;
150             }
151         }
152     }
153
154     /**
155      * Available Beeper codes.
156      */
157     private enum Beeper {
158         OFF("0xc0"),
159         LONG_LONG("0xc1"),
160         LONG_SHORT("0xc2"),
161         LONG_SHORT_SHORT("0xc3"),
162         SHORT("0xc4"),
163         SHORT_SHORT("0xc5"),
164         LONG("0xc6");
165
166         private final String code;
167
168         private Beeper(String code) {
169             this.code = code;
170         }
171
172         protected String getCode() {
173             return code;
174         }
175
176         /**
177          * Returns the beeper code.
178          */
179         public static String getCode(String name) {
180             try {
181                 return valueOf(name).getCode();
182             } catch (Exception ex) {
183                 return OFF.getCode();
184             }
185         }
186     }
187
188     /**
189      * Available LED colors.
190      */
191     private enum Led {
192         OFF("0xf0"),
193         RED("0xf1"),
194         GREEN("0xf2"),
195         ORANGE("0xf3");
196
197         private final String code;
198
199         private Led(String code) {
200             this.code = code;
201         }
202
203         protected String getCode() {
204             return code;
205         }
206
207         /**
208          * Returns the LED code.
209          */
210         public static String getCode(String name) {
211             try {
212                 return valueOf(name).getCode();
213             } catch (Exception ex) {
214                 return OFF.getCode();
215             }
216         }
217     }
218
219     @Override
220     public String getName() {
221         return DATAPOINT_NAME_DISPLAY_SUBMIT;
222     }
223
224     @Override
225     public void initialize(HmDevice device) {
226         if (isDisplay(device)) {
227             for (HmChannel channel : device.getChannels()) {
228                 if (channel.hasDatapoint(new HmDatapointInfo(HmParamsetType.VALUES, channel, DATAPOINT_NAME_SUBMIT))) {
229                     for (int i = 1; i <= getLineCount(device); i++) {
230                         addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LINE + i, HmValueType.STRING,
231                                 null, false);
232
233                         addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_ICON + i,
234                                 Icon.class);
235
236                         if (!isEpDisplay(device)) {
237                             addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_COLOR + i,
238                                     Color.class);
239                         }
240                     }
241                     if (isEpDisplay(device)) {
242                         addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPER,
243                                 Beeper.class);
244                         HmDatapoint bc = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPCOUNT,
245                                 HmValueType.INTEGER, 1, false);
246                         bc.setMinValue(0);
247                         bc.setMaxValue(15);
248                         HmDatapoint bd = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPINTERVAL,
249                                 HmValueType.INTEGER, 1, false);
250                         bd.setMinValue(10);
251                         bd.setMaxValue(160);
252                         addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LED, Led.class);
253                     }
254                     addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_SUBMIT, HmValueType.BOOL, false,
255                             false);
256                 }
257             }
258         }
259     }
260
261     /**
262      * Adds a Datapoint to the device with the values of the given enum.
263      */
264     private void addEnumDisplayDatapoint(HmDevice device, int channelNumber, String datapointName,
265             Class<? extends Enum<?>> e) {
266         HmDatapoint dpEnum = addDatapoint(device, channelNumber, datapointName, HmValueType.ENUM, null, false);
267         dpEnum.setOptions(getEnumNames(e));
268         dpEnum.setMinValue(0);
269         dpEnum.setMaxValue(e.getEnumConstants().length);
270     }
271
272     /**
273      * Returns a string array with all the constants in the Enum.
274      */
275     private String[] getEnumNames(Class<? extends Enum<?>> e) {
276         return Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", ");
277     }
278
279     /**
280      * Returns the number of lines of the display.
281      */
282     private int getLineCount(HmDevice device) {
283         return (DEVICE_TYPE_STATUS_DISPLAY.equals(device.getType()) ? 6 : 3);
284     }
285
286     /**
287      * Returns true, if the display is an EP display.
288      */
289     private boolean isEpDisplay(HmDevice device) {
290         return DEVICE_TYPE_EP_STATUS_DISPLAY.equals(device.getType());
291     }
292
293     /**
294      * Returns true, if the device is a supported display.
295      */
296     private boolean isDisplay(HmDevice device) {
297         return device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device);
298     }
299
300     @Override
301     public boolean canHandleCommand(HmDatapoint dp, Object value) {
302         HmDevice device = dp.getChannel().getDevice();
303         return (device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device))
304                 && (getName().equals(dp.getName()) || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_LINE)
305                         || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_COLOR)
306                         || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_ICON)
307                         || dp.getName().equals(DATAPOINT_NAME_DISPLAY_LED)
308                         || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPER)
309                         || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPCOUNT)
310                         || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPINTERVAL));
311     }
312
313     @Override
314     public void handleCommand(VirtualGateway gateway, HmDatapoint dp, HmDatapointConfig dpConfig, Object value)
315             throws IOException, HomematicClientException {
316         dp.setValue(value);
317
318         if (DATAPOINT_NAME_DISPLAY_SUBMIT.equals(dp.getName()) && MiscUtils.isTrueValue(dp.getValue())) {
319             HmChannel channel = dp.getChannel();
320             boolean isEp = isEpDisplay(channel.getDevice());
321
322             List<String> message = new ArrayList<>();
323             message.add(START);
324             if (isEp) {
325                 message.add(LF);
326             }
327
328             for (int i = 1; i <= getLineCount(channel.getDevice()); i++) {
329                 String line = Objects.toString(
330                         channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LINE + i).getValue(), "");
331                 if (line.isEmpty()) {
332                     line = " ";
333                 }
334                 message.add(LINE);
335                 message.add(encodeText(line));
336                 if (!isEp) {
337                     String color = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_COLOR + i)
338                             .getOptionValue();
339                     message.add(COLOR);
340                     String colorCode = Color.getCode(color);
341                     message.add(colorCode == null || colorCode.isBlank() ? Color.WHITE.getCode() : colorCode);
342                 }
343                 String icon = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_ICON + i)
344                         .getOptionValue();
345                 String iconCode = Icon.getCode(icon);
346                 if (iconCode != null && !iconCode.isBlank()) {
347                     message.add(ICON);
348                     message.add(iconCode);
349                 }
350                 message.add(LF);
351             }
352
353             if (isEp) {
354                 String beeper = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPER)
355                         .getOptionValue();
356                 message.add(BEEPER_START);
357                 message.add(Beeper.getCode(beeper));
358                 message.add(BEEPER_END);
359                 // set number of beeps
360                 message.add(
361                         encodeBeepCount(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPCOUNT)));
362                 message.add(BEEPCOUNT_END);
363                 // set interval between two beeps
364                 message.add(encodeBeepInterval(
365                         channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPINTERVAL)));
366                 message.add(BEEPINTERVAL_END);
367                 // LED value must always set (same as beeps)
368                 String led = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LED).getOptionValue();
369                 message.add(Led.getCode(led));
370
371             }
372             message.add(STOP);
373
374             gateway.sendDatapoint(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_SUBMIT),
375                     new HmDatapointConfig(), String.join(",", message), null);
376         }
377     }
378
379     /**
380      * Encodes the beep count value. Allowed values 0 - 15, where 0 means infinite.
381      */
382     private String encodeBeepCount(HmDatapoint dp) {
383         int counts = (int) (Number) dp.getValue();
384         if (counts == 0) {
385             counts = 16;
386         }
387         return String.format("0x%02x", 207 + counts);
388     }
389
390     /**
391      * Encodes the beep interval value in 10 s steps. Allowed values 10 - 160.
392      */
393     private String encodeBeepInterval(HmDatapoint dp) {
394         int interval = (int) (Number) dp.getValue();
395         return String.format("0x%02x", 224 + ((interval - 1) / 10));
396     }
397
398     /**
399      * Encodes the given text for the display.
400      */
401     private String encodeText(String text) {
402         final byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1);
403         StringBuilder sb = new StringBuilder(bytes.length * 2);
404         for (byte b : bytes) {
405             sb.append("0x");
406             String hexValue = String.format("%02x", b);
407             sb.append(replaceMap.containsKey(hexValue) ? replaceMap.get(hexValue) : hexValue);
408             sb.append(",");
409         }
410         sb.setLength(sb.length() - 1);
411         return sb.toString();
412     }
413 }