Skip to content

Commit 74c8771

Browse files
authored
Merge pull request #1 from tannewt/blinka_bridge
Add Raspberry Pi bridge support
2 parents 5c4aeb2 + df112b2 commit 74c8771

15 files changed

+388
-266
lines changed

.github/workflows/build.yml

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ jobs:
3939
source actions-ci/install.sh
4040
- name: Library version
4141
run: git describe --dirty --always --tags
42+
- name: Check formatting
43+
run: |
44+
black --check --target-version=py35 .
4245
- name: PyLint
4346
run: |
4447
pylint $( find . -path './adafruit*.py' )

.pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ confidence=
5252
# no Warning level messages displayed, use"--disable=all --enable=classes
5353
# --disable=W"
5454
# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
55-
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error
55+
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation
5656

5757
# Enable the message, report, category or checker with the given id(s). You can
5858
# either give multiple identifier separated by comma (,) or put this option

README.rst

+3-6
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@ This is easily achieved by downloading
2828

2929
Installing from PyPI
3030
=====================
31-
.. note:: This library is not available on PyPI yet. Install documentation is included
32-
as a standard element. Stay tuned for PyPI availability!
33-
34-
.. todo:: Remove the above note if PyPI version is/will be available at time of release.
35-
If the library is not planned for PyPI, remove the entire 'Installing from PyPI' section.
31+
.. note:: Only the bridge examples work on Raspberry Pi because Blinka `_bleio` doesn't support
32+
advertising, only scanning.
3633

3734
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
3835
PyPI <https://pypi.org/project/adafruit-circuitpython-ble_broadcastnet/>`_. To install for current user:
@@ -59,7 +56,7 @@ To install in a virtual environment in your current project:
5956
Usage Example
6057
=============
6158

62-
.. todo:: Add a quick, simple example. It and other examples should live in the examples folder and be included in docs/examples.rst.
59+
Add a secrets.py file and then run ``ble_broadcastnet_blinka_bridge.py``.
6360

6461
Contributing
6562
============

adafruit_ble_broadcastnet.py

