2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.homematic.internal.communicator.virtual;
15 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
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;
24 import java.util.Objects;
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;
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.
40 * @author Gerhard Riegler - Initial contribution
43 public class DisplayTextVirtualDatapoint extends AbstractVirtualDatapointHandler {
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";
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";
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";
66 private static Map<String, String> replaceMap = new HashMap<>();
68 // replace special chars while encoding
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");
81 * Available text colors.
92 private final String code;
94 private Color(String code) {
98 protected String getCode() {
103 * Returns the color code.
105 public static String getCode(String name) {
107 return valueOf(name).getCode();
108 } catch (Exception ex) {
128 SIGNAL_GREEN("0x89"),
129 SIGNAL_YELLOW("0x8a"),
132 private final String code;
134 private Icon(String code) {
138 protected String getCode() {
143 * Returns the icon code.
145 public static String getCode(String name) {
147 return valueOf(name).getCode();
148 } catch (Exception ex) {
155 * Available Beeper codes.
157 private enum Beeper {
161 LONG_SHORT_SHORT("0xc3"),
166 private final String code;
168 private Beeper(String code) {
172 protected String getCode() {
177 * Returns the beeper code.
179 public static String getCode(String name) {
181 return valueOf(name).getCode();
182 } catch (Exception ex) {
183 return OFF.getCode();
189 * Available LED colors.
197 private final String code;
199 private Led(String code) {
203 protected String getCode() {
208 * Returns the LED code.
210 public static String getCode(String name) {
212 return valueOf(name).getCode();
213 } catch (Exception ex) {
214 return OFF.getCode();
220 public String getName() {
221 return DATAPOINT_NAME_DISPLAY_SUBMIT;
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,
233 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_ICON + i,
236 if (!isEpDisplay(device)) {
237 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_COLOR + i,
241 if (isEpDisplay(device)) {
242 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPER,
244 HmDatapoint bc = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPCOUNT,
245 HmValueType.INTEGER, 1, false);
248 HmDatapoint bd = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPINTERVAL,
249 HmValueType.INTEGER, 1, false);
253 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LED, Led.class);
255 addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_SUBMIT, HmValueType.BOOL, false,
263 * Adds a Datapoint to the device with the values of the given enum.
265 private void addEnumDisplayDatapoint(HmDevice device, int channelNumber, String datapointName,
266 Class<? extends Enum<?>> e) {
267 HmDatapoint dpEnum = addDatapoint(device, channelNumber, datapointName, HmValueType.ENUM, null, false);
268 dpEnum.setOptions(getEnumNames(e));
269 dpEnum.setMinValue(0);
270 dpEnum.setMaxValue(e.getEnumConstants().length);
274 * Returns a string array with all the constants in the Enum.
276 private String[] getEnumNames(Class<? extends Enum<?>> e) {
277 return Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", ");
281 * Returns the number of lines of the display.
283 private int getLineCount(HmDevice device) {
284 return (DEVICE_TYPE_STATUS_DISPLAY.equals(device.getType()) ? 6 : 3);
288 * Returns true, if the display is a EP display.
290 private boolean isEpDisplay(HmDevice device) {
291 return DEVICE_TYPE_EP_STATUS_DISPLAY.equals(device.getType());
295 * Returns true, if the device is a supported display.
297 private boolean isDisplay(HmDevice device) {
298 return device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device);
302 public boolean canHandleCommand(HmDatapoint dp, Object value) {
303 HmDevice device = dp.getChannel().getDevice();
304 return (device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device))
305 && (getName().equals(dp.getName()) || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_LINE)
306 || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_COLOR)
307 || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_ICON)
308 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_LED)
309 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPER)
310 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPCOUNT)
311 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPINTERVAL));
315 public void handleCommand(VirtualGateway gateway, HmDatapoint dp, HmDatapointConfig dpConfig, Object value)
316 throws IOException, HomematicClientException {
319 if (DATAPOINT_NAME_DISPLAY_SUBMIT.equals(dp.getName()) && MiscUtils.isTrueValue(dp.getValue())) {
320 HmChannel channel = dp.getChannel();
321 boolean isEp = isEpDisplay(channel.getDevice());
323 List<String> message = new ArrayList<>();
329 for (int i = 1; i <= getLineCount(channel.getDevice()); i++) {
330 String line = Objects.toString(
331 channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LINE + i).getValue(), "");
332 if (line.isEmpty()) {
336 message.add(encodeText(line));
338 String color = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_COLOR + i)
341 String colorCode = Color.getCode(color);
342 message.add(colorCode == null || colorCode.isBlank() ? Color.WHITE.getCode() : colorCode);
344 String icon = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_ICON + i)
346 String iconCode = Icon.getCode(icon);
347 if (iconCode != null && !iconCode.isBlank()) {
349 message.add(iconCode);
355 String beeper = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPER)
357 message.add(BEEPER_START);
358 message.add(Beeper.getCode(beeper));
359 message.add(BEEPER_END);
360 // set number of beeps
362 encodeBeepCount(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPCOUNT)));
363 message.add(BEEPCOUNT_END);
364 // set interval between two beeps
365 message.add(encodeBeepInterval(
366 channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPINTERVAL)));
367 message.add(BEEPINTERVAL_END);
368 // LED value must always set (same as beeps)
369 String led = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LED).getOptionValue();
370 message.add(Led.getCode(led));
375 gateway.sendDatapoint(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_SUBMIT),
376 new HmDatapointConfig(), String.join(",", message), null);
381 * Encodes the beep count value. Allowed values 0 - 15, where 0 means infinite.
383 private String encodeBeepCount(HmDatapoint dp) {
384 int counts = (int) (Number) dp.getValue();
388 return String.format("0x%02x", 207 + counts);
392 * Encodes the beep interval value in 10 s steps. Allowed values 10 - 160.
394 private String encodeBeepInterval(HmDatapoint dp) {
395 int interval = (int) (Number) dp.getValue();
396 return String.format("0x%02x", 224 + ((interval - 1) / 10));
400 * Encodes the given text for the display.
402 private String encodeText(String text) {
403 final byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1);
404 StringBuilder sb = new StringBuilder(bytes.length * 2);
405 for (byte b : bytes) {
407 String hexValue = String.format("%02x", b);
408 sb.append(replaceMap.containsKey(hexValue) ? replaceMap.get(hexValue) : hexValue);
411 sb.setLength(sb.length() - 1);
412 return sb.toString();