Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ecobee] Rework discovery #9522

Merged
merged 4 commits into from Dec 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion bundles/org.openhab.binding.ecobee/README.md
Expand Up @@ -107,7 +107,6 @@ The following configuration parameters are available on the Ecobee Account:
| refreshIntervalQuick | Integer | Required | Specifies the interval in seconds with which the Ecobee data will be updated after sending an update or executing a function. |
| apiTimeout | Integer | Required | Time in seconds to allow an API request against the Ecobee servers to complete. |
| discoveryEnabled | Switch | Required | Specifies whether the binding should auto-discover thermostats and remote sensors. |
| discoveryInterval | Integer | Optional | Specifies time interval in seconds in which the binding will attempt to discover thermostats. |

### Ecobee Thermostat

Expand Down
Expand Up @@ -59,10 +59,19 @@ public class EcobeeBindingConstants {
public static final Set<ThingTypeUID> SUPPORTED_SENSOR_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(UID_SENSOR_THING).collect(Collectors.toSet()));

// Collection of thermostat and sensor thing types
public static final Set<ThingTypeUID> SUPPORTED_THERMOSTAT_AND_SENSOR_THING_TYPES_UIDS = Stream
.concat(SUPPORTED_THERMOSTAT_BRIDGE_THING_TYPES_UIDS.stream(), SUPPORTED_SENSOR_THING_TYPES_UIDS.stream())
.collect(Collectors.toSet());

// Collection of all supported thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(UID_ACCOUNT_BRIDGE, UID_THERMOSTAT_BRIDGE, UID_SENSOR_THING).collect(Collectors.toSet()));

// Background discovery frequency
public static final int DISCOVERY_INTERVAL_SECONDS = 300;
public static final int DISCOVERY_INITIAL_DELAY_SECONDS = 10;

// Thermostat bridge and remote sensor thing config parameters
public static final String CONFIG_THERMOSTAT_ID = "thermostatId";
public static final String CONFIG_SENSOR_ID = "sensorId";
Expand Down
Expand Up @@ -48,9 +48,4 @@ public class EcobeeAccountConfiguration {
* Enable/disable automatic discovery
*/
public @Nullable Boolean discoveryEnabled;

/**
* Interval with which to run the thermostat discovery process
*/
public @Nullable Integer discoveryInterval;
}
@@ -0,0 +1,185 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.ecobee.internal.discovery;

import static org.openhab.binding.ecobee.internal.EcobeeBindingConstants.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ecobee.internal.dto.thermostat.RemoteSensorDTO;
import org.openhab.binding.ecobee.internal.dto.thermostat.ThermostatDTO;
import org.openhab.binding.ecobee.internal.handler.EcobeeAccountBridgeHandler;
import org.openhab.binding.ecobee.internal.handler.EcobeeThermostatBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link EcobeeDiscoveryService} is responsible for discovering the Ecobee
* thermostats that are associated with the Ecobee Account, as well as the sensors
* are associated with the Ecobee thermostats.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class EcobeeDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {

private final Logger logger = LoggerFactory.getLogger(EcobeeDiscoveryService.class);

private @NonNullByDefault({}) EcobeeAccountBridgeHandler bridgeHandler;

private @Nullable Future<?> discoveryJob;

public EcobeeDiscoveryService() {
super(SUPPORTED_THERMOSTAT_AND_SENSOR_THING_TYPES_UIDS, 8, true);
}

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof EcobeeAccountBridgeHandler) {
this.bridgeHandler = (EcobeeAccountBridgeHandler) handler;
}
}

@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}

@Override
public void activate() {
super.activate(null);
}

@Override
public void deactivate() {
super.deactivate();
}

@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_THERMOSTAT_AND_SENSOR_THING_TYPES_UIDS;
}

