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.omnilink.internal.discovery;
15 import static com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties.*;
16 import static com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties.*;
17 import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
19 import java.math.BigInteger;
20 import java.util.HashMap;
21 import java.util.LinkedList;
22 import java.util.List;
24 import java.util.Optional;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.omnilink.internal.SystemType;
29 import org.openhab.binding.omnilink.internal.exceptions.BridgeOfflineException;
30 import org.openhab.binding.omnilink.internal.handler.OmnilinkBridgeHandler;
31 import org.openhab.core.config.discovery.AbstractDiscoveryService;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.config.discovery.DiscoveryService;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.ThingHandler;
37 import org.openhab.core.thing.binding.ThingHandlerService;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.digitaldan.jomnilinkII.MessageTypes.SystemInformation;
42 import com.digitaldan.jomnilinkII.MessageTypes.properties.AccessControlReaderProperties;
43 import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
44 import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioSourceProperties;
45 import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioZoneProperties;
46 import com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties;
47 import com.digitaldan.jomnilinkII.MessageTypes.properties.ButtonProperties;
48 import com.digitaldan.jomnilinkII.MessageTypes.properties.ThermostatProperties;
49 import com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties;
50 import com.digitaldan.jomnilinkII.MessageTypes.properties.ZoneProperties;
51 import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
52 import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
55 * The {@link OmnilinkDiscoveryService} creates things based on the configured bridge.
57 * @author Craig Hamilton - Initial contribution
58 * @author Ethan Dye - openHAB3 rewrite
61 public class OmnilinkDiscoveryService extends AbstractDiscoveryService
62 implements DiscoveryService, ThingHandlerService {
63 private final Logger logger = LoggerFactory.getLogger(OmnilinkDiscoveryService.class);
64 private static final int DISCOVER_TIMEOUT_SECONDS = 30;
65 private @Nullable OmnilinkBridgeHandler bridgeHandler;
66 private Optional<SystemType> systemType = Optional.empty();
67 private @Nullable List<AreaProperties> areas;
70 * Creates an OmnilinkDiscoveryService.
72 public OmnilinkDiscoveryService() {
73 super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, false);
77 public void setThingHandler(@Nullable ThingHandler handler) {
78 if (handler instanceof OmnilinkBridgeHandler) {
79 bridgeHandler = (OmnilinkBridgeHandler) handler;
84 public @Nullable ThingHandler getThingHandler() {
89 public void activate() {
93 public void deactivate() {
97 protected synchronized void startScan() {
98 final OmnilinkBridgeHandler handler = bridgeHandler;
99 if (handler != null) {
100 logger.debug("Starting scan");
102 SystemInformation systemInformation = handler.reqSystemInformation();
103 this.systemType = SystemType.getType(systemInformation.getModel());
104 this.areas = discoverAreas();
108 discoverThermostats();
109 discoverAudioZones();
110 discoverAudioSources();
111 discoverTempSensors();
112 discoverHumiditySensors();
114 } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
115 logger.debug("Received error during discovery: {}", e.getMessage());
121 protected synchronized void stopScan() {
123 removeOlderResults(getTimestampOfLastScan());
127 * Calculate the area filter the a supplied area
129 * @param area Area to calculate filter for.
130 * @return Calculated Bit Filter for the supplied area. Bit 0 is area 1, bit 2 is area 2 and so on.
132 private static int bitFilterForArea(AreaProperties areaProperties) {
133 return BigInteger.ZERO.setBit(areaProperties.getNumber() - 1).intValue();
137 * Discovers OmniLink buttons
139 private void discoverButtons() {
140 final OmnilinkBridgeHandler handler = bridgeHandler;
141 if (handler != null) {
142 final ThingUID bridgeUID = handler.getThing().getUID();
143 final List<AreaProperties> areas = this.areas;
146 for (AreaProperties areaProperties : areas) {
147 int areaFilter = bitFilterForArea(areaProperties);
149 ObjectPropertyRequest<ButtonProperties> objectPropertyRequest = ObjectPropertyRequest
150 .builder(handler, ObjectPropertyRequests.BUTTONS, 0, 1).selectNamed().areaFilter(areaFilter)
153 for (ButtonProperties buttonProperties : objectPropertyRequest) {
154 String thingName = buttonProperties.getName();
155 String thingID = Integer.toString(buttonProperties.getNumber());
157 Map<String, Object> properties = new HashMap<>();
158 properties.put(THING_PROPERTIES_NAME, thingName);
159 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
161 ThingUID thingUID = new ThingUID(THING_TYPE_BUTTON, bridgeUID, thingID);
163 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
164 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
165 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
166 .withLabel(thingName).build();
167 thingDiscovered(discoveryResult);
175 * Discovers OmniLink locks
177 private void discoverLocks() {
178 final OmnilinkBridgeHandler handler = bridgeHandler;
179 if (handler != null) {
180 final ThingUID bridgeUID = handler.getThing().getUID();
182 ObjectPropertyRequest<AccessControlReaderProperties> objectPropertyRequest = ObjectPropertyRequest
183 .builder(handler, ObjectPropertyRequests.LOCK, 0, 1).selectNamed().build();
185 for (AccessControlReaderProperties lockProperties : objectPropertyRequest) {
186 String thingName = lockProperties.getName();
187 String thingID = Integer.toString(lockProperties.getNumber());
189 Map<String, Object> properties = Map.of(THING_PROPERTIES_NAME, thingName);
191 ThingUID thingUID = new ThingUID(THING_TYPE_LOCK, bridgeUID, thingID);
193 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
194 .withProperty(THING_PROPERTIES_NUMBER, thingID)
195 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
197 thingDiscovered(discoveryResult);
203 * Discovers OmniLink audio zones
205 private void discoverAudioZones() {
206 final OmnilinkBridgeHandler handler = bridgeHandler;
207 if (handler != null) {
208 final ThingUID bridgeUID = handler.getThing().getUID();
210 ObjectPropertyRequest<AudioZoneProperties> objectPropertyRequest = ObjectPropertyRequest
211 .builder(handler, ObjectPropertyRequests.AUDIO_ZONE, 0, 1).selectNamed().build();
213 for (AudioZoneProperties audioZoneProperties : objectPropertyRequest) {
214 String thingName = audioZoneProperties.getName();
215 String thingID = Integer.toString(audioZoneProperties.getNumber());
217 Map<String, Object> properties = Map.of(THING_PROPERTIES_NAME, thingName);
219 ThingUID thingUID = new ThingUID(THING_TYPE_AUDIO_ZONE, bridgeUID, thingID);
221 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
222 .withProperty(THING_PROPERTIES_NUMBER, thingID)
223 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
225 thingDiscovered(discoveryResult);
231 * Discovers OmniLink audio sources
233 private void discoverAudioSources() {
234 final OmnilinkBridgeHandler handler = bridgeHandler;
235 if (handler != null) {
236 final ThingUID bridgeUID = handler.getThing().getUID();
238 ObjectPropertyRequest<AudioSourceProperties> objectPropertyRequest = ObjectPropertyRequest
239 .builder(handler, ObjectPropertyRequests.AUDIO_SOURCE, 0, 1).selectNamed().build();
241 for (AudioSourceProperties audioSourceProperties : objectPropertyRequest) {
242 String thingName = audioSourceProperties.getName();
243 String thingID = Integer.toString(audioSourceProperties.getNumber());
245 Map<String, Object> properties = Map.of(THING_PROPERTIES_NAME, thingName);
247 ThingUID thingUID = new ThingUID(THING_TYPE_AUDIO_SOURCE, bridgeUID, thingID);
249 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
250 .withProperty(THING_PROPERTIES_NUMBER, thingID)
251 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
253 thingDiscovered(discoveryResult);
259 * Discovers OmniLink temperature sensors
261 private void discoverTempSensors() {
262 final OmnilinkBridgeHandler handler = bridgeHandler;
263 if (handler != null) {
264 final ThingUID bridgeUID = handler.getThing().getUID();
265 final List<AreaProperties> areas = this.areas;
268 for (AreaProperties areaProperties : areas) {
269 int areaFilter = bitFilterForArea(areaProperties);
271 ObjectPropertyRequest<AuxSensorProperties> objectPropertyRequest = ObjectPropertyRequest
272 .builder(handler, ObjectPropertyRequests.AUX_SENSORS, 0, 1).selectNamed()
273 .areaFilter(areaFilter).build();
275 for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
276 if (auxSensorProperties.getSensorType() != SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE
277 && auxSensorProperties.getSensorType() != SENSOR_TYPE_HUMIDITY) {
278 String thingName = auxSensorProperties.getName();
279 String thingID = Integer.toString(auxSensorProperties.getNumber());
281 Map<String, Object> properties = new HashMap<>();
282 properties.put(THING_PROPERTIES_NAME, thingName);
283 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
285 ThingUID thingUID = new ThingUID(THING_TYPE_TEMP_SENSOR, bridgeUID, thingID);
287 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
288 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
289 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
290 .withLabel(thingName).build();
291 thingDiscovered(discoveryResult);
300 * Discovers OmniLink humidity sensors
302 private void discoverHumiditySensors() {
303 final OmnilinkBridgeHandler handler = bridgeHandler;
304 if (handler != null) {
305 final ThingUID bridgeUID = handler.getThing().getUID();
306 final List<AreaProperties> areas = this.areas;
309 for (AreaProperties areaProperties : areas) {
310 int areaFilter = bitFilterForArea(areaProperties);
312 ObjectPropertyRequest<AuxSensorProperties> objectPropertyRequest = ObjectPropertyRequest
313 .builder(handler, ObjectPropertyRequests.AUX_SENSORS, 0, 1).selectNamed()
314 .areaFilter(areaFilter).build();
316 for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
317 if (auxSensorProperties.getSensorType() == SENSOR_TYPE_HUMIDITY) {
318 String thingName = auxSensorProperties.getName();
319 String thingID = Integer.toString(auxSensorProperties.getNumber());
321 Map<String, Object> properties = new HashMap<>();
322 properties.put(THING_PROPERTIES_NAME, thingName);
323 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
325 ThingUID thingUID = new ThingUID(THING_TYPE_HUMIDITY_SENSOR, bridgeUID, thingID);
327 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
328 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
329 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
330 .withLabel(thingName).build();
331 thingDiscovered(discoveryResult);
340 * Discovers OmniLink thermostats
342 private void discoverThermostats() {
343 final OmnilinkBridgeHandler handler = bridgeHandler;
344 if (handler != null) {
345 final ThingUID bridgeUID = handler.getThing().getUID();
346 final List<AreaProperties> areas = this.areas;
349 for (AreaProperties areaProperties : areas) {
350 int areaFilter = bitFilterForArea(areaProperties);
352 ObjectPropertyRequest<ThermostatProperties> objectPropertyRequest = ObjectPropertyRequest
353 .builder(handler, ObjectPropertyRequests.THERMOSTAT, 0, 1).selectNamed()
354 .areaFilter(areaFilter).build();
356 for (ThermostatProperties thermostatProperties : objectPropertyRequest) {
357 String thingName = thermostatProperties.getName();
358 String thingID = Integer.toString(thermostatProperties.getNumber());
360 ThingUID thingUID = new ThingUID(THING_TYPE_THERMOSTAT, bridgeUID, thingID);
362 Map<String, Object> properties = new HashMap<>();
363 properties.put(THING_PROPERTIES_NAME, thingName);
364 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
366 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
367 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
368 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
369 .withLabel(thingName).build();
370 thingDiscovered(discoveryResult);
378 * Discovers OmniLink areas
380 private @Nullable List<AreaProperties> discoverAreas() {
381 final OmnilinkBridgeHandler handler = bridgeHandler;
382 if (handler != null) {
383 final ThingUID bridgeUID = handler.getThing().getUID();
384 List<AreaProperties> areas = new LinkedList<>();
386 ObjectPropertyRequest<AreaProperties> objectPropertyRequest = ObjectPropertyRequest
387 .builder(handler, ObjectPropertyRequests.AREA, 0, 1).build();
389 for (AreaProperties areaProperties : objectPropertyRequest) {
390 int thingNumber = areaProperties.getNumber();
391 String thingName = areaProperties.getName();
392 String thingID = Integer.toString(thingNumber);
395 * It seems that for simple OmniLink Controller configurations there
396 * is only 1 area, without a name. So if there is no name for the
397 * first area, we will call that Main Area. If other area's name is
398 * blank, we will not create a thing.
400 if (thingNumber == 1 && "".equals(thingName)) {
401 thingName = "Main Area";
402 } else if ("".equals(thingName)) {
406 Map<String, Object> properties = Map.of(THING_PROPERTIES_NAME, thingName);
408 final String name = thingName;
409 systemType.ifPresentOrElse(t -> {
410 ThingUID thingUID = null;
413 thingUID = new ThingUID(THING_TYPE_LUMINA_AREA, bridgeUID, thingID);
416 thingUID = new ThingUID(THING_TYPE_OMNI_AREA, bridgeUID, thingID);
418 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
419 .withProperty(THING_PROPERTIES_NUMBER, thingID)
420 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(name)
422 thingDiscovered(discoveryResult);
424 logger.warn("Unknown System Type");
427 areas.add(areaProperties);
436 * Discovers OmniLink supported units
438 private void discoverUnits() {
439 final OmnilinkBridgeHandler handler = bridgeHandler;
440 if (handler != null) {
441 final ThingUID bridgeUID = handler.getThing().getUID();
442 final List<AreaProperties> areas = this.areas;
445 for (AreaProperties areaProperties : areas) {
446 int areaFilter = bitFilterForArea(areaProperties);
448 ObjectPropertyRequest<UnitProperties> objectPropertyRequest = ObjectPropertyRequest
449 .builder(handler, ObjectPropertyRequests.UNIT, 0, 1).selectNamed().areaFilter(areaFilter)
450 .selectAnyLoad().build();
452 for (UnitProperties unitProperties : objectPropertyRequest) {
453 int thingType = unitProperties.getUnitType();
454 String thingName = unitProperties.getName();
455 String thingID = Integer.toString(unitProperties.getNumber());
456 ThingUID thingUID = null;
458 Map<String, Object> properties = new HashMap<>();
459 properties.put(THING_PROPERTIES_NAME, thingName);
460 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
463 case UNIT_TYPE_HLC_ROOM:
464 case UNIT_TYPE_VIZIARF_ROOM:
465 thingUID = new ThingUID(THING_TYPE_ROOM, bridgeUID, thingID);
468 thingUID = new ThingUID(THING_TYPE_FLAG, bridgeUID, thingID);
470 case UNIT_TYPE_OUTPUT:
471 thingUID = new ThingUID(THING_TYPE_OUTPUT, bridgeUID, thingID);
474 case UNIT_TYPE_HLC_LOAD:
475 thingUID = new ThingUID(THING_TYPE_UNIT_UPB, bridgeUID, thingID);
477 case UNIT_TYPE_CENTRALITE:
478 case UNIT_TYPE_RADIORA:
479 case UNIT_TYPE_VIZIARF_LOAD:
480 case UNIT_TYPE_COMPOSE:
481 thingUID = new ThingUID(THING_TYPE_DIMMABLE, bridgeUID, thingID);
484 thingUID = new ThingUID(THING_TYPE_UNIT, bridgeUID, thingID);
485 logger.debug("Generic unit type: {}", thingType);
489 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
490 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
491 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
492 .withLabel(thingName).build();
493 thingDiscovered(discoveryResult);
501 * Generates zone items
503 private void discoverZones() {
504 final OmnilinkBridgeHandler handler = bridgeHandler;
505 if (handler != null) {
506 final ThingUID bridgeUID = handler.getThing().getUID();
507 final List<AreaProperties> areas = this.areas;
510 for (AreaProperties areaProperties : areas) {
511 int areaFilter = bitFilterForArea(areaProperties);
513 ObjectPropertyRequest<ZoneProperties> objectPropertyRequest = ObjectPropertyRequest
514 .builder(handler, ObjectPropertyRequests.ZONE, 0, 1).selectNamed().areaFilter(areaFilter)
517 for (ZoneProperties zoneProperties : objectPropertyRequest) {
518 if (zoneProperties.getZoneType() <= SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE) {
519 String thingName = zoneProperties.getName();
520 String thingID = Integer.toString(zoneProperties.getNumber());
522 Map<String, Object> properties = new HashMap<>();
523 properties.put(THING_PROPERTIES_NAME, thingName);
524 properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
526 ThingUID thingUID = new ThingUID(THING_TYPE_ZONE, bridgeUID, thingID);
528 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
529 .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
530 .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
531 .withLabel(thingName).build();
532 thingDiscovered(discoveryResult);