]> git.basschouten.com Git - openhab-addons.git/blob
2759c5510fa5a6c29b3e2b59411595e487c803fc
[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.boschshc.internal.discovery;
14
15 import java.util.*;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
20 import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
21 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
22 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
23 import org.openhab.core.config.discovery.AbstractDiscoveryService;
24 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
25 import org.openhab.core.thing.ThingTypeUID;
26 import org.openhab.core.thing.ThingUID;
27 import org.openhab.core.thing.binding.ThingHandler;
28 import org.openhab.core.thing.binding.ThingHandlerService;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * The {@link ThingDiscoveryService} is responsible to discover Bosch Smart Home things.
34  * The paired SHC BridgeHandler is required to get the lists of rooms and devices.
35  * With this data the openhab things are discovered.
36  *
37  * The order to make this work is
38  * 1. SHC bridge is created, e.v via openhab UI
39  * 2. Service is instantiated setBridgeHandler of this service is called
40  * 3. Service is activated
41  * 4. Service registers itself as discoveryLister at the bridge
42  * 5. bridge calls startScan after bridge is paired and things can be discovered
43  *
44  * @author Gerd Zanker - Initial contribution
45  */
46 @NonNullByDefault
47 public class ThingDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
48     private static final int SEARCH_TIME = 1;
49
50     private final Logger logger = LoggerFactory.getLogger(ThingDiscoveryService.class);
51     private @Nullable BridgeHandler shcBridgeHandler;
52
53     protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(
54             BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH, BoschSHCBindingConstants.THING_TYPE_TWINGUARD,
55             BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT, BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR,
56             BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL, BoschSHCBindingConstants.THING_TYPE_THERMOSTAT,
57             BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL, BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT,
58             BoschSHCBindingConstants.THING_TYPE_CAMERA_360, BoschSHCBindingConstants.THING_TYPE_CAMERA_EYES,
59             BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM,
60             BoschSHCBindingConstants.THING_TYPE_SMART_PLUG_COMPACT, BoschSHCBindingConstants.THING_TYPE_SMART_BULB,
61             BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR);
62
63     // @formatter:off
64     protected static final Map<String, ThingTypeUID> DEVICEMODEL_TO_THINGTYPE_MAP = Map.ofEntries(
65             new AbstractMap.SimpleEntry<>("BBL", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL),
66             new AbstractMap.SimpleEntry<>("TWINGUARD", BoschSHCBindingConstants.THING_TYPE_TWINGUARD),
67             new AbstractMap.SimpleEntry<>("PSM", BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH),
68             new AbstractMap.SimpleEntry<>("PLUG_COMPACT", BoschSHCBindingConstants.THING_TYPE_SMART_PLUG_COMPACT),
69             new AbstractMap.SimpleEntry<>("CAMERA_360", BoschSHCBindingConstants.THING_TYPE_CAMERA_360),
70             new AbstractMap.SimpleEntry<>("CAMERA_EYES", BoschSHCBindingConstants.THING_TYPE_CAMERA_EYES),
71             new AbstractMap.SimpleEntry<>("BWTH", BoschSHCBindingConstants.THING_TYPE_THERMOSTAT), // wall thermostat
72             new AbstractMap.SimpleEntry<>("THB", BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT), // wall thermostat with batteries
73             new AbstractMap.SimpleEntry<>("SD", BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR),
74             new AbstractMap.SimpleEntry<>("MD", BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR),
75             new AbstractMap.SimpleEntry<>("ROOM_CLIMATE_CONTROL", BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL),
76             new AbstractMap.SimpleEntry<>("INTRUSION_DETECTION_SYSTEM", BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM),
77             new AbstractMap.SimpleEntry<>("HUE_LIGHT", BoschSHCBindingConstants.THING_TYPE_SMART_BULB),
78             new AbstractMap.SimpleEntry<>("WRC2", BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT)
79 // Future Extension: map deviceModel names to BoschSHC Thing Types when they are supported
80 //            new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.),
81 //            new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.),
82 //            new AbstractMap.SimpleEntry<>("VENTILATION_SERVICE", BoschSHCBindingConstants.),
83 //            new AbstractMap.SimpleEntry<>("HUE_BRIDGE", BoschSHCBindingConstants.)
84 //            new AbstractMap.SimpleEntry<>("HUE_BRIDGE_MANAGER*", BoschSHCBindingConstants.)
85 //            new AbstractMap.SimpleEntry<>("HUE_LIGHT_ROOM_CONTROL", BoschSHCBindingConstants.)
86             );
87     // @formatter:on
88
89     public ThingDiscoveryService() {
90         super(SUPPORTED_THING_TYPES, SEARCH_TIME);
91     }
92
93     @Override
94     public void activate() {
95         logger.trace("activate");
96         final BridgeHandler handler = shcBridgeHandler;
97         if (handler != null) {
98             handler.registerDiscoveryListener(this);
99         }
100     }
101
102     @Override
103     public void deactivate() {
104         logger.trace("deactivate");
105         final BridgeHandler handler = shcBridgeHandler;
106         if (handler != null) {
107             removeOlderResults(new Date().getTime(), handler.getThing().getUID());
108             handler.unregisterDiscoveryListener();
109         }
110
111         super.deactivate();
112     }
113
114     @Override
115     protected void startScan() {
116         if (shcBridgeHandler == null) {
117             logger.debug("The shcBridgeHandler is empty, no manual scan is currently possible");
118             return;
119         }
120
121         try {
122             doScan();
123         } catch (InterruptedException e) {
124             // Restore interrupted state...
125             Thread.currentThread().interrupt();
126         }
127     }
128
129     @Override
130     protected synchronized void stopScan() {
131         logger.debug("Stop manual scan on bridge {}",
132                 shcBridgeHandler != null ? shcBridgeHandler.getThing().getUID() : "?");
133         super.stopScan();
134         final BridgeHandler handler = shcBridgeHandler;
135         if (handler != null) {
136             removeOlderResults(getTimestampOfLastScan(), handler.getThing().getUID());
137         }
138     }
139
140     @Override
141     public void setThingHandler(@Nullable ThingHandler handler) {
142         if (handler instanceof BridgeHandler) {
143             logger.trace("Set bridge handler {}", handler);
144             shcBridgeHandler = (BridgeHandler) handler;
145         }
146     }
147
148     @Override
149     public @Nullable ThingHandler getThingHandler() {
150         return shcBridgeHandler;
151     }
152
153     public void doScan() throws InterruptedException {
154         logger.debug("Start manual scan on bridge {}", shcBridgeHandler.getThing().getUID());
155         // use shcBridgeHandler to getDevices()
156         List<Room> rooms = shcBridgeHandler.getRooms();
157         logger.debug("SHC has {} rooms", rooms.size());
158         List<Device> devices = shcBridgeHandler.getDevices();
159         logger.debug("SHC has {} devices", devices.size());
160
161         // Write found devices into openhab.log to support manual configuration
162         for (Device d : devices) {
163             logger.debug("Found device: name={} id={}", d.name, d.id);
164             if (d.deviceServiceIds != null) {
165                 for (String s : d.deviceServiceIds) {
166                     logger.debug(".... service: {}", s);
167                 }
168             }
169         }
170
171         addDevices(devices, rooms);
172     }
173
174     protected void addDevices(List<Device> devices, List<Room> rooms) {
175         for (Device device : devices) {
176             addDevice(device, getRoomNameForDevice(device, rooms));
177         }
178     }
179
180     protected String getRoomNameForDevice(Device device, List<Room> rooms) {
181         return rooms.stream().filter(room -> room.id.equals(device.roomId)).findAny().map(r -> r.name).orElse("");
182     }
183
184     protected void addDevice(Device device, String roomName) {
185         // see startScan for the runtime null check of shcBridgeHandler
186         assert shcBridgeHandler != null;
187
188         logger.trace("Discovering device {}", device.name);
189         logger.trace("- details: id {}, roomId {}, deviceModel {}", device.id, device.roomId, device.deviceModel);
190
191         ThingTypeUID thingTypeUID = getThingTypeUID(device);
192         if (thingTypeUID == null) {
193             return;
194         }
195
196         logger.trace("- got thingTypeID '{}' for deviceModel '{}'", thingTypeUID.getId(), device.deviceModel);
197
198         ThingUID thingUID = new ThingUID(thingTypeUID, shcBridgeHandler.getThing().getUID(),
199                 device.id.replace(':', '_'));
200
201         logger.trace("- got thingUID '{}' for device: '{}'", thingUID, device);
202
203         DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
204                 .withProperty("id", device.id).withLabel(getNiceName(device.name, roomName));
205         if (null != shcBridgeHandler) {
206             discoveryResult.withBridge(shcBridgeHandler.getThing().getUID());
207         }
208         if (!roomName.isEmpty()) {
209             discoveryResult.withProperty("Location", roomName);
210         }
211         thingDiscovered(discoveryResult.build());
212
213         logger.debug("Discovered device '{}' with thingTypeUID={}, thingUID={}, id={}, deviceModel={}", device.name,
214                 thingUID, thingTypeUID, device.id, device.deviceModel);
215     }
216
217     private String getNiceName(String name, String roomName) {
218         if (!name.startsWith("-"))
219             return name;
220
221         // convert "-IntrusionDetectionSystem-" into "Intrusion Detection System"
222         // convert "-RoomClimateControl-" into "Room Climate Control myRoomName"
223         final char[] chars = name.toCharArray();
224         StringBuilder niceNameBuilder = new StringBuilder(32);
225         for (int pos = 0; pos < chars.length; pos++) {
226             // skip "-"
227             if (chars[pos] == '-') {
228                 continue;
229             }
230             // convert "CamelCase" into "Camel Case", skipping the first Uppercase after the "-"
231             if (pos > 1 && Character.getType(chars[pos]) == Character.UPPERCASE_LETTER) {
232                 niceNameBuilder.append(" ");
233             }
234             niceNameBuilder.append(chars[pos]);
235         }
236         // append roomName for "Room Climate Control", because it appears for each room with a thermostat
237         if (!roomName.isEmpty() && niceNameBuilder.toString().startsWith("Room Climate Control")) {
238             niceNameBuilder.append(" ").append(roomName);
239         }
240         return niceNameBuilder.toString();
241     }
242
243     protected @Nullable ThingTypeUID getThingTypeUID(Device device) {
244         @Nullable
245         ThingTypeUID thingTypeId = DEVICEMODEL_TO_THINGTYPE_MAP.get(device.deviceModel);
246         if (thingTypeId != null) {
247             return new ThingTypeUID(BoschSHCBindingConstants.BINDING_ID, thingTypeId.getId());
248         }
249         logger.debug("Unknown deviceModel '{}'! Please create a support request issue for this unknown device model.",
250                 device.deviceModel);
251         return null;
252     }
253 }