diff --git a/eq3bt/connection.py b/eq3bt/connection.py index 052a81b..fa6dbb3 100644 --- a/eq3bt/connection.py +++ b/eq3bt/connection.py @@ -15,10 +15,12 @@ class BTLEConnection(btle.DefaultDelegate): """Representation of a BTLE Connection.""" - def __init__(self, mac): + def __init__(self, mac, iface=None): """Initialize the connection.""" btle.DefaultDelegate.__init__(self) + self._iface = iface + self._keep_connected = False self._conn = None self._mac = mac self._callbacks = {} @@ -29,32 +31,55 @@ def __enter__(self): :rtype: btle.Peripheral :return: """ - self._conn = btle.Peripheral() - self._conn.withDelegate(self) - _LOGGER.debug("Trying to connect to %s", self._mac) - try: - self._conn.connect(self._mac) - except btle.BTLEException as ex: - _LOGGER.debug("Unable to connect to the device %s, retrying: %s", self._mac, ex) + conn_state = "conn" + if self._conn: + # connection active, check if still connected try: - self._conn.connect(self._mac) - except Exception as ex2: - _LOGGER.debug("Second connection try to %s failed: %s", self._mac, ex2) - raise + conn_state = self._conn.getState() + except (btle.BTLEInternalError, btle.BTLEDisconnectError): + # connection not active, set _conn object to None + self._conn = None + + if self._conn is None or conn_state != "conn": + # no active connection, connect now + self._conn = btle.Peripheral() + self._conn.withDelegate(self) + _LOGGER.debug("Trying to connect to %s", self._mac) + self.connect(self._iface) + _LOGGER.debug("Connected to %s", self._mac) - _LOGGER.debug("Connected to %s", self._mac) return self def __exit__(self, exc_type, exc_val, exc_tb): - if self._conn: + if self._conn and self._keep_connected is False: self._conn.disconnect() self._conn = None + def connect(self, iface): + self._keep_connected = True + self._connect(iface) + + def _connect(self, iface): + try: + self._conn.connect(self._mac, iface=iface) + except btle.BTLEException as ex: + _LOGGER.debug("Unable to connect to the device %s using iface %s, retrying: %s", self._mac, iface, ex) + try: + self._conn.connect(self._mac, iface=iface) + except Exception as ex2: + _LOGGER.debug("Second connection try to %s using ifaces %s failed: %s", self._mac, iface, ex2) + raise + + def disconnect(self): + self._conn.disconnect() + self._conn = None + def handleNotification(self, handle, data): """Handle Callback from a Bluetooth (GATT) request.""" _LOGGER.debug("Got notification from %s: %s", handle, codecs.encode(data, 'hex')) if handle in self._callbacks: - self._callbacks[handle](data) + for callback in self._callbacks[handle]: + callback(data) @property def mac(self): @@ -63,7 +88,9 @@ def mac(self): def set_callback(self, handle, function): """Set the callback for a Notification handle. It will be called with the parameter data, which is binary.""" - self._callbacks[handle] = function + if handle not in self._callbacks: + self._callbacks[handle] = [] + self._callbacks[handle].append(function) def make_request(self, handle, value, timeout=DEFAULT_TIMEOUT, with_response=True): """Write a GATT Command without callback - not utf-8.""" diff --git a/eq3bt/eq3btsmart.py b/eq3bt/eq3btsmart.py index b510ce4..984245c 100644 --- a/eq3bt/eq3btsmart.py +++ b/eq3bt/eq3btsmart.py @@ -71,7 +71,7 @@ class TemperatureException(Exception): class Thermostat: """Representation of a EQ3 Bluetooth Smart thermostat.""" - def __init__(self, _mac, connection_cls=BTLEConnection): + def __init__(self, _mac, connection_cls=BTLEConnection, iface=None): """Initialize the thermostat.""" self._target_temperature = Mode.Unknown @@ -94,7 +94,7 @@ def __init__(self, _mac, connection_cls=BTLEConnection): self._firmware_version = None self._device_serial = None - self._conn = connection_cls(_mac) + self._conn = connection_cls(_mac, iface) self._conn.set_callback(PROP_NTFY_HANDLE, self.handle_notification) def __str__(self): @@ -107,6 +107,12 @@ def __str__(self): self.mode_readable, away_end) + def connect(self, iface): + self._conn.connect(iface) + + def disconnect(self): + self._conn.disconnect() + def _verify_temperature(self, temp): """Verifies that the temperature is valid. :raises TemperatureException: On invalid temperature. diff --git a/eq3bt/eq3cli.py b/eq3bt/eq3cli.py index 5801083..7d30ba7 100644 --- a/eq3bt/eq3cli.py +++ b/eq3bt/eq3cli.py @@ -21,16 +21,17 @@ def validate_mac(ctx, param, mac): @click.group(invoke_without_command=True) @click.option('--mac', envvar="EQ3_MAC", required=True, callback=validate_mac) +@click.option('--interface', default=None) @click.option('--debug/--normal', default=False) @click.pass_context -def cli(ctx, mac, debug): +def cli(ctx, mac, interface, debug): """ Tool to query and modify the state of EQ3 BT smart thermostat. """ if debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) - thermostat = Thermostat(mac) + thermostat = Thermostat(mac, iface=interface) thermostat.update() ctx.obj = thermostat