]> git.basschouten.com Git - openhab-addons.git/blob
b5a6d64b502cd17f171757b31e3bb4db58cca37b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.freeathomesystem.internal.handler;
14
15 import java.math.BigDecimal;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Map;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.binding.freeathomesystem.internal.configuration.FreeAtHomeDeviceHandlerConfiguration;
26 import org.openhab.binding.freeathomesystem.internal.datamodel.FreeAtHomeDatapoint;
27 import org.openhab.binding.freeathomesystem.internal.datamodel.FreeAtHomeDatapointGroup;
28 import org.openhab.binding.freeathomesystem.internal.datamodel.FreeAtHomeDeviceChannel;
29 import org.openhab.binding.freeathomesystem.internal.datamodel.FreeAtHomeDeviceDescription;
30 import org.openhab.binding.freeathomesystem.internal.type.FreeAtHomeChannelTypeProvider;
31 import org.openhab.binding.freeathomesystem.internal.util.FreeAtHomeGeneralException;
32 import org.openhab.binding.freeathomesystem.internal.util.FreeAtHomeHttpCommunicationException;
33 import org.openhab.binding.freeathomesystem.internal.util.UidUtils;
34 import org.openhab.binding.freeathomesystem.internal.valuestateconverter.ValueStateConverter;
35 import org.openhab.core.i18n.LocaleProvider;
36 import org.openhab.core.i18n.TranslationProvider;
37 import org.openhab.core.library.types.StopMoveType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.Channel;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingStatus;
44 import org.openhab.core.thing.ThingStatusDetail;
45 import org.openhab.core.thing.ThingUID;
46 import org.openhab.core.thing.binding.BaseThingHandler;
47 import org.openhab.core.thing.binding.ThingHandler;
48 import org.openhab.core.thing.binding.builder.ChannelBuilder;
49 import org.openhab.core.thing.binding.builder.ThingBuilder;
50 import org.openhab.core.thing.type.AutoUpdatePolicy;
51 import org.openhab.core.thing.type.ChannelKind;
52 import org.openhab.core.thing.type.ChannelType;
53 import org.openhab.core.thing.type.ChannelTypeBuilder;
54 import org.openhab.core.thing.type.ChannelTypeUID;
55 import org.openhab.core.types.Command;
56 import org.openhab.core.types.RefreshType;
57 import org.openhab.core.types.State;
58 import org.openhab.core.types.StateDescriptionFragmentBuilder;
59 import org.osgi.framework.Bundle;
60 import org.osgi.framework.FrameworkUtil;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * The {@link FreeAtHomeDeviceHandler} is responsible for handling the generic free@home device main communication
66  * and thing updates
67  *
68  * @author Andras Uhrin - Initial contribution
69  *
70  */
71 @NonNullByDefault
72 public class FreeAtHomeDeviceHandler extends BaseThingHandler implements FreeAtHomeDeviceStateListener {
73
74     private static final String CHANNEL_URI = "channel-type:freeathomesystem:config";
75
76     private final Logger logger = LoggerFactory.getLogger(FreeAtHomeDeviceHandler.class);
77     private FreeAtHomeDeviceDescription device = new FreeAtHomeDeviceDescription();
78     private FreeAtHomeChannelTypeProvider channelTypeProvider;
79     private TranslationProvider i18nProvider;
80     private Locale locale;
81     private Bundle bundle;
82
83     private Map<ChannelUID, FreeAtHomeDatapointGroup> mapChannelUID = new HashMap<ChannelUID, FreeAtHomeDatapointGroup>();
84     private Map<String, ChannelUID> mapEventToChannelUID = new HashMap<String, ChannelUID>();
85
86     public FreeAtHomeDeviceHandler(Thing thing, FreeAtHomeChannelTypeProvider channelTypeProvider,
87             TranslationProvider i18nProvider, LocaleProvider localeProvider) {
88         super(thing);
89
90         this.channelTypeProvider = channelTypeProvider;
91         this.i18nProvider = i18nProvider;
92         this.bundle = FrameworkUtil.getBundle(getClass());
93         this.locale = localeProvider.getLocale();
94     }
95
96     @Override
97     public void initialize() {
98         updateStatus(ThingStatus.UNKNOWN);
99
100         scheduler.execute(() -> {
101             FreeAtHomeDeviceHandlerConfiguration config = getConfigAs(FreeAtHomeDeviceHandlerConfiguration.class);
102
103             Bridge bridge = this.getBridge();
104             String locDeviceId = config.deviceId;
105
106             if (bridge != null) {
107                 ThingHandler handler = bridge.getHandler();
108
109                 if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
110                     if (!locDeviceId.isBlank()) {
111                         try {
112                             device = bridgeHandler.getFreeatHomeDeviceDescription(locDeviceId);
113
114                             updateChannels();
115                         } catch (FreeAtHomeHttpCommunicationException e) {
116                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
117                                     "@text/comm-error.error-in-sysap-com");
118                         } catch (FreeAtHomeGeneralException e) {
119                             logger.debug("General error in the binding - during initialization {}",
120                                     device.getDeviceId());
121
122                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
123                                     "@text/conf-error.general-binding-error");
124                         }
125
126                         // register device for status updates
127                         bridgeHandler.registerDeviceStateListener(device.getDeviceId(), this);
128
129                         updateStatus(ThingStatus.ONLINE);
130
131                         logger.debug("Device created - device id: {}", device.getDeviceId());
132                     } else {
133                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
134                                 "@text/conf-error.invalid-deviceconfig");
135
136                         logger.debug("Device cannot be created: device ID is null!");
137                     }
138                 }
139             } else {
140                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
141                         "@text/conf-error.bridge-not-configured");
142
143                 logger.debug("Device cannot be created: no bridge is configured!");
144                 return;
145             }
146         });
147     }
148
149     @Override
150     public void dispose() {
151         Bridge bridge = this.getBridge();
152
153         // Unregister device and specific channel for event based state updated
154         if (bridge != null) {
155             ThingHandler handler = bridge.getHandler();
156
157             if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
158                 bridgeHandler.unregisterDeviceStateListener(device.getDeviceId());
159             }
160         }
161
162         // Remove mapping tables
163         mapChannelUID.clear();
164
165         mapEventToChannelUID.clear();
166
167         logger.debug("Device removed - device id: {}", device.getDeviceId());
168     }
169
170     private void handleRefreshCommand(FreeAtHomeBridgeHandler freeAtHomeBridge, FreeAtHomeDatapointGroup dpg,
171             ChannelUID channelUID) {
172         String valueStr = "0";
173         String channelID = "ch000";
174         String datapointID = "0";
175
176         // Check whether it is a INPUT only datapoint group
177
178         if (dpg.getDirection() == FreeAtHomeDatapointGroup.DatapointGroupDirection.INPUT) {
179             FreeAtHomeDatapoint datapoint = dpg.getInputDatapoint();
180
181             if (datapoint != null) {
182                 channelID = datapoint.channelId;
183                 datapointID = datapoint.getDatapointId();
184             }
185         } else {
186             FreeAtHomeDatapoint datapoint = dpg.getOutputDatapoint();
187
188             if (datapoint != null) {
189                 channelID = datapoint.channelId;
190                 datapointID = datapoint.getDatapointId();
191             }
192         }
193
194         try {
195             valueStr = freeAtHomeBridge.getDatapoint(device.getDeviceId(), channelID, datapointID);
196
197             ValueStateConverter vsc = dpg.getValueStateConverter();
198
199             updateState(channelUID, vsc.convertToState(valueStr));
200         } catch (FreeAtHomeHttpCommunicationException e) {
201             logger.debug("Communication error during refresh command {} - at channel {} - Error string {}",
202                     device.getDeviceId(), channelUID.getAsString(), e.getMessage());
203
204             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
205                     "@text/comm-error.error-in-sysap-com");
206         } catch (FreeAtHomeGeneralException e) {
207             logger.debug("General error in the binding - during REFRESH command {} - at channel {} - Error string {}",
208                     device.getDeviceId(), channelUID.getAsString(), e.getMessage());
209
210             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
211                     "@text/conf-error.general-binding-error");
212         }
213     }
214
215     private void handleSetCommand(FreeAtHomeBridgeHandler freeAtHomeBridge, FreeAtHomeDatapointGroup dpg,
216             ChannelUID channelUID, Command command) {
217         State state = null;
218         String valueString = "0";
219
220         // initial error handling. look for the data point group validity
221         FreeAtHomeDatapoint datapoint = dpg.getInputDatapoint();
222
223         if (datapoint == null) {
224             logger.debug("Invalid parameter in handleSetCommand - DeviceId - {} - at channel {}", device.getDeviceId(),
225                     channelUID.getAsString());
226
227             return;
228         }
229
230         try {
231             ValueStateConverter vsc = dpg.getValueStateConverter();
232
233             if (command instanceof StopMoveType) {
234                 valueString = "0";
235             } else {
236                 state = ((State) command);
237                 valueString = vsc.convertToValueString(state);
238             }
239
240             freeAtHomeBridge.setDatapoint(device.getDeviceId(), datapoint.channelId, datapoint.getDatapointId(),
241                     valueString);
242
243             if (!device.isScene()) {
244                 if (state != null) {
245                     updateState(channelUID, state);
246                 } else {
247                     updateState(channelUID, new StringType("STOP"));
248                 }
249             }
250         } catch (FreeAtHomeHttpCommunicationException e) {
251             logger.debug(
252                     "Communication error during set command {} - at channel {} - full command {} - Error string {}",
253                     device.getDeviceId(), channelUID.getAsString(), command.toFullString(), e.getMessage());
254
255             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
256                     "@text/comm-error.error-in-sysap-com");
257         } catch (FreeAtHomeGeneralException e) {
258             logger.debug("General error in the binding - during SET command {} - at channel {} - Error string {}",
259                     device.getDeviceId(), channelUID.getAsString(), e.getMessage());
260
261             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
262                     "@text/conf-error.general-binding-error");
263         }
264     }
265
266     @Override
267     public void handleCommand(ChannelUID channelUID, Command command) {
268         FreeAtHomeBridgeHandler freeAtHomeBridge = null;
269
270         Bridge bridge = this.getBridge();
271
272         if (bridge != null) {
273             ThingHandler handler = bridge.getHandler();
274
275             if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
276                 freeAtHomeBridge = bridgeHandler;
277             }
278         }
279
280         if (freeAtHomeBridge != null) {
281             updateStatus(ThingStatus.ONLINE);
282         } else {
283             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
284                     "@text/conf-error.invalid-bridge");
285             return;
286         }
287
288         FreeAtHomeDatapointGroup dpg = mapChannelUID.get(channelUID);
289
290         // is the datapointgroup invalid
291         if (dpg == null) {
292             logger.debug("Handle command for device (but invalid datapointgroup) {} - at channel {} - full command {}",
293                     device.getDeviceId(), channelUID.getAsString(), command.toFullString());
294
295             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
296                     "@text/conf-error.invalid-deviceconfig");
297         } else {
298             if (command instanceof RefreshType) {
299                 handleRefreshCommand(freeAtHomeBridge, dpg, channelUID);
300             } else {
301                 handleSetCommand(freeAtHomeBridge, dpg, channelUID, command);
302             }
303
304             logger.debug("Handle command for device {} - at channel {} - full command {}", device.getDeviceId(),
305                     channelUID.getAsString(), command.toFullString());
306         }
307     }
308
309     public void onDeviceRemoved() {
310         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE);
311     }
312
313     public void onDeviceStateChanged(String event, String valueString) {
314         // Get the channle UID belonging to this event
315         ChannelUID channelUID = mapEventToChannelUID.get(event);
316
317         try {
318             if (channelUID != null) {
319                 // get the value State Converter for the channel
320                 FreeAtHomeDatapointGroup dpg = mapChannelUID.get(channelUID);
321
322                 if (dpg != null) {
323                     State state;
324                     state = dpg.getValueStateConverter().convertToState(valueString);
325
326                     // Handle state change
327                     handleEventBasedUpdate(channelUID, state);
328
329                     // if it is virtual device, give a feedback to free@home also
330                     if (isThingHandlesVirtualDevice()) {
331                         feedbackForVirtualDevice(channelUID, valueString);
332                     }
333                 }
334             }
335         } catch (FreeAtHomeGeneralException e) {
336             logger.debug("General error in the binding during onDeviceStateChange");
337
338             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
339                     "@text/conf-error.general-binding-error");
340         }
341     }
342
343     private void handleEventBasedUpdate(ChannelUID channelUID, State state) {
344         this.updateState(channelUID, state);
345     }
346
347     private void feedbackForVirtualDevice(ChannelUID channelUID, String valueString) {
348         FreeAtHomeBridgeHandler freeAtHomeBridge = null;
349
350         FreeAtHomeDatapointGroup dpg = mapChannelUID.get(channelUID);
351
352         Bridge bridge = this.getBridge();
353
354         if (bridge != null) {
355             ThingHandler handler = bridge.getHandler();
356
357             if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
358                 freeAtHomeBridge = bridgeHandler;
359             }
360         }
361
362         if (freeAtHomeBridge == null) {
363             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/gen-error.no-bridge-avail");
364             return;
365         }
366
367         if (dpg == null) {
368             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
369                     "@text/conf-error.datapointgroup-invalid");
370             return;
371         }
372
373         FreeAtHomeDatapoint inputDatapoint = dpg.getInputDatapoint();
374
375         if (inputDatapoint == null) {
376             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
377                     "@text/conf-error.inputdatapoint-invalid");
378             return;
379         }
380
381         if ((dpg.getDirection() != FreeAtHomeDatapointGroup.DatapointGroupDirection.INPUT)
382                 || (dpg.getDirection() != FreeAtHomeDatapointGroup.DatapointGroupDirection.INPUTOUTPUT)) {
383             logger.debug("Handle feedback for virtual device {} - at channel {} - but wrong config",
384                     device.getDeviceId(), channelUID.getAsString());
385         }
386
387         try {
388             freeAtHomeBridge.setDatapoint(device.getDeviceId(), inputDatapoint.channelId,
389                     inputDatapoint.getDatapointId(), valueString);
390
391             updateStatus(ThingStatus.ONLINE);
392
393             logger.debug("Handle feedback for virtual device {} - at channel {} - value {}", device.getDeviceId(),
394                     channelUID.getAsString(), valueString);
395         } catch (FreeAtHomeHttpCommunicationException e) {
396             logger.debug("Communication error during set command {} - at channel {} - value {} - Error string {}",
397                     device.getDeviceId(), channelUID.getAsString(), valueString, e.getMessage());
398
399             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
400                     "@text/comm-error.not-able-open-httpconnection");
401         }
402     }
403
404     public ChannelTypeUID createChannelTypeForDatapointgroup(FreeAtHomeDatapointGroup dpg,
405             ChannelTypeUID channelTypeUID) throws FreeAtHomeGeneralException {
406         StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create();
407
408         stateFragment.withReadOnly(dpg.isReadOnly());
409         stateFragment.withPattern(dpg.getTypePattern());
410
411         if (dpg.isDecimal() || dpg.isInteger()) {
412             BigDecimal min = new BigDecimal(dpg.getMin());
413             BigDecimal max = new BigDecimal(dpg.getMax());
414             stateFragment.withMinimum(min).withMaximum(max);
415         }
416
417         try {
418             URI configDescriptionUriChannel = new URI(CHANNEL_URI);
419
420             ChannelTypeBuilder<?> channelTypeBuilder = ChannelTypeBuilder
421                     .state(channelTypeUID,
422                             String.format("%s-%s-%s-%s", dpg.getLabel(), dpg.getOpenHabItemType(),
423                                     dpg.getOpenHabCategory(), "type"),
424                             dpg.getOpenHabItemType())
425                     .withCategory(dpg.getOpenHabCategory()).withStateDescriptionFragment(stateFragment.build());
426
427             ChannelType channelType = channelTypeBuilder.isAdvanced(false)
428                     .withConfigDescriptionURI(configDescriptionUriChannel)
429                     .withDescription(String.format("Type for channel - %s ", dpg.getLabel())).build();
430
431             channelTypeProvider.addChannelType(channelType);
432
433             logger.debug("Channel type created {} - label: {} - category: {}", channelTypeUID.getAsString(),
434                     dpg.getLabel(), dpg.getOpenHabCategory());
435         } catch (URISyntaxException e) {
436             logger.debug("Channel config URI cannot created for datapoint - datapoint group: {}", dpg.getLabel());
437         }
438
439         return channelTypeUID;
440     }
441
442     public void updateChannels() throws FreeAtHomeGeneralException {
443         // define update policy
444         AutoUpdatePolicy policy = AutoUpdatePolicy.DEFAULT;
445
446         if (device.isScene()) {
447             policy = AutoUpdatePolicy.VETO;
448         }
449
450         // Initialize channels
451         List<Channel> thingChannels = new ArrayList<>(this.getThing().getChannels());
452
453         if (thingChannels.isEmpty()) {
454             ThingBuilder thingBuilder = editThing();
455
456             ThingUID thingUID = thing.getUID();
457
458             for (int i = 0; i < device.getNumberOfChannels(); i++) {
459                 FreeAtHomeDeviceChannel channel = device.getChannel(i);
460
461                 for (int j = 0; j < channel.getNumberOfDatapointGroup(); j++) {
462                     FreeAtHomeDatapointGroup dpg = channel.getDatapointGroup(j);
463                     Map<String, String> channelProps = new HashMap<>();
464
465                     FreeAtHomeDatapoint inputDatapoint = dpg.getInputDatapoint();
466                     FreeAtHomeDatapoint outputDatapoint = dpg.getOutputDatapoint();
467
468                     if (inputDatapoint != null) {
469                         channelProps.put("input", inputDatapoint.getDatapointId());
470                     }
471
472                     if (outputDatapoint != null) {
473                         channelProps.put("output", outputDatapoint.getDatapointId());
474                     }
475
476                     ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(dpg.getValueType(),
477                             dpg.isReadOnly());
478
479                     if (channelTypeProvider.getChannelType(channelTypeUID, null) == null) {
480                         channelTypeUID = createChannelTypeForDatapointgroup(dpg, channelTypeUID);
481                     }
482
483                     ChannelUID channelUID = new ChannelUID(thingUID, channel.getChannelId(),
484                             dpg.getLabel().substring(4));
485
486                     String channelLabel = String.format("%s",
487                             i18nProvider.getText(bundle, dpg.getLabel(), "-", locale));
488
489                     String channelDescription = String.format("(%s) %s", channel.getChannelLabel(),
490                             i18nProvider.getText(bundle, dpg.getDescription(), "-", locale));
491
492                     Channel thingChannel = ChannelBuilder.create(channelUID)
493                             .withAcceptedItemType(dpg.getOpenHabItemType()).withKind(ChannelKind.STATE)
494                             .withProperties(channelProps).withLabel(capitalizeWordsInLabel(channelLabel))
495                             .withDescription(channelDescription).withType(channelTypeUID).withAutoUpdatePolicy(policy)
496                             .build();
497                     thingChannels.add(thingChannel);
498
499                     logger.debug("Thing channel created - device: {} - channelUID: {} - channel label: {}",
500                             device.getDeviceId() + device.getDeviceLabel(), channelUID.getAsString(), channelLabel);
501
502                     // in case of output channel, register it for updates
503                     if (outputDatapoint != null) {
504                         String eventDatapointID = new String(device.getDeviceId() + "/" + channel.getChannelId() + "/"
505                                 + outputDatapoint.getDatapointId());
506
507                         mapEventToChannelUID.put(eventDatapointID, channelUID);
508                     }
509
510                     // add the datapoint group to the mapping channel
511                     mapChannelUID.put(channelUID, dpg);
512
513                     if (dpg.getInputDatapoint() == null) {
514                         logger.debug(
515                                 "Thing channel registered - device:  {} - channelUID: {} - channel label: {} - category: {}",
516                                 device.getDeviceId() + device.getDeviceLabel(), channelUID.getAsString(),
517                                 dpg.getLabel(), dpg.getOpenHabCategory());
518                     } else {
519                         logger.debug(
520                                 "Thing channel registered - device: {} - channelUID: {} - channel label: {} - category: {}",
521                                 device.getDeviceId() + device.getDeviceLabel(), channelUID.getAsString(),
522                                 dpg.getLabel(), dpg.getOpenHabCategory());
523                     }
524                 }
525
526                 thingBuilder.withChannels(thingChannels);
527
528                 updateThing(thingBuilder.build());
529             }
530         } else {
531             reloadChannelTypes();
532         }
533
534         thingChannels.forEach(channel -> {
535             if (isLinked(channel.getUID())) {
536                 channelLinked(channel.getUID());
537             }
538         });
539     }
540
541     private void reloadChannelTypes() throws FreeAtHomeGeneralException {
542         Bridge bridge = this.getBridge();
543
544         ThingUID thingUID = thing.getUID();
545
546         try {
547             if (bridge != null) {
548                 ThingHandler handler = bridge.getHandler();
549
550                 if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
551                     device = bridgeHandler.getFreeatHomeDeviceDescription(device.getDeviceId());
552                 }
553             }
554         } catch (FreeAtHomeHttpCommunicationException e) {
555             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
556                     "@text/comm-error.error-in-sysap-com");
557         }
558
559         for (int i = 0; i < device.getNumberOfChannels(); i++) {
560             FreeAtHomeDeviceChannel channel = device.getChannel(i);
561
562             for (int j = 0; j < channel.getNumberOfDatapointGroup(); j++) {
563                 FreeAtHomeDatapointGroup dpg = channel.getDatapointGroup(j);
564
565                 ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(dpg.getValueType(), dpg.isReadOnly());
566
567                 if (channelTypeProvider.getChannelType(channelTypeUID, null) == null) {
568                     channelTypeUID = createChannelTypeForDatapointgroup(dpg, channelTypeUID);
569                 }
570
571                 ChannelUID channelUID = new ChannelUID(thingUID, channel.getChannelId());
572
573                 FreeAtHomeDatapoint outputDatapoint = dpg.getOutputDatapoint();
574
575                 // in case of output channel, register it for updates
576                 if (outputDatapoint != null) {
577                     String eventDatapointID = new String(device.getDeviceId() + "/" + channel.getChannelId() + "/"
578                             + outputDatapoint.getDatapointId());
579
580                     mapEventToChannelUID.put(eventDatapointID, channelUID);
581                 }
582
583                 // add the datapoint group to the mapping channel
584                 mapChannelUID.put(channelUID, dpg);
585
586                 logger.debug("Thing channelType reloaded - Device: {} - channelTypeUID: {}",
587                         device.getDeviceId() + device.getDeviceLabel(), channelTypeUID.getAsString());
588             }
589         }
590     }
591
592     public void removeChannels() {
593         Bridge bridge = this.getBridge();
594
595         try {
596             if (bridge != null) {
597                 ThingHandler handler = bridge.getHandler();
598
599                 if (handler instanceof FreeAtHomeBridgeHandler bridgeHandler) {
600                     device = bridgeHandler.getFreeatHomeDeviceDescription(device.getDeviceId());
601                 }
602             }
603         } catch (FreeAtHomeHttpCommunicationException e) {
604             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
605                     "@text/comm-error.error-in-sysap-com");
606         }
607
608         mapChannelUID.clear();
609
610         mapEventToChannelUID.clear();
611     }
612
613     private String capitalizeWordsInLabel(String label) {
614         // splliting up words using split function
615         String[] words = label.split(" ");
616
617         for (int i = 0; i < words.length; i++) {
618
619             // taking letter individually from sentences
620             String firstLetter = words[i].substring(0, 1);
621             String restOfWord = words[i].substring(1);
622
623             // making first letter uppercase using toUpperCase function
624             firstLetter = firstLetter.toUpperCase();
625             words[i] = firstLetter + restOfWord;
626         }
627
628         // joining the words together to make a sentence
629         return String.join(" ", words);
630     }
631
632     private boolean isThingHandlesVirtualDevice() {
633         return device.isVirtual();
634     }
635 }