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.type;
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*;
16 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
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;
30 import java.util.ResourceBundle;
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;
43 * Helper methods for generating the openHAB metadata.
45 * @author Gerhard Riegler - Initial contribution
46 * @author Michael Reitler - QuantityType support
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<>();
55 protected static void initialize() {
56 // loads all Homematic device names
57 loadBundle("homematic/generated-descriptions");
58 loadBundle("homematic/extra-descriptions");
59 loadStandardDatapoints();
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));
67 ResourceBundle.clearCache();
68 descriptionsBundle = null;
72 * Loads the standard datapoints for channel metadata generation.
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))) {
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();
91 Set<String> channelDatapoints = standardDatapoints.get(channelType);
92 if (channelDatapoints == null) {
93 channelDatapoints = new HashSet<>();
94 standardDatapoints.put(channelType, channelDatapoints);
97 channelDatapoints.add(datapointName);
100 } catch (IllegalStateException | IOException e) {
101 logger.warn("Can't load standard-datapoints.properties file!", e);
105 public interface OptionsBuilder<T> {
106 public T createOption(String value, String description);
110 * Creates channel and config description metadata options for the given Datapoint.
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);
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]);
123 if (description == null) {
124 description = dp.getOptions()[i];
126 options.add(optionsBuilder.createOption(dp.getOptions()[i], description));
133 * Returns the ConfigDescriptionParameter type for the given Datapoint.
135 public static Type getConfigDescriptionParameterType(HmDatapoint dp) {
136 if (dp.isBooleanType()) {
138 } else if (dp.isIntegerType()) {
140 } else if (dp.isFloatType()) {
148 * Returns the unit metadata string for the given Datapoint.
150 public static String getUnit(HmDatapoint dp) {
151 if (dp.getUnit() != null) {
152 return dp.getUnit().replace("100%", "%").replace("%", "%%");
158 * Returns the pattern metadata string for the given Datapoint.
160 public static String getPattern(HmDatapoint dp) {
161 if (dp.isFloatType()) {
163 } else if (dp.isNumberType()) {
171 * Returns the state pattern metadata string with unit for the given Datapoint.
173 public static String getStatePattern(HmDatapoint dp) {
174 String unit = getUnit(dp);
175 if ("%%".equals(unit)) {
178 if (unit != null && !unit.isEmpty()) {
179 String pattern = getPattern(dp);
180 if (pattern != null) {
181 return String.format("%s %s", pattern, "%unit%");
188 * Returns the label string for the given Datapoint.
190 public static String getLabel(HmDatapoint dp) {
191 return MiscUtils.capitalize(dp.getName().replace("_", " "));
195 * Returns the parameter name for the specified Datapoint.
197 public static String getParameterName(HmDatapoint dp) {
198 return String.format("HMP_%d_%s", dp.getChannel().getNumber(), dp.getName());
202 * Returns the description for the given keys.
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);
211 String description = descriptions.get(key.toUpperCase());
212 if (description != null) {
215 sb.append(key).append(", ");
217 if (logger.isTraceEnabled()) {
218 logger.trace("Description not found for: {}", sb.toString().substring(0, sb.length() - 2));
224 * Returns the device name for the given device type.
226 public static String getDeviceName(HmDevice device) {
227 if (device.isGatewayExtras()) {
228 return getDescription(HmDevice.TYPE_GATEWAY_EXTRAS);
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";
239 return deviceDescription == null ? "No Description" : deviceDescription;
243 * Returns the description for the given datapoint.
245 public static String getDatapointDescription(HmDatapoint dp) {
246 if (dp.isVariable() || dp.isScript()) {
249 return getDescription(dp.getChannel().getType(), dp.getName());
253 * Returns true, if the given datapoint is a standard datapoint.
255 public static boolean isStandard(HmDatapoint dp) {
256 Set<String> channelDatapoints = standardDatapoints.get(dp.getChannel().getType());
257 if (channelDatapoints == null) {
261 return channelDatapoints.contains(dp.getName());
265 * Helper method for creating a BigDecimal.
267 public static BigDecimal createBigDecimal(Number number) {
268 if (number == null) {
272 return new BigDecimal(number.toString());
273 } catch (Exception ex) {
274 logger.warn("Can't create BigDecimal for number: {}", number.toString());
280 * Determines the itemType for the given Datapoint.
282 public static String getItemType(HmDatapoint dp) {
283 String dpName = dp.getName();
284 String channelType = dp.getChannel().getType();
285 if (channelType == null) {
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;
296 return ITEM_TYPE_SWITCH;
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;
305 // determine QuantityType
306 String unit = dp.getUnit() != null ? dp.getUnit() : "";
310 return ITEM_TYPE_NUMBER + ":Temperature";
312 return ITEM_TYPE_NUMBER + ":ElectricPotential";
317 return ITEM_TYPE_NUMBER + ":Dimensionless";
320 return ITEM_TYPE_NUMBER + ":Frequency";
322 return ITEM_TYPE_NUMBER + ":Pressure";
324 return ITEM_TYPE_NUMBER + ":Illuminance";
326 return ITEM_TYPE_NUMBER + ":Angle";
328 return ITEM_TYPE_NUMBER + ":Speed";
330 return ITEM_TYPE_NUMBER + ":Length";
332 return ITEM_TYPE_NUMBER + ":Power";
334 return ITEM_TYPE_NUMBER + ":Energy";
336 return ITEM_TYPE_NUMBER + ":Volume";
338 if (dpName.startsWith(DATAPOINT_NAME_OPERATING_VOLTAGE)) {
339 return ITEM_TYPE_NUMBER + ":ElectricPotential";
348 return ITEM_TYPE_NUMBER;
351 } else if (dp.isDateTimeType()) {
352 return ITEM_TYPE_DATETIME;
354 return ITEM_TYPE_STRING;
359 * Returns true, if the device of the datapoint is a rollershutter.
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);
371 * Determines the category for the given Datapoint.
373 public static String getCategory(HmDatapoint dp, String itemType) {
374 String dpName = dp.getName();
375 String channelType = dp.getChannel().getType();
376 if (channelType == null) {
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)) {
411 } else if (itemType.equals(ITEM_TYPE_SWITCH)) {
412 return CATEGORY_SWITCH;