]> git.basschouten.com Git - openhab-addons.git/blob
4ee82f6d86020a6c2a638e69c843f998448ca126
[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.type;
14
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*;
16 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
17
18 import java.io.BufferedReader;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.math.BigDecimal;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.ResourceBundle;
31 import java.util.Set;
32
33 import org.openhab.binding.homematic.internal.misc.MiscUtils;
34 import org.openhab.binding.homematic.internal.model.HmDatapoint;
35 import org.openhab.binding.homematic.internal.model.HmDevice;
36 import org.openhab.core.config.core.ConfigDescriptionParameter.Type;
37 import org.osgi.framework.Bundle;
38 import org.osgi.framework.FrameworkUtil;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Helper methods for generating the openHAB metadata.
44  *
45  * @author Gerhard Riegler - Initial contribution
46  * @author Michael Reitler - QuantityType support
47  */
48
49 public class MetadataUtils {
50     private static final Logger LOGGER = LoggerFactory.getLogger(MetadataUtils.class);
51     private static ResourceBundle descriptionsBundle;
52     private static Map<String, String> descriptions = new HashMap<>();
53     private static Map<String, Set<String>> standardDatapoints = new HashMap<>();
54
55     protected static void initialize() {
56         // loads all Homematic device names
57         loadBundle("homematic/generated-descriptions");
58         loadBundle("homematic/extra-descriptions");
59         loadStandardDatapoints();
60     }
61
62     private static void loadBundle(String filename) {
63         descriptionsBundle = ResourceBundle.getBundle(filename, Locale.getDefault());
64         for (String key : descriptionsBundle.keySet()) {
65             descriptions.put(key.toUpperCase(), descriptionsBundle.getString(key));
66         }
67         ResourceBundle.clearCache();
68         descriptionsBundle = null;
69     }
70
71     /**
72      * Loads the standard datapoints for channel metadata generation.
73      */
74     private static void loadStandardDatapoints() {
75         Bundle bundle = FrameworkUtil.getBundle(MetadataUtils.class);
76         try (InputStream stream = bundle.getResource("homematic/standard-datapoints.properties").openStream();
77                 BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
78             String line;
79             while ((line = reader.readLine()) != null) {
80                 if (!line.trim().isEmpty() && !line.startsWith("#")) {
81                     String[] parts = line.split("\\|");
82                     String channelType = null;
83                     String datapointName = null;
84                     if (parts.length > 0) {
85                         channelType = parts[0].trim();
86                         if (parts.length > 1) {
87                             datapointName = parts[1].trim();
88                         }
89                     }
90
91                     Set<String> channelDatapoints = standardDatapoints.get(channelType);
92                     if (channelDatapoints == null) {
93                         channelDatapoints = new HashSet<>();
94                         standardDatapoints.put(channelType, channelDatapoints);
95                     }
96
97                     channelDatapoints.add(datapointName);
98                 }
99             }
100         } catch (IllegalStateException | IOException e) {
101             LOGGER.warn("Can't load standard-datapoints.properties file!", e);
102         }
103     }
104
105     public interface OptionsBuilder<T> {
106         public T createOption(String value, String description);
107     }
108
109     /**
110      * Creates channel and config description metadata options for the given Datapoint.
111      */
112     public static <T> List<T> generateOptions(HmDatapoint dp, OptionsBuilder<T> optionsBuilder) {
113         List<T> options = null;
114         if (dp.getOptions() == null) {
115             LOGGER.warn("No options for ENUM datapoint {}", dp);
116         } else {
117             options = new ArrayList<>();
118             for (int i = 0; i < dp.getOptions().length; i++) {
119                 String description = null;
120                 if (!dp.isVariable() && !dp.isScript()) {
121                     description = getDescription(dp.getChannel().getType(), dp.getName(), dp.getOptions()[i]);
122                 }
123                 if (description == null) {
124                     description = dp.getOptions()[i];
125                 }
126                 options.add(optionsBuilder.createOption(dp.getOptions()[i], description));
127             }
128         }
129         return options;
130     }
131
132     /**
133      * Returns the ConfigDescriptionParameter type for the given Datapoint.
134      */
135     public static Type getConfigDescriptionParameterType(HmDatapoint dp) {
136         if (dp.isBooleanType()) {
137             return Type.BOOLEAN;
138         } else if (dp.isIntegerType()) {
139             return Type.INTEGER;
140         } else if (dp.isFloatType()) {
141             return Type.DECIMAL;
142         } else {
143             return Type.TEXT;
144         }
145     }
146
147     /**
148      * Returns the unit metadata string for the given Datapoint.
149      */
150     public static String getUnit(HmDatapoint dp) {
151         if (dp.getUnit() != null) {
152             return dp.getUnit().replace("100%", "%").replace("%", "%%");
153         }
154         return null;
155     }
156
157     /**
158      * Returns the pattern metadata string for the given Datapoint.
159      */
160     public static String getPattern(HmDatapoint dp) {
161         if (dp.isFloatType()) {
162             return "%.2f";
163         } else if (dp.isNumberType()) {
164             return "%d";
165         } else {
166             return null;
167         }
168     }
169
170     /**
171      * Returns the state pattern metadata string with unit for the given Datapoint.
172      */
173     public static String getStatePattern(HmDatapoint dp) {
174         String unit = getUnit(dp);
175         if ("%%".equals(unit)) {
176             return "%d %%";
177         }
178         if (unit != null && !unit.isEmpty()) {
179             String pattern = getPattern(dp);
180             if (pattern != null) {
181                 return String.format("%s %s", pattern, "%unit%");
182             }
183         }
184         return null;
185     }
186
187     /**
188      * Returns the label string for the given Datapoint.
189      */
190     public static String getLabel(HmDatapoint dp) {
191         return MiscUtils.capitalize(dp.getName().replace("_", " "));
192     }
193
194     /**
195      * Returns the parameter name for the specified Datapoint.
196      */
197     public static String getParameterName(HmDatapoint dp) {
198         return String.format("HMP_%d_%s", dp.getChannel().getNumber(), dp.getName());
199     }
200
201     /**
202      * Returns the description for the given keys.
203      */
204     public static String getDescription(String... keys) {
205         StringBuilder sb = new StringBuilder();
206         for (int startIdx = 0; startIdx < keys.length; startIdx++) {
207             String key = String.join("|", Arrays.copyOfRange(keys, startIdx, keys.length));
208             if (key.endsWith("|")) {
209                 key = key.substring(0, key.length() - 1);
210             }
211             String description = descriptions.get(key.toUpperCase());
212             if (description != null) {
213                 return description;
214             }
215             sb.append(key).append(", ");
216         }
217         if (LOGGER.isTraceEnabled()) {
218             LOGGER.trace("Description not found for: {}", sb.toString().substring(0, sb.length() - 2));
219         }
220         return null;
221     }
222
223     /**
224      * Returns the device name for the given device type.
225      */
226     public static String getDeviceName(HmDevice device) {
227         if (device.isGatewayExtras()) {
228             return getDescription(HmDevice.TYPE_GATEWAY_EXTRAS);
229         }
230
231         String deviceDescription = null;
232         boolean isTeam = device.getType().endsWith("-Team");
233         String type = isTeam ? device.getType().replace("-Team", "") : device.getType();
234         deviceDescription = getDescription(type);
235         if (deviceDescription != null && isTeam) {
236             deviceDescription += " Team";
237         }
238
239         return deviceDescription == null ? "No Description" : deviceDescription;
240     }
241
242     /**
243      * Returns the description for the given datapoint.
244      */
245     public static String getDatapointDescription(HmDatapoint dp) {
246         if (dp.isVariable() || dp.isScript()) {
247             return null;
248         }
249         return getDescription(dp.getChannel().getType(), dp.getName());
250     }
251
252     /**
253      * Returns true, if the given datapoint is a standard datapoint.
254      */
255     public static boolean isStandard(HmDatapoint dp) {
256         Set<String> channelDatapoints = standardDatapoints.get(dp.getChannel().getType());
257         if (channelDatapoints == null) {
258             return true;
259         }
260
261         return channelDatapoints.contains(dp.getName());
262     }
263
264     /**
265      * Helper method for creating a BigDecimal.
266      */
267     public static BigDecimal createBigDecimal(Number number) {
268         if (number == null) {
269             return null;
270         }
271         try {
272             return new BigDecimal(number.toString());
273         } catch (Exception ex) {
274             LOGGER.warn("Can't create BigDecimal for number: {}", number.toString());
275             return null;
276         }
277     }
278
279     /**
280      * Determines the itemType for the given Datapoint.
281      */
282     public static String getItemType(HmDatapoint dp) {
283         String dpName = dp.getName();
284         String channelType = dp.getChannel().getType();
285         if (channelType == null) {
286             channelType = "";
287         }
288
289         if (dp.isBooleanType()) {
290             if (((dpName.equals(DATAPOINT_NAME_STATE) || dpName.equals(VIRTUAL_DATAPOINT_NAME_STATE_CONTACT))
291                     && (channelType.equals(CHANNEL_TYPE_SHUTTER_CONTACT)
292                             || channelType.contentEquals(CHANNEL_TYPE_TILT_SENSOR)))
293                     || (dpName.equals(DATAPOINT_NAME_SENSOR) && channelType.equals(CHANNEL_TYPE_SENSOR))) {
294                 return ITEM_TYPE_CONTACT;
295             } else {
296                 return ITEM_TYPE_SWITCH;
297             }
298         } else if (dp.isNumberType()) {
299             if (dpName.startsWith(DATAPOINT_NAME_LEVEL) && isRollerShutter(dp)) {
300                 return ITEM_TYPE_ROLLERSHUTTER;
301             } else if (dpName.startsWith(DATAPOINT_NAME_LEVEL) && !channelType.equals(CHANNEL_TYPE_WINMATIC)
302                     && !channelType.equals(CHANNEL_TYPE_AKKU)) {
303                 return ITEM_TYPE_DIMMER;
304             } else {
305                 // determine QuantityType
306                 String unit = dp.getUnit() != null ? dp.getUnit() : "";
307                 switch (unit) {
308                     case "°C":
309                     case "°C":
310                         return ITEM_TYPE_NUMBER + ":Temperature";
311                     case "V":
312                         return ITEM_TYPE_NUMBER + ":ElectricPotential";
313                     case "100%":
314                     case "% rH":
315                     case "% rF":
316                     case "%":
317                         return ITEM_TYPE_NUMBER + ":Dimensionless";
318                     case "mHz":
319                     case "Hz":
320                         return ITEM_TYPE_NUMBER + ":Frequency";
321                     case "hPa":
322                         return ITEM_TYPE_NUMBER + ":Pressure";
323                     case "Lux":
324                         return ITEM_TYPE_NUMBER + ":Illuminance";
325                     case "degree":
326                         return ITEM_TYPE_NUMBER + ":Angle";
327                     case "km/h":
328                         return ITEM_TYPE_NUMBER + ":Speed";
329                     case "mm":
330                         return ITEM_TYPE_NUMBER + ":Length";
331                     case "W":
332                         return ITEM_TYPE_NUMBER + ":Power";
333                     case "Wh":
334                         return ITEM_TYPE_NUMBER + ":Energy";
335                     case "m3":
336                         return ITEM_TYPE_NUMBER + ":Volume";
337                     case "":
338                         if (dpName.startsWith(DATAPOINT_NAME_OPERATING_VOLTAGE)) {
339                             return ITEM_TYPE_NUMBER + ":ElectricPotential";
340                         }
341                     case "s":
342                     case "min":
343                     case "minutes":
344                     case "day":
345                     case "month":
346                     case "year":
347                     default:
348                         return ITEM_TYPE_NUMBER;
349                 }
350             }
351         } else if (dp.isDateTimeType()) {
352             return ITEM_TYPE_DATETIME;
353         } else {
354             return ITEM_TYPE_STRING;
355         }
356     }
357
358     /**
359      * Returns true, if the device of the datapoint is a rollershutter.
360      */
361     public static boolean isRollerShutter(HmDatapoint dp) {
362         String channelType = dp.getChannel().getType();
363         return channelType.equals(CHANNEL_TYPE_BLIND) || channelType.equals(CHANNEL_TYPE_JALOUSIE)
364                 || channelType.equals(CHANNEL_TYPE_BLIND_TRANSMITTER)
365                 || channelType.equals(CHANNEL_TYPE_SHUTTER_TRANSMITTER)
366                 || channelType.equals(CHANNEL_TYPE_SHUTTER_VIRTUAL_RECEIVER)
367                 || channelType.contentEquals(CHANNEL_TYPE_BLIND_VIRTUAL_RECEIVER);
368     }
369
370     /**
371      * Determines the category for the given Datapoint.
372      */
373     public static String getCategory(HmDatapoint dp, String itemType) {
374         String dpName = dp.getName();
375         String channelType = dp.getChannel().getType();
376         if (channelType == null) {
377             channelType = "";
378         }
379
380         if (dpName.equals(DATAPOINT_NAME_BATTERY_TYPE) || dpName.equals(DATAPOINT_NAME_LOWBAT)
381                 || dpName.equals(DATAPOINT_NAME_LOWBAT_IP)) {
382             return CATEGORY_BATTERY;
383         } else if (dpName.equals(DATAPOINT_NAME_STATE) && channelType.equals(CHANNEL_TYPE_ALARMACTUATOR)) {
384             return CATEGORY_ALARM;
385         } else if (dpName.equals(DATAPOINT_NAME_HUMIDITY)) {
386             return CATEGORY_HUMIDITY;
387         } else if (dpName.contains(DATAPOINT_NAME_TEMPERATURE)) {
388             return CATEGORY_TEMPERATURE;
389         } else if (dpName.equals(DATAPOINT_NAME_MOTION)) {
390             return CATEGORY_MOTION;
391         } else if (dpName.equals(DATAPOINT_NAME_AIR_PRESSURE)) {
392             return CATEGORY_PRESSURE;
393         } else if (dpName.equals(DATAPOINT_NAME_STATE) && channelType.equals(CHANNEL_TYPE_SMOKE_DETECTOR)) {
394             return CATEGORY_SMOKE;
395         } else if (dpName.equals(DATAPOINT_NAME_STATE) && channelType.equals(CHANNEL_TYPE_WATERDETECTIONSENSOR)) {
396             return CATEGORY_WATER;
397         } else if (dpName.equals(DATAPOINT_NAME_WIND_SPEED)) {
398             return CATEGORY_WIND;
399         } else if (dpName.startsWith(DATAPOINT_NAME_RAIN)
400                 || dpName.equals(DATAPOINT_NAME_STATE) && channelType.equals(CHANNEL_TYPE_RAINDETECTOR)) {
401             return CATEGORY_RAIN;
402         } else if (channelType.equals(CHANNEL_TYPE_POWERMETER) && !dpName.equals(DATAPOINT_NAME_BOOT)
403                 && !dpName.equals(DATAPOINT_NAME_FREQUENCY)) {
404             return CATEGORY_ENERGY;
405         } else if (itemType.equals(ITEM_TYPE_ROLLERSHUTTER)) {
406             return CATEGORY_BLINDS;
407         } else if (itemType.equals(ITEM_TYPE_CONTACT)) {
408             return CATEGORY_CONTACT;
409         } else if (itemType.equals(ITEM_TYPE_DIMMER)) {
410             return "";
411         } else if (itemType.equals(ITEM_TYPE_SWITCH)) {
412             return CATEGORY_SWITCH;
413         } else {
414             return null;
415         }
416     }
417 }