]> git.basschouten.com Git - openhab-addons.git/blob
91e102adade8bfa5c7757adbd04b950dd5f714e7
[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.knx.internal.dpt;
14
15 import static org.openhab.binding.knx.internal.dpt.DPTUtil.NORMALIZED_DPT;
16
17 import java.math.BigDecimal;
18 import java.math.RoundingMode;
19 import java.util.Locale;
20 import java.util.regex.Matcher;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.core.library.types.DateTimeType;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.HSBType;
27 import org.openhab.core.library.types.IncreaseDecreaseType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.OpenClosedType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.types.UpDownType;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.types.Type;
37 import org.openhab.core.util.ColorUtil;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import tuwien.auto.calimero.KNXException;
42 import tuwien.auto.calimero.dptxlator.DPT;
43 import tuwien.auto.calimero.dptxlator.DPTXlator;
44 import tuwien.auto.calimero.dptxlator.DPTXlator1BitControlled;
45 import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat;
46 import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
47 import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
48 import tuwien.auto.calimero.dptxlator.DPTXlatorDate;
49 import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
50 import tuwien.auto.calimero.dptxlator.DPTXlatorTime;
51 import tuwien.auto.calimero.dptxlator.TranslatorTypes;
52
53 /**
54  * This class encodes openHAB data types to strings for sending via Calimero
55  *
56  * Parts of this code are based on the openHAB KNXCoreTypeMapper by Kai Kreuzer et al.
57  *
58  * @author Jan N. Klug - Initial contribution
59  */
60 @NonNullByDefault
61 public class ValueEncoder {
62     private static final Logger LOGGER = LoggerFactory.getLogger(ValueEncoder.class);
63
64     private ValueEncoder() {
65         // prevent instantiation
66     }
67
68     /**
69      * Formats the given value as String for outputting via Calimero.
70      *
71      * @param value the value
72      * @param dptId the DPT id to use for formatting the string (e.g. 9.001)
73      * @return the value formatted as String
74      */
75     public static @Nullable String encode(Type value, String dptId) {
76         Matcher m = DPTUtil.DPT_PATTERN.matcher(dptId);
77         if (!m.matches() || m.groupCount() != 2) {
78             LOGGER.warn("Couldn't identify main/sub number in dptId '{}'", dptId);
79             return null;
80         }
81
82         String mainNumber = m.group("main");
83
84         try {
85             DPTXlator translator = TranslatorTypes.createTranslator(Integer.parseInt(mainNumber),
86                     NORMALIZED_DPT.getOrDefault(dptId, dptId));
87             DPT dpt = translator.getType();
88
89             // check for HSBType first, because it extends PercentType as well
90             if (value instanceof HSBType) {
91                 return handleHSBType(dptId, (HSBType) value);
92             } else if (value instanceof OnOffType) {
93                 return OnOffType.OFF.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
94             } else if (value instanceof UpDownType) {
95                 return UpDownType.UP.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
96             } else if (value instanceof IncreaseDecreaseType) {
97                 DPT valueDPT = ((DPTXlator3BitControlled.DPT3BitControlled) dpt).getControlDPT();
98                 return IncreaseDecreaseType.DECREASE.equals(value) ? valueDPT.getLowerValue() + " 5"
99                         : valueDPT.getUpperValue() + " 5";
100             } else if (value instanceof OpenClosedType) {
101                 return OpenClosedType.CLOSED.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
102             } else if (value instanceof StopMoveType) {
103                 return StopMoveType.STOP.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
104             } else if (value instanceof PercentType) {
105                 int intValue = ((PercentType) value).intValue();
106                 return "251.600".equals(dptId) ? String.format("- - - %d %%", intValue) : String.valueOf(intValue);
107             } else if (value instanceof DecimalType || value instanceof QuantityType<?>) {
108                 return handleNumericTypes(dptId, mainNumber, dpt, value);
109             } else if (value instanceof StringType) {
110                 return value.toString();
111             } else if (value instanceof DateTimeType) {
112                 return handleDateTimeType(dptId, (DateTimeType) value);
113             }
114         } catch (KNXException e) {
115             return null;
116         } catch (Exception e) {
117             LOGGER.warn("An exception occurred converting value {} to dpt id {}: error message={}", value, dptId,
118                     e.getMessage());
119             return null;
120         }
121
122         LOGGER.debug("formatAsDPTString: Couldn't convert value {} to dpt id {} (no mapping).", value, dptId);
123         return null;
124     }
125
126     /**
127      * Formats the given internal <code>dateType</code> to a knx readable String
128      * according to the target datapoint type <code>dpt</code>.
129      *
130      * @param value the input value
131      * @param dptId the target datapoint type
132      *
133      * @return a String which contains either an ISO8601 formatted date (yyyy-mm-dd),
134      *         a formatted 24-hour clock with the day of week prepended (Mon, 12:00:00) or
135      *         a formatted 24-hour clock (12:00:00)
136      */
137     private static @Nullable String handleDateTimeType(String dptId, DateTimeType value) {
138         if (DPTXlatorDate.DPT_DATE.getID().equals(dptId)) {
139             return value.format("%tF");
140         } else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dptId)) {
141             return value.format(Locale.US, "%1$ta, %1$tT");
142         } else if (DPTXlatorDateTime.DPT_DATE_TIME.getID().equals(dptId)) {
143             return value.format(Locale.US, "%tF %1$tT");
144         }
145         LOGGER.warn("Could not format DateTimeType for datapoint type '{}'", dptId);
146         return null;
147     }
148
149     private static String handleHSBType(String dptId, HSBType hsb) {
150         switch (dptId) {
151             case "232.600":
152                 return "r:" + convertPercentToByte(hsb.getRed()) + " g:" + convertPercentToByte(hsb.getGreen()) + " b:"
153                         + convertPercentToByte(hsb.getBlue());
154             case "232.60000":
155                 // MDT specific: mis-use 232.600 for hsv instead of rgb
156                 int hue = hsb.getHue().toBigDecimal().multiply(BigDecimal.valueOf(255))
157                         .divide(BigDecimal.valueOf(360), 2, RoundingMode.HALF_UP).intValue();
158                 return "r:" + hue + " g:" + convertPercentToByte(hsb.getSaturation()) + " b:"
159                         + convertPercentToByte(hsb.getBrightness());
160             case "242.600":
161                 double[] xyY = ColorUtil.hsbToXY(hsb);
162                 return String.format("(%,.4f %,.4f) %,.1f %%", xyY[0], xyY[1], xyY[2] * 100.0);
163             case "251.600":
164                 return String.format("%d %d %d - %%", hsb.getRed().intValue(), hsb.getGreen().intValue(),
165                         hsb.getBlue().intValue());
166             case "5.003":
167                 return hsb.getHue().toString();
168             default:
169                 return hsb.getBrightness().toString();
170         }
171     }
172
173     private static String handleNumericTypes(String dptId, String mainNumber, DPT dpt, Type value) {
174         BigDecimal bigDecimal;
175         if (value instanceof DecimalType decimalType) {
176             bigDecimal = decimalType.toBigDecimal();
177         } else {
178             String unit = DPTUnits.getUnitForDpt(dptId);
179
180             // exception for DPT using temperature differences
181             // - conversion °C or °F to K is wrong for differences,
182             // - stick to the unit given, fix the scaling for °F
183             // 9.002 DPT_Value_Tempd
184             // 9.003 DPT_Value_Tempa
185             // 9.023 DPT_KelvinPerPercent
186             if (DPTXlator2ByteFloat.DPT_TEMPERATURE_DIFFERENCE.getID().equals(dptId)
187                     || DPTXlator2ByteFloat.DPT_TEMPERATURE_GRADIENT.getID().equals(dptId)
188                     || DPTXlator2ByteFloat.DPT_KELVIN_PER_PERCENT.getID().equals(dptId)) {
189                 // match unicode character or °C
190                 if (value.toString().contains(SIUnits.CELSIUS.getSymbol()) || value.toString().contains("°C")) {
191                     unit = unit.replace("K", "°C");
192                 } else if (value.toString().contains("°F")) {
193                     unit = unit.replace("K", "°F");
194                     value = ((QuantityType<?>) value).multiply(BigDecimal.valueOf(5.0 / 9.0));
195                 }
196             } else if (DPTXlator4ByteFloat.DPT_LIGHT_QUANTITY.getID().equals(dptId)) {
197                 if (!value.toString().contains("J")) {
198                     unit = unit.replace("J", "lm*s");
199                 }
200             } else if (DPTXlator4ByteFloat.DPT_ELECTRIC_FLUX.getID().equals(dptId)) {
201                 // use alternate definition of flux
202                 if (value.toString().contains("C")) {
203                     unit = "C";
204                 }
205             }
206
207             if (unit != null) {
208                 QuantityType<?> converted = ((QuantityType<?>) value).toUnit(unit);
209                 if (converted == null) {
210                     LOGGER.warn("Could not convert {} to unit {}, stripping unit only. Check your configuration.",
211                             value, unit);
212                     bigDecimal = ((QuantityType<?>) value).toBigDecimal();
213                 } else {
214                     bigDecimal = converted.toBigDecimal();
215                 }
216             } else {
217                 bigDecimal = ((QuantityType<?>) value).toBigDecimal();
218             }
219         }
220         switch (mainNumber) {
221             case "2":
222                 DPT valueDPT = ((DPTXlator1BitControlled.DPT1BitControlled) dpt).getValueDPT();
223                 switch (bigDecimal.intValue()) {
224                     case 0:
225                         return "0 " + valueDPT.getLowerValue();
226                     case 1:
227                         return "0 " + valueDPT.getUpperValue();
228                     case 2:
229                         return "1 " + valueDPT.getLowerValue();
230                     default:
231                         return "1 " + valueDPT.getUpperValue();
232                 }
233             case "18":
234                 int intVal = bigDecimal.intValue();
235                 if (intVal > 63) {
236                     return "learn " + (intVal - 0x80);
237                 } else {
238                     return "activate " + intVal;
239                 }
240             default:
241                 return bigDecimal.stripTrailingZeros().toPlainString();
242         }
243     }
244
245     /**
246      * convert 0...100% to 1 byte 0..255
247      *
248      * @param percent
249      * @return int 0..255
250      */
251     private static int convertPercentToByte(PercentType percent) {
252         return percent.toBigDecimal().multiply(BigDecimal.valueOf(255))
253                 .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP).intValue();
254     }
255 }