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

support connect/disconnect, support multiple callbacks #39

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 43 additions & 16 deletions eq3bt/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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):
Expand All @@ -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."""
Expand Down
10 changes: 8 additions & 2 deletions eq3bt/eq3btsmart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions eq3bt/eq3cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down