]> git.basschouten.com Git - openhab-addons.git/blob
948ca9179f7bda96f5fa965fd472eb31df4f36c1
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.math.BigDecimal;
19 import java.net.URI;
20 import java.net.URISyntaxException;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27
28 import org.apache.commons.lang.ObjectUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.apache.commons.lang.WordUtils;
31 import org.openhab.binding.homematic.internal.model.HmChannel;
32 import org.openhab.binding.homematic.internal.model.HmDatapoint;
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.type.MetadataUtils.OptionsBuilder;
36 import org.openhab.core.config.core.ConfigDescriptionBuilder;
37 import org.openhab.core.config.core.ConfigDescriptionParameter;
38 import org.openhab.core.config.core.ConfigDescriptionParameterBuilder;
39 import org.openhab.core.config.core.ConfigDescriptionParameterGroup;
40 import org.openhab.core.config.core.ParameterOption;
41 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.thing.type.ChannelDefinition;
45 import org.openhab.core.thing.type.ChannelDefinitionBuilder;
46 import org.openhab.core.thing.type.ChannelGroupDefinition;
47 import org.openhab.core.thing.type.ChannelGroupType;
48 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
49 import org.openhab.core.thing.type.ChannelGroupTypeUID;
50 import org.openhab.core.thing.type.ChannelType;
51 import org.openhab.core.thing.type.ChannelTypeBuilder;
52 import org.openhab.core.thing.type.ChannelTypeUID;
53 import org.openhab.core.thing.type.ThingType;
54 import org.openhab.core.thing.type.ThingTypeBuilder;
55 import org.openhab.core.types.EventDescription;
56 import org.openhab.core.types.EventOption;
57 import org.openhab.core.types.StateDescriptionFragmentBuilder;
58 import org.openhab.core.types.StateOption;
59 import org.osgi.service.component.annotations.Activate;
60 import org.osgi.service.component.annotations.Component;
61 import org.osgi.service.component.annotations.Reference;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 /**
66  * Generates ThingTypes based on metadata from a Homematic gateway.
67  *
68  * @author Gerhard Riegler - Initial contribution
69  */
70 @Component
71 public class HomematicTypeGeneratorImpl implements HomematicTypeGenerator {
72     private final Logger logger = LoggerFactory.getLogger(HomematicTypeGeneratorImpl.class);
73     private static URI configDescriptionUriChannel;
74
75     private HomematicThingTypeProvider thingTypeProvider;
76     private HomematicChannelTypeProvider channelTypeProvider;
77     private HomematicChannelGroupTypeProvider channelGroupTypeProvider;
78     private HomematicConfigDescriptionProvider configDescriptionProvider;
79     private final Map<String, Set<String>> firmwaresByType = new HashMap<>();
80
81     private static final String[] IGNORE_DATAPOINT_NAMES = new String[] { DATAPOINT_NAME_AES_KEY,
82             VIRTUAL_DATAPOINT_NAME_RELOAD_FROM_GATEWAY };
83
84     public HomematicTypeGeneratorImpl() {
85         try {
86             configDescriptionUriChannel = new URI(CONFIG_DESCRIPTION_URI_CHANNEL);
87         } catch (Exception ex) {
88             logger.warn("Can't create ConfigDescription URI '{}', ConfigDescription for channels not avilable!",
89                     CONFIG_DESCRIPTION_URI_CHANNEL);
90         }
91     }
92
93     @Reference
94     protected void setThingTypeProvider(HomematicThingTypeProvider thingTypeProvider) {
95         this.thingTypeProvider = thingTypeProvider;
96     }
97
98     protected void unsetThingTypeProvider(HomematicThingTypeProvider thingTypeProvider) {
99         this.thingTypeProvider = null;
100     }
101
102     @Reference
103     protected void setChannelTypeProvider(HomematicChannelTypeProvider channelTypeProvider) {
104         this.channelTypeProvider = channelTypeProvider;
105     }
106
107     protected void unsetChannelTypeProvider(HomematicChannelTypeProvider channelTypeProvider) {
108         this.channelTypeProvider = null;
109     }
110
111     @Reference
112     protected void setChannelGroupTypeProvider(HomematicChannelGroupTypeProvider channelGroupTypeProvider) {
113         this.channelGroupTypeProvider = channelGroupTypeProvider;
114     }
115
116     protected void unsetChannelGroupTypeProvider(HomematicChannelGroupTypeProvider channelGroupTypeProvider) {
117         this.channelGroupTypeProvider = null;
118     }
119
120     @Reference
121     protected void setConfigDescriptionProvider(HomematicConfigDescriptionProvider configDescriptionProvider) {
122         this.configDescriptionProvider = configDescriptionProvider;
123     }
124
125     protected void unsetConfigDescriptionProvider(HomematicConfigDescriptionProvider configDescriptionProvider) {
126         this.configDescriptionProvider = null;
127     }
128
129     @Override
130     @Activate
131     public void initialize() {
132         MetadataUtils.initialize();
133     }
134
135     @Override
136     public void generate(HmDevice device) {
137         if (thingTypeProvider != null) {
138             ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device);
139             ThingType tt = thingTypeProvider.getInternalThingType(thingTypeUID);
140
141             if (tt == null || device.isGatewayExtras()) {
142                 logger.debug("Generating ThingType for device '{}' with {} datapoints", device.getType(),
143                         device.getDatapointCount());
144
145                 List<ChannelGroupType> groupTypes = new ArrayList<>();
146                 for (HmChannel channel : device.getChannels()) {
147                     List<ChannelDefinition> channelDefinitions = new ArrayList<>();
148                     // Omit thing channel definitions for reconfigurable channels;
149                     // those will be populated dynamically during thing initialization
150                     if (!channel.isReconfigurable()) {
151                         // generate channel
152                         for (HmDatapoint dp : channel.getDatapoints()) {
153                             if (!isIgnoredDatapoint(dp) && dp.getParamsetType() == HmParamsetType.VALUES) {
154                                 ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(dp);
155                                 ChannelType channelType = channelTypeProvider.getInternalChannelType(channelTypeUID);
156                                 if (channelType == null) {
157                                     channelType = createChannelType(dp, channelTypeUID);
158                                     channelTypeProvider.addChannelType(channelType);
159                                 }
160
161                                 ChannelDefinition channelDef = new ChannelDefinitionBuilder(dp.getName(),
162                                         channelType.getUID()).build();
163                                 channelDefinitions.add(channelDef);
164                             }
165                         }
166                     }
167
168                     // generate group
169                     ChannelGroupTypeUID groupTypeUID = UidUtils.generateChannelGroupTypeUID(channel);
170                     ChannelGroupType groupType = channelGroupTypeProvider.getInternalChannelGroupType(groupTypeUID);
171                     if (groupType == null || device.isGatewayExtras()) {
172                         String groupLabel = String.format("%s",
173                                 WordUtils.capitalizeFully(StringUtils.replace(channel.getType(), "_", " ")));
174                         groupType = ChannelGroupTypeBuilder.instance(groupTypeUID, groupLabel)
175                                 .withChannelDefinitions(channelDefinitions).build();
176                         channelGroupTypeProvider.addChannelGroupType(groupType);
177                         groupTypes.add(groupType);
178                     }
179
180                 }
181                 tt = createThingType(device, groupTypes);
182                 thingTypeProvider.addThingType(tt);
183             }
184             addFirmware(device);
185         }
186     }
187
188     @Override
189     public void validateFirmwares() {
190         for (String deviceType : firmwaresByType.keySet()) {
191             Set<String> firmwares = firmwaresByType.get(deviceType);
192             if (firmwares.size() > 1) {
193                 logger.info(
194                         "Multiple firmware versions for device type '{}' found ({}). "
195                                 + "Make sure, all devices of the same type have the same firmware version, "
196                                 + "otherwise you MAY have channel and/or datapoint errors in the logfile",
197                         deviceType, StringUtils.join(firmwares, ", "));
198             }
199         }
200     }
201
202     /**
203      * Adds the firmware version for validation.
204      */
205     private void addFirmware(HmDevice device) {
206         if (!StringUtils.equals(device.getFirmware(), "?") && !DEVICE_TYPE_VIRTUAL.equals(device.getType())
207                 && !DEVICE_TYPE_VIRTUAL_WIRED.equals(device.getType())) {
208             Set<String> firmwares = firmwaresByType.get(device.getType());
209             if (firmwares == null) {
210                 firmwares = new HashSet<>();
211                 firmwaresByType.put(device.getType(), firmwares);
212             }
213             firmwares.add(device.getFirmware());
214         }
215     }
216
217     /**
218      * Creates the ThingType for the given device.
219      */
220     private ThingType createThingType(HmDevice device, List<ChannelGroupType> groupTypes) {
221         String label = MetadataUtils.getDeviceName(device);
222         String description = String.format("%s (%s)", label, device.getType());
223
224         List<String> supportedBridgeTypeUids = new ArrayList<>();
225         supportedBridgeTypeUids.add(THING_TYPE_BRIDGE.toString());
226         ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device);
227
228         Map<String, String> properties = new HashMap<>();
229         properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
230         properties.put(Thing.PROPERTY_MODEL_ID, device.getType());
231
232         URI configDescriptionURI = getConfigDescriptionURI(device);
233         if (configDescriptionProvider.getInternalConfigDescription(configDescriptionURI) == null) {
234             generateConfigDescription(device, configDescriptionURI);
235         }
236
237         List<ChannelGroupDefinition> groupDefinitions = new ArrayList<>();
238         for (ChannelGroupType groupType : groupTypes) {
239             String id = StringUtils.substringAfterLast(groupType.getUID().getId(), "_");
240             groupDefinitions.add(new ChannelGroupDefinition(id, groupType.getUID()));
241         }
242
243         return ThingTypeBuilder.instance(thingTypeUID, label).withSupportedBridgeTypeUIDs(supportedBridgeTypeUids)
244                 .withDescription(description).withChannelGroupDefinitions(groupDefinitions).withProperties(properties)
245                 .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withConfigDescriptionURI(configDescriptionURI)
246                 .build();
247     }
248
249     /**
250      * Creates the ChannelType for the given datapoint.
251      */
252     private ChannelType createChannelType(HmDatapoint dp, ChannelTypeUID channelTypeUID) {
253         ChannelType channelType;
254         if (dp.getName().equals(DATAPOINT_NAME_LOWBAT) || dp.getName().equals(DATAPOINT_NAME_LOWBAT_IP)) {
255             channelType = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_LOW_BATTERY;
256         } else if (dp.getName().equals(VIRTUAL_DATAPOINT_NAME_SIGNAL_STRENGTH)) {
257             channelType = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_SIGNAL_STRENGTH;
258         } else if (dp.getName().equals(VIRTUAL_DATAPOINT_NAME_BUTTON)) {
259             channelType = DefaultSystemChannelTypeProvider.SYSTEM_BUTTON;
260         } else {
261             String itemType = MetadataUtils.getItemType(dp);
262             String category = MetadataUtils.getCategory(dp, itemType);
263             String label = MetadataUtils.getLabel(dp);
264             String description = MetadataUtils.getDatapointDescription(dp);
265
266             List<StateOption> options = null;
267             if (dp.isEnumType()) {
268                 options = MetadataUtils.generateOptions(dp, new OptionsBuilder<StateOption>() {
269                     @Override
270                     public StateOption createOption(String value, String description) {
271                         return new StateOption(value, description);
272                     }
273                 });
274             }
275
276             StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create();
277             if (dp.isNumberType()) {
278                 BigDecimal min = MetadataUtils.createBigDecimal(dp.getMinValue());
279                 BigDecimal max = MetadataUtils.createBigDecimal(dp.getMaxValue());
280
281                 BigDecimal step = MetadataUtils.createBigDecimal(dp.getStep());
282                 if (step == null) {
283                     step = MetadataUtils.createBigDecimal(dp.isFloatType() ? new Float(0.1) : new Long(1L));
284                 }
285                 stateFragment.withMinimum(min).withMaximum(max).withStep(step)
286                         .withPattern(MetadataUtils.getStatePattern(dp)).withReadOnly(dp.isReadOnly());
287             } else {
288                 stateFragment.withPattern(MetadataUtils.getStatePattern(dp)).withReadOnly(dp.isReadOnly());
289             }
290             if (options != null) {
291                 stateFragment.withOptions(options);
292             }
293
294             ChannelTypeBuilder channelTypeBuilder;
295             EventDescription eventDescription = null;
296             if (dp.isTrigger()) {
297                 eventDescription = new EventDescription(
298                         MetadataUtils.generateOptions(dp, new OptionsBuilder<EventOption>() {
299                             @Override
300                             public EventOption createOption(String value, String description) {
301                                 return new EventOption(value, description);
302                             }
303                         }));
304                 channelTypeBuilder = ChannelTypeBuilder.trigger(channelTypeUID, label)
305                         .withEventDescription(eventDescription);
306             } else {
307                 channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, itemType)
308                         .withStateDescription(stateFragment.build().toStateDescription());
309             }
310             channelType = channelTypeBuilder.isAdvanced(!MetadataUtils.isStandard(dp)).withDescription(description)
311                     .withCategory(category).withConfigDescriptionURI(configDescriptionUriChannel).build();
312         }
313         return channelType;
314     }
315
316     private void generateConfigDescription(HmDevice device, URI configDescriptionURI) {
317         List<ConfigDescriptionParameter> parms = new ArrayList<>();
318         List<ConfigDescriptionParameterGroup> groups = new ArrayList<>();
319
320         for (HmChannel channel : device.getChannels()) {
321             String groupName = "HMG_" + channel.getNumber();
322             String groupLabel = MetadataUtils.getDescription("CHANNEL_NAME") + " " + channel.getNumber();
323             groups.add(new ConfigDescriptionParameterGroup(groupName, null, false, groupLabel, null));
324
325             for (HmDatapoint dp : channel.getDatapoints()) {
326                 if (dp.getParamsetType() == HmParamsetType.MASTER) {
327                     ConfigDescriptionParameterBuilder builder = ConfigDescriptionParameterBuilder.create(
328                             MetadataUtils.getParameterName(dp), MetadataUtils.getConfigDescriptionParameterType(dp));
329
330                     builder.withLabel(MetadataUtils.getLabel(dp));
331                     builder.withDefault(ObjectUtils.toString(dp.getDefaultValue()));
332                     builder.withDescription(MetadataUtils.getDatapointDescription(dp));
333
334                     if (dp.isEnumType()) {
335                         builder.withLimitToOptions(dp.isEnumType());
336                         List<ParameterOption> options = MetadataUtils.generateOptions(dp,
337                                 new OptionsBuilder<ParameterOption>() {
338                                     @Override
339                                     public ParameterOption createOption(String value, String description) {
340                                         return new ParameterOption(value, description);
341                                     }
342                                 });
343                         builder.withOptions(options);
344                     }
345
346                     if (dp.isNumberType()) {
347                         builder.withMinimum(MetadataUtils.createBigDecimal(dp.getMinValue()));
348                         builder.withMaximum(MetadataUtils.createBigDecimal(dp.getMaxValue()));
349                         builder.withStepSize(
350                                 MetadataUtils.createBigDecimal(dp.isFloatType() ? new Float(0.1) : new Long(1L)));
351                         builder.withUnitLabel(MetadataUtils.getUnit(dp));
352                     }
353
354                     builder.withGroupName(groupName);
355                     parms.add(builder.build());
356                 }
357             }
358         }
359         configDescriptionProvider.addConfigDescription(ConfigDescriptionBuilder.create(configDescriptionURI)
360                 .withParameters(parms).withParameterGroups(groups).build());
361     }
362
363     private URI getConfigDescriptionURI(HmDevice device) {
364         try {
365             return new URI(
366                     String.format("%s:%s", CONFIG_DESCRIPTION_URI_THING_PREFIX, UidUtils.generateThingTypeUID(device)));
367         } catch (URISyntaxException ex) {
368             logger.warn("Can't create configDescriptionURI for device type {}", device.getType());
369             return null;
370         }
371     }
372
373     /**
374      * Returns true, if the given datapoint can be ignored for metadata generation.
375      */
376     public static boolean isIgnoredDatapoint(HmDatapoint dp) {
377         return StringUtils.indexOfAny(dp.getName(), IGNORE_DATAPOINT_NAMES) != -1;
378     }
379 }