2 * Copyright (c) 2010-2023 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);
252 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LED, Led.class);
254 addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_SUBMIT, HmValueType.BOOL, false,
262 * Adds a Datapoint to the device with the values of the given enum.
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);
273 * Returns a string array with all the constants in the Enum.
275 private String[] getEnumNames(Class<? extends Enum<?>> e) {
276 return Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", ");
280 * Returns the number of lines of the display.
282 private int getLineCount(HmDevice device) {
283 return (DEVICE_TYPE_STATUS_DISPLAY.equals(device.getType()) ? 6 : 3);
287 * Returns true, if the display is an EP display.
289 private boolean isEpDisplay(HmDevice device) {
290 return DEVICE_TYPE_EP_STATUS_DISPLAY.equals(device.getType());
294 * Returns true, if the device is a supported display.
296 private boolean isDisplay(HmDevice device) {
297 return device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device);
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));
314 public void handleCommand(VirtualGateway gateway, HmDatapoint dp, HmDatapointConfig dpConfig, Object value)
315 throws IOException, HomematicClientException {
318 if (DATAPOINT_NAME_DISPLAY_SUBMIT.equals(dp.getName()) && MiscUtils.isTrueValue(dp.getValue())) {
319 HmChannel channel = dp.getChannel();
320 boolean isEp = isEpDisplay(channel.getDevice());
322 List<String> message = new ArrayList<>();
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()) {
335 message.add(encodeText(line));
337 String color = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_COLOR + i)
340 String colorCode = Color.getCode(color);
341 message.add(colorCode == null || colorCode.isBlank() ? Color.WHITE.getCode() : colorCode);
343 String icon = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_ICON + i)
345 String iconCode = Icon.getCode(icon);
346 if (iconCode != null && !iconCode.isBlank()) {
348 message.add(iconCode);
354 String beeper = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPER)
356 message.add(BEEPER_START);
357 message.add(Beeper.getCode(beeper));
358 message.add(BEEPER_END);
359 // set number of beeps
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));
374 gateway.sendDatapoint(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_SUBMIT),
375 new HmDatapointConfig(), String.join(",", message), null);
380 * Encodes the beep count value. Allowed values 0 - 15, where 0 means infinite.
382 private String encodeBeepCount(HmDatapoint dp) {
383 int counts = (int) (Number) dp.getValue();
387 return String.format("0x%02x", 207 + counts);
391 * Encodes the beep interval value in 10 s steps. Allowed values 10 - 160.
393 private String encodeBeepInterval(HmDatapoint dp) {
394 int interval = (int) (Number) dp.getValue();
395 return String.format("0x%02x", 224 + ((interval - 1) / 10));
399 * Encodes the given text for the display.
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) {
406 String hexValue = String.format("%02x", b);
407 sb.append(replaceMap.containsKey(hexValue) ? replaceMap.get(hexValue) : hexValue);
410 sb.setLength(sb.length() - 1);
411 return sb.toString();