Skip to content

Commit

Permalink
Allow to make more than one request with same connection
Browse files Browse the repository at this point in the history
`@with_connect` decorator performs device connect/disconnect for each
request. Added `connect` context manager allows to do many requests
within same connection to the device.
  • Loading branch information
gdyuldin committed Feb 1, 2020
1 parent 09df306 commit 6474e2f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 51 deletions.
13 changes: 11 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ mac = '3F:59:C8:80:70:BE'
client = Lywsd02Client(mac)
```

Read data
Read data (each property will be fetched using new connection)

```python
print(client.temperature)
Expand All @@ -47,6 +47,16 @@ Read temperature as humidity from a single notification
data = client.data
print(data.temperature)
print(data.humidity)


Read data (all data will be retrieved with a single connection)

```python
with client.connect():
data = client.data
print(data.temperature)
print(data.humidity)
print(client.battery)
```

## Available properties
Expand All @@ -69,7 +79,6 @@ Client may be initialized with additional kwargs.

* `notification_timeout` – timeout to wait for `temperature` and `humidity` requests. If sensor responds slower
then timeout data would not updated. Default value is 5 second.
* `data_request_timeout``temperature` and `humidity` are cached for this period. Default value is 15 second.

```python
from lywsd02 import Lywsd02Client
Expand Down
91 changes: 50 additions & 41 deletions lywsd02/client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import collections
import contextlib
import logging
import struct
import time
from datetime import datetime

from bluepy import btle

from .decorators import with_connect

_LOGGER = logging.getLogger(__name__)

UUID_UNITS = 'EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6' # 0x00 - F, 0x01 - C READ WRITE
Expand All @@ -31,15 +30,30 @@ class Lywsd02Client:
'F': b'\x01',
}

def __init__(self, mac, notification_timeout=5.0, data_request_timeout=15.0):
def __init__(self, mac, notification_timeout=5.0):
self._mac = mac
self._peripheral = btle.Peripheral()
self._notification_timeout = notification_timeout
self._request_timeout = data_request_timeout
self._handles = {}
self._tz_offset = None
self._data = SensorData(None, None)
self._last_request = None
self._connected = False

@contextlib.contextmanager
def connect(self):
# Store connecion status
conn_status = self._connected
if not conn_status:
_LOGGER.debug('Connecting to %s', self._mac)
self._peripheral.connect(self._mac)
self._connected = True
try:
yield self
finally:
if not conn_status:
_LOGGER.debug('Disconnecting from %s', self._mac)
self._peripheral.disconnect()
self._connected = False

@property
def temperature(self):
Expand All @@ -55,34 +69,35 @@ def data(self):
return self._data

@property
@with_connect
def units(self):
ch = self._peripheral.getCharacteristics(uuid=UUID_UNITS)[0]
value = ch.read()
with self.connect():
ch = self._peripheral.getCharacteristics(uuid=UUID_UNITS)[0]
value = ch.read()
return self.UNITS[value]

@units.setter
@with_connect
def units(self, value):
if value.upper() not in self.UNITS_CODES.keys():
raise ValueError('Units value must be one of %s' % self.UNITS_CODES.keys())
raise ValueError(
'Units value must be one of %s' % self.UNITS_CODES.keys())

ch = self._peripheral.getCharacteristics(uuid=UUID_UNITS)[0]
ch.write(self.UNITS_CODES[value.upper()], withResponse=True)
with self.connect():
ch = self._peripheral.getCharacteristics(uuid=UUID_UNITS)[0]
ch.write(self.UNITS_CODES[value.upper()], withResponse=True)

@property
@with_connect
def battery(self):
ch = self._peripheral.getCharacteristics(
uuid=btle.AssignedNumbers.battery_level)[0]
value = ch.read()
with self.connect():
ch = self._peripheral.getCharacteristics(
uuid=btle.AssignedNumbers.battery_level)[0]
value = ch.read()
return ord(value)

@property
@with_connect
def time(self):
ch = self._peripheral.getCharacteristics(uuid=UUID_TIME)[0]
value = ch.read()
with self.connect():
ch = self._peripheral.getCharacteristics(uuid=UUID_TIME)[0]
value = ch.read()
if len(value) == 5:
ts, tz_offset = struct.unpack('Ib', value)
else:
Expand All @@ -91,16 +106,16 @@ def time(self):
return datetime.fromtimestamp(ts), tz_offset

@time.setter
@with_connect
def time(self, dt: datetime):
if self._tz_offset is not None:
tz_offset = self._tz_offset
else:
tz_offset = int(-time.timezone / 3600)

data = struct.pack('Ib', int(dt.timestamp()), tz_offset)
ch = self._peripheral.getCharacteristics(uuid=UUID_TIME)[0]
ch.write(data, withResponse=True)
with self.connect():
ch = self._peripheral.getCharacteristics(uuid=UUID_TIME)[0]
ch.write(data, withResponse=True)

@property
def tz_offset(self):
Expand All @@ -110,29 +125,24 @@ def tz_offset(self):
def tz_offset(self, tz_offset: int):
self._tz_offset = tz_offset

@with_connect
def _get_sensor_data(self):
now = datetime.now().timestamp()
if self._last_request and now - self._last_request < self._request_timeout:
return

self._subscribe(UUID_DATA, self._process_sensor_data)
def _get_sensor_data(self):
with self.connect():
self._subscribe(UUID_DATA, self._process_sensor_data)

while True:
if self._peripheral.waitForNotifications(self._notification_timeout):
break
if not self._peripheral.waitForNotifications(
self._notification_timeout):
raise TimeoutError('No data from device for {}'.format(
self._notification_timeout))

@with_connect
def get_history_data(self):
data_received = False
self._subscribe(UUID_HISTORY, self._process_history_data)
with self.connect():
self._subscribe(UUID_HISTORY, self._process_history_data)

while True:
if self._peripheral.waitForNotifications(self._notification_timeout):
data_received = True
continue
if data_received:
break
while True:
if not self._peripheral.waitForNotifications(
self._notification_timeout):
break

def handleNotification(self, handle, data):
func = self._handles.get(handle)
Expand All @@ -151,7 +161,6 @@ def _process_sensor_data(self, data):
temperature, humidity = struct.unpack_from('HB', data)
temperature /= 100

self._last_request = datetime.now().timestamp()
self._data = SensorData(temperature=temperature, humidity=humidity)

def _process_history_data(self, data):
Expand Down
8 changes: 0 additions & 8 deletions lywsd02/decorators.py

This file was deleted.

0 comments on commit 6474e2f

Please sign in to comment.