2 * Copyright (c) 2010-2021 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;
25 import org.apache.commons.lang.ObjectUtils;
26 import org.apache.commons.lang.StringUtils;
27 import org.openhab.binding.homematic.internal.misc.HomematicClientException;
28 import org.openhab.binding.homematic.internal.misc.MiscUtils;
29 import org.openhab.binding.homematic.internal.model.HmChannel;
30 import org.openhab.binding.homematic.internal.model.HmDatapoint;
31 import org.openhab.binding.homematic.internal.model.HmDatapointConfig;
32 import org.openhab.binding.homematic.internal.model.HmDatapointInfo;
33 import org.openhab.binding.homematic.internal.model.HmDevice;
34 import org.openhab.binding.homematic.internal.model.HmParamsetType;
35 import org.openhab.binding.homematic.internal.model.HmValueType;
38 * The {@link DisplayTextVirtualDatapoint} adds multiple virtual datapoints to the HM-Dis-WM55 and HM-Dis-EP-WM55
39 * devices to easily handle colored text, icons, the led and the beeper of the display.
41 * @author Gerhard Riegler - Initial contribution
44 public class DisplayTextVirtualDatapoint extends AbstractVirtualDatapointHandler {
46 private static final String DATAPOINT_NAME_DISPLAY_LINE = "DISPLAY_LINE_";
47 private static final String DATAPOINT_NAME_DISPLAY_COLOR = "DISPLAY_COLOR_";
48 private static final String DATAPOINT_NAME_DISPLAY_ICON = "DISPLAY_ICON_";
49 private static final String DATAPOINT_NAME_DISPLAY_LED = "DISPLAY_LED";
50 private static final String DATAPOINT_NAME_DISPLAY_BEEPER = "DISPLAY_BEEPER";
51 private static final String DATAPOINT_NAME_DISPLAY_BEEPCOUNT = "DISPLAY_BEEPCOUNT";
52 private static final String DATAPOINT_NAME_DISPLAY_BEEPINTERVAL = "DISPLAY_BEEPINTERVAL";
53 private static final String DATAPOINT_NAME_DISPLAY_SUBMIT = "DISPLAY_SUBMIT";
55 private static final String START = "0x02";
56 private static final String STOP = "0x03";
57 private static final String LINE = "0x12";
58 private static final String COLOR = "0x11";
59 private static final String LF = "0x0a";
60 private static final String ICON = "0x13";
62 private static final String BEEPER_START = "0x14";
63 private static final String BEEPER_END = "0x1c";
64 private static final String BEEPCOUNT_END = "0x1D";
65 private static final String BEEPINTERVAL_END = "0x16";
67 private static Map<String, String> replaceMap = new HashMap<>();
69 // replace special chars while encoding
71 replaceMap.put("d6", "23");
72 replaceMap.put("dc", "24");
73 replaceMap.put("3d", "27");
74 replaceMap.put("c4", "5b");
75 replaceMap.put("df", "5f");
76 replaceMap.put("e4", "7b");
77 replaceMap.put("f6", "7c");
78 replaceMap.put("fc", "7d");
82 * Available text colors.
93 private final String code;
95 private Color(String code) {
99 protected String getCode() {
104 * Returns the color code.
106 public static String getCode(String name) {
108 return valueOf(name).getCode();
109 } catch (Exception ex) {
129 SIGNAL_GREEN("0x89"),
130 SIGNAL_YELLOW("0x8a"),
133 private final String code;
135 private Icon(String code) {
139 protected String getCode() {
144 * Returns the icon code.
146 public static String getCode(String name) {
148 return valueOf(name).getCode();
149 } catch (Exception ex) {
156 * Available Beeper codes.
158 private enum Beeper {
162 LONG_SHORT_SHORT("0xc3"),
167 private final String code;
169 private Beeper(String code) {
173 protected String getCode() {
178 * Returns the beeper code.
180 public static String getCode(String name) {
182 return valueOf(name).getCode();
183 } catch (Exception ex) {
184 return OFF.getCode();
190 * Available LED colors.
198 private final String code;
200 private Led(String code) {
204 protected String getCode() {
209 * Returns the LED code.
211 public static String getCode(String name) {
213 return valueOf(name).getCode();
214 } catch (Exception ex) {
215 return OFF.getCode();
221 public String getName() {
222 return DATAPOINT_NAME_DISPLAY_SUBMIT;
226 public void initialize(HmDevice device) {
227 if (isDisplay(device)) {
228 for (HmChannel channel : device.getChannels()) {
229 if (channel.hasDatapoint(new HmDatapointInfo(HmParamsetType.VALUES, channel, DATAPOINT_NAME_SUBMIT))) {
230 for (int i = 1; i <= getLineCount(device); i++) {
231 addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LINE + i, HmValueType.STRING,
234 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_ICON + i,
237 if (!isEpDisplay(device)) {
238 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_COLOR + i,
242 if (isEpDisplay(device)) {
243 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPER,
245 HmDatapoint bc = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPCOUNT,
246 HmValueType.INTEGER, 1, false);
249 HmDatapoint bd = addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_BEEPINTERVAL,
250 HmValueType.INTEGER, 1, false);
254 addEnumDisplayDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_LED, Led.class);
256 addDatapoint(device, channel.getNumber(), DATAPOINT_NAME_DISPLAY_SUBMIT, HmValueType.BOOL, false,
264 * Adds a Datapoint to the device with the values of the given enum.
266 private void addEnumDisplayDatapoint(HmDevice device, int channelNumber, String datapointName,
267 Class<? extends Enum<?>> e) {
268 HmDatapoint dpEnum = addDatapoint(device, channelNumber, datapointName, HmValueType.ENUM, null, false);
269 dpEnum.setOptions(getEnumNames(e));
270 dpEnum.setMinValue(0);
271 dpEnum.setMaxValue(e.getEnumConstants().length);
275 * Returns a string array with all the constants in the Enum.
277 private String[] getEnumNames(Class<? extends Enum<?>> e) {
278 return Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", ");
282 * Returns the number of lines of the display.
284 private int getLineCount(HmDevice device) {
285 return (DEVICE_TYPE_STATUS_DISPLAY.equals(device.getType()) ? 6 : 3);
289 * Returns true, if the display is a EP display.
291 private boolean isEpDisplay(HmDevice device) {
292 return DEVICE_TYPE_EP_STATUS_DISPLAY.equals(device.getType());
296 * Returns true, if the device is a supported display.
298 private boolean isDisplay(HmDevice device) {
299 return device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device);
303 public boolean canHandleCommand(HmDatapoint dp, Object value) {
304 HmDevice device = dp.getChannel().getDevice();
305 return (device.getType().equals(DEVICE_TYPE_STATUS_DISPLAY) || isEpDisplay(device))
306 && (getName().equals(dp.getName()) || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_LINE)
307 || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_COLOR)
308 || dp.getName().startsWith(DATAPOINT_NAME_DISPLAY_ICON)
309 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_LED)
310 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPER)
311 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPCOUNT)
312 || dp.getName().equals(DATAPOINT_NAME_DISPLAY_BEEPINTERVAL));
316 public void handleCommand(VirtualGateway gateway, HmDatapoint dp, HmDatapointConfig dpConfig, Object value)
317 throws IOException, HomematicClientException {
320 if (DATAPOINT_NAME_DISPLAY_SUBMIT.equals(dp.getName()) && MiscUtils.isTrueValue(dp.getValue())) {
321 HmChannel channel = dp.getChannel();
322 boolean isEp = isEpDisplay(channel.getDevice());
324 List<String> message = new ArrayList<>();
330 for (int i = 1; i <= getLineCount(channel.getDevice()); i++) {
331 String line = ObjectUtils.toString(
332 channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LINE + i).getValue());
333 if (StringUtils.isEmpty(line)) {
337 message.add(encodeText(line));
339 String color = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_COLOR + i)
342 String colorCode = Color.getCode(color);
343 message.add(StringUtils.isBlank(colorCode) ? Color.WHITE.getCode() : colorCode);
345 String icon = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_ICON + i)
347 String iconCode = Icon.getCode(icon);
348 if (StringUtils.isNotBlank(iconCode)) {
350 message.add(iconCode);
356 String beeper = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPER)
358 message.add(BEEPER_START);
359 message.add(Beeper.getCode(beeper));
360 message.add(BEEPER_END);
361 // set number of beeps
363 encodeBeepCount(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPCOUNT)));
364 message.add(BEEPCOUNT_END);
365 // set interval between two beeps
366 message.add(encodeBeepInterval(
367 channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_BEEPINTERVAL)));
368 message.add(BEEPINTERVAL_END);
369 // LED value must always set (same as beeps)
370 String led = channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_DISPLAY_LED).getOptionValue();
371 message.add(Led.getCode(led));
376 gateway.sendDatapoint(channel.getDatapoint(HmParamsetType.VALUES, DATAPOINT_NAME_SUBMIT),
377 new HmDatapointConfig(), StringUtils.join(message, ","), null);
382 * Encodes the beep count value. Allowed values 0 - 15, where 0 means infinite.
384 private String encodeBeepCount(HmDatapoint dp) {
385 int counts = (int) (Number) dp.getValue();
389 return String.format("0x%02x", 207 + counts);
393 * Encodes the beep interval value in 10 s steps. Allowed values 10 - 160.
395 private String encodeBeepInterval(HmDatapoint dp) {
396 int interval = (int) (Number) dp.getValue();
397 return String.format("0x%02x", 224 + ((interval - 1) / 10));
401 * Encodes the given text for the display.
403 private String encodeText(String text) {
404 final byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1);
405 StringBuilder sb = new StringBuilder(bytes.length * 2);
406 for (byte b : bytes) {
408 String hexValue = String.format("%02x", b);
409 sb.append(replaceMap.containsKey(hexValue) ? replaceMap.get(hexValue) : hexValue);
412 sb.setLength(sb.length() - 1);
413 return sb.toString();