@Override
protected void startBackgroundDiscovery() {
logger.debug("EcobeeDiscovery: Starting background discovery job");
Future<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
discoveryJob = scheduler.scheduleWithFixedDelay(this::backgroundDiscover, DISCOVERY_INITIAL_DELAY_SECONDS,
DISCOVERY_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}

@Override
protected void stopBackgroundDiscovery() {
logger.debug("EcobeeDiscovery: Stopping background discovery job");
Future<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob != null) {
localDiscoveryJob.cancel(true);
discoveryJob = null;
}
}

@Override
public void startScan() {
logger.debug("EcobeeDiscovery: Starting discovery scan");
discover();
}

private void backgroundDiscover() {
if (!bridgeHandler.isBackgroundDiscoveryEnabled()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should be removed. This method is called upon manually triggered scans, so it must be performed even if background discovery is not activated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return;
}
discover();
}

private void discover() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be synchronized instead of discoverThermostats() and discoverSensors().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that. As the thermostat and sensor discovery processes are largely independent, I decided to do it at that level. Is there a preference for doing it here instead?

if (bridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("EcobeeDiscovery: Skipping discovery because Account Bridge thing is not ONLINE");
return;
}
logger.debug("EcobeeDiscovery: Discovering Ecobee devices");
discoverThermostats();
discoverSensors();
}

private synchronized void discoverThermostats() {
logger.debug("EcobeeDiscovery: Discovering thermostats");
for (ThermostatDTO thermostat : bridgeHandler.getRegisteredThermostats()) {
String name = thermostat.name;
String identifier = thermostat.identifier;
if (identifier != null && name != null) {
ThingUID thingUID = new ThingUID(UID_THERMOSTAT_BRIDGE, bridgeHandler.getThing().getUID(), identifier);
thingDiscovered(createThermostatDiscoveryResult(thingUID, identifier, name));
logger.debug("EcobeeDiscovery: Thermostat '{}' and name '{}' added with UID '{}'", identifier, name,
thingUID);
}
}
}

private DiscoveryResult createThermostatDiscoveryResult(ThingUID thermostatUID, String identifier, String name) {
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_THERMOSTAT_ID, identifier);
return DiscoveryResultBuilder.create(thermostatUID).withProperties(properties)
.withRepresentationProperty(CONFIG_THERMOSTAT_ID).withBridge(bridgeHandler.getThing().getUID())
.withLabel(String.format("Ecobee Thermostat %s", name)).build();
}

private synchronized void discoverSensors() {
List<Thing> thermostatThings = bridgeHandler.getThing().getThings();
if (thermostatThings.size() == 0) {
logger.debug("EcobeeDiscovery: Skipping sensor discovery because there are no thermostat things");
return;
}
logger.debug("EcobeeDiscovery: Discovering sensors");
for (Thing thermostat : thermostatThings) {
EcobeeThermostatBridgeHandler thermostatHandler = (EcobeeThermostatBridgeHandler) thermostat.getHandler();
if (thermostatHandler != null) {
String thermostatId = thermostatHandler.getThermostatId();
logger.debug("EcobeeDiscovery: Discovering sensors for thermostat '{}'", thermostatId);
for (RemoteSensorDTO sensor : thermostatHandler.getSensors()) {
ThingUID bridgeUID = thermostatHandler.getThing().getUID();
ThingUID sensorUID = new ThingUID(UID_SENSOR_THING, bridgeUID, sensor.id.replace(":", "-"));
thingDiscovered(createSensorDiscoveryResult(sensorUID, bridgeUID, sensor));
logger.debug("EcobeeDiscovery: Sensor for '{}' with id '{}' and name '{}' added with UID '{}'",
thermostatId, sensor.id, sensor.name, sensorUID);
}
}
}
}

private DiscoveryResult createSensorDiscoveryResult(ThingUID sensorUID, ThingUID bridgeUID,
RemoteSensorDTO sensor) {
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_SENSOR_ID, sensor.id);
return DiscoveryResultBuilder.create(sensorUID).withProperties(properties)
.withRepresentationProperty(CONFIG_SENSOR_ID).withBridge(bridgeUID)
.withLabel(String.format("Ecobee Sensor %s", sensor.name)).build();
}
}

This file was deleted.