+87-58
Original file line numberDiff line numberDiff line change
@@ -27,127 +27,130 @@
2727
2828
2929
* Author(s): Scott Shawcroft
30-
31-
Implementation Notes
32-
--------------------
33-
34-
**Hardware:**
35-
36-
.. todo:: Add links to any specific hardware product page(s), or category page(s). Use unordered list & hyperlink rST
37-
inline format: "* `Link Text <url>`_"
38-
39-
**Software and Dependencies:**
40-
41-
* Adafruit CircuitPython firmware for the supported boards:
42-
https://github.com/adafruit/circuitpython/releases
43-
* Adafruit's BLE library: https://github.com/adafruit/Adafruit_CircuitPython_BLE
4430
"""
4531

32+
import struct
33+
import time
34+
from micropython import const
4635
import adafruit_ble
4736
from adafruit_ble.advertising import Advertisement, LazyObjectField
4837
from adafruit_ble.advertising.standard import ManufacturerData, ManufacturerDataField
49-
import struct
50-
import time
5138

5239
__version__ = "0.0.0-auto.0"
5340
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_BroadcastNet.git"
5441

55-
_ble = adafruit_ble.BLERadio()
56-
_sequence_number = 0
57-
def broadcast(measurement, *, broadcast_time=0.1):
58-
global _sequence_number
59-
measurement.sequence_number = _sequence_number
60-
_ble.start_advertising(measurement, scan_response=None)
61-
time.sleep(broadcast_time)
62-
_ble.stop_advertising()
63-
_sequence_number = (_sequence_number + 1) % 256
42+
_ble = adafruit_ble.BLERadio() # pylint: disable=invalid-name
43+
_sequence_number = 0 # pylint: disable=invalid-name
44+
45+
46+
def broadcast(measurement, *, broadcast_time=0.1, extended=False):
47+
"""Broadcasts the given measurement for the given broadcast time. If extended is False and the
48+
measurement would be too long, it will be split into multiple measurements for transmission.
49+
"""
50+
global _sequence_number # pylint: disable=global-statement,invalid-name
51+
for submeasurement in measurement.split(252 if extended else 31):
52+
submeasurement.sequence_number = _sequence_number
53+
_ble.start_advertising(submeasurement, scan_response=None)
54+
time.sleep(broadcast_time)
55+
_ble.stop_advertising()
56+
_sequence_number = (_sequence_number + 1) % 256
57+
6458

65-
device_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*_ble._adapter.address.address_bytes)
59+
device_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format( # pylint: disable=invalid-name
60+
*reversed(
61+
list(_ble._adapter.address.address_bytes) # pylint: disable=protected-access
62+
)
63+
)
64+
"""Device address as a string."""
6665

67-
_MANUFACTURING_DATA_ADT = const(0xff)
66+
_MANUFACTURING_DATA_ADT = const(0xFF)
6867
_ADAFRUIT_COMPANY_ID = const(0x0822)
69-
_SENSOR_DATA_ID = const(0x0002)
68+
7069

7170
class AdafruitSensorMeasurement(Advertisement):
7271
"""Broadcast a single RGB color."""
72+
7373
# This prefix matches all
74-
prefix = struct.pack("<BBH",
75-
3,
76-
_MANUFACTURING_DATA_ADT,
77-
_ADAFRUIT_COMPANY_ID)
74+
prefix = struct.pack("<BBH", 3, _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID)
7875

79-
manufacturer_data = LazyObjectField(ManufacturerData,
80-
"manufacturer_data",
81-
advertising_data_type=_MANUFACTURING_DATA_ADT,
82-
company_id=_ADAFRUIT_COMPANY_ID,
83-
key_encoding="<H")
76+
manufacturer_data = LazyObjectField(
77+
ManufacturerData,
78+
"manufacturer_data",
79+
advertising_data_type=_MANUFACTURING_DATA_ADT,
80+
company_id=_ADAFRUIT_COMPANY_ID,
81+
key_encoding="<H",
82+
)
8483

8584
sequence_number = ManufacturerDataField(0x0003, "<B")
8685
"""Sequence number of the measurement. Used to detect missed packets."""
8786

88-
acceleration = ManufacturerDataField(0x0a00, "<fff", ("x", "y", "z"))
87+
acceleration = ManufacturerDataField(0x0A00, "<fff", ("x", "y", "z"))
8988
"""Acceleration as (x, y, z) tuple of floats in meters per second per second."""
9089

91-
magnetic = ManufacturerDataField(0x0a01, "<fff", ("x", "y", "z"))
90+
magnetic = ManufacturerDataField(0x0A01, "<fff", ("x", "y", "z"))
9291
"""Magnetism as (x, y, z) tuple of floats in micro-Tesla."""
9392

94-
orientation = ManufacturerDataField(0x0a02, "<fff", ("x", "y", "z"))
93+
orientation = ManufacturerDataField(0x0A02, "<fff", ("x", "y", "z"))
9594
"""Absolution orientation as (x, y, z) tuple of floats in degrees."""
9695

97-
gyro = ManufacturerDataField(0x0a03, "<fff", ("x", "y", "z"))
96+
gyro = ManufacturerDataField(0x0A03, "<fff", ("x", "y", "z"))
9897
"""Gyro motion as (x, y, z) tuple of floats in radians per second."""
9998

100-
temperature = ManufacturerDataField(0x0a04, "<f")
99+
temperature = ManufacturerDataField(0x0A04, "<f")
101100
"""Temperature as a float in degrees centigrade."""
102101

103-
eCO2 = ManufacturerDataField(0x0a05, "<f")
102+
eCO2 = ManufacturerDataField(0x0A05, "<f")
104103
"""Equivalent CO2 as a float in parts per million."""
105104

106-
TVOC = ManufacturerDataField(0x0a06, "<f")
105+
TVOC = ManufacturerDataField(0x0A06, "<f")
107106
"""Total Volatile Organic Compounds as a float in parts per billion."""
108107

109-
distance = ManufacturerDataField(0x0a07, "<f")
108+
distance = ManufacturerDataField(0x0A07, "<f")
110109
"""Distance as a float in centimeters."""
111110

112-
light = ManufacturerDataField(0x0a08, "<f")
111+
light = ManufacturerDataField(0x0A08, "<f")
113112
"""Brightness as a float without units."""
114113

115-
lux = ManufacturerDataField(0x0a09, "<f")
114+
lux = ManufacturerDataField(0x0A09, "<f")
116115
"""Brightness as a float in SI lux."""
117116

118-
pressure = ManufacturerDataField(0x0a0a, "<f")
117+
pressure = ManufacturerDataField(0x0A0A, "<f")
119118
"""Pressure as a float in hectopascals."""
120119

121-
relative_humidity = ManufacturerDataField(0x0a0b, "<f")
120+
relative_humidity = ManufacturerDataField(0x0A0B, "<f")
122121
"""Relative humidity as a float percentage."""
123122

124-
current = ManufacturerDataField(0x0a0c, "<f")
123+
current = ManufacturerDataField(0x0A0C, "<f")
125124
"""Current as a float in milliamps."""
126125

127-
voltage = ManufacturerDataField(0x0a0d, "<f")
126+
voltage = ManufacturerDataField(0x0A0D, "<f")
128127
"""Voltage as a float in Volts."""
129128

130-
color = ManufacturerDataField(0x0a0e, "<f")
129+
color = ManufacturerDataField(0x0A0E, "<f")
131130
"""Color as RGB integer."""
132131

133132
# alarm = ManufacturerDataField(0x0a0f, "<f")
134-
"""Alarm as a start date and time and recurrence period. Not supported."""
133+
# """Alarm as a start date and time and recurrence period. Not supported."""
135134

136135
# datetime = ManufacturerDataField(0x0a10, "<f")
137-
"""Date and time as a struct. Not supported."""
136+
# """Date and time as a struct. Not supported."""
138137

139-
duty_cycle = ManufacturerDataField(0x0a11, "<f")
138+
duty_cycle = ManufacturerDataField(0x0A11, "<f")
140139
"""16-bit PWM duty cycle. Independent of frequency."""
141140

142-
frequency = ManufacturerDataField(0x0a12, "<f")
141+
frequency = ManufacturerDataField(0x0A12, "<f")
143142
"""As integer Hertz"""
144143

145-
value = ManufacturerDataField(0x0a13, "<f")
144+
value = ManufacturerDataField(0x0A13, "<f")
146145
"""16-bit unit-less value. Used for analog values and for booleans."""
147146

148-
weight = ManufacturerDataField(0x0a14, "<f")
147+
weight = ManufacturerDataField(0x0A14, "<f")
149148
"""Weight as a float in grams."""
150149

150+
battery_voltage = ManufacturerDataField(0x0A15, "<H")
151+
"""Battery voltage in millivolts. Saves two bytes over voltage and is more readable in bare
152+
packets."""
153+
151154
def __init__(self, *, sequence_number=None):
152155
super().__init__()
153156
if sequence_number:
@@ -162,3 +165,29 @@ def __str__(self):
162165
if value is not None:
163166
parts.append("{}={}".format(attr, str(value)))
164167
return "<{} {} >".format(self.__class__.__name__, " ".join(parts))
168+
169+
def split(self, max_packet_size=31):
170+
"""Split the measurement into multiple measurements with the given max_packet_size. Yields
171+
each submeasurement."""
172+
current_size = 8 # baseline for mfg data and sequence number
173+
if current_size + len(self.manufacturer_data) < max_packet_size:
174+
yield self
175+
return
176+
177+
original_data = self.manufacturer_data.data
178+
submeasurement = None
179+
for key in original_data:
180+
value = original_data[key]
181+
entry_size = 2 + len(value)
182+
if not submeasurement or current_size + entry_size > max_packet_size:
183+
if submeasurement:
184+
yield submeasurement
185+
submeasurement = self.__class__()
186+
current_size = 8
187+
submeasurement.manufacturer_data.data[key] = value
188+
current_size += entry_size
189+
190+
if submeasurement:
191+
yield submeasurement
192+
193+
return

0 commit comments

Comments
 (0)