Skip to content

Add Raspberry Pi bridge support #1

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

Merged
merged 10 commits into from
Feb 26, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
source actions-ci/install.sh
- name: Library version
run: git describe --dirty --always --tags
- name: Check formatting
run: |
black --check --target-version=py35 .
- name: PyLint
run: |
pylint $( find . -path './adafruit*.py' )
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ confidence=
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
# 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
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
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

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
9 changes: 3 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ This is easily achieved by downloading

Installing from PyPI
=====================
.. note:: This library is not available on PyPI yet. Install documentation is included
as a standard element. Stay tuned for PyPI availability!

.. todo:: Remove the above note if PyPI version is/will be available at time of release.
If the library is not planned for PyPI, remove the entire 'Installing from PyPI' section.
.. note:: Only the bridge examples work on Raspberry Pi because Blinka `_bleio` doesn't support
advertising, only scanning.

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

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

Contributing
============
Expand Down
145 changes: 87 additions & 58 deletions adafruit_ble_broadcastnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,127 +27,130 @@


* Author(s): Scott Shawcroft

Implementation Notes
--------------------

**Hardware:**

.. todo:: Add links to any specific hardware product page(s), or category page(s). Use unordered list & hyperlink rST
inline format: "* `Link Text <url>`_"

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit's BLE library: https://github.com/adafruit/Adafruit_CircuitPython_BLE
"""

import struct
import time
from micropython import const
import adafruit_ble
from adafruit_ble.advertising import Advertisement, LazyObjectField
from adafruit_ble.advertising.standard import ManufacturerData, ManufacturerDataField
import struct
import time

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

_ble = adafruit_ble.BLERadio()
_sequence_number = 0
def broadcast(measurement, *, broadcast_time=0.1):
global _sequence_number
measurement.sequence_number = _sequence_number
_ble.start_advertising(measurement, scan_response=None)
time.sleep(broadcast_time)
_ble.stop_advertising()
_sequence_number = (_sequence_number + 1) % 256
_ble = adafruit_ble.BLERadio() # pylint: disable=invalid-name
_sequence_number = 0 # pylint: disable=invalid-name


def broadcast(measurement, *, broadcast_time=0.1, extended=False):
"""Broadcasts the given measurement for the given broadcast time. If extended is False and the
measurement would be too long, it will be split into multiple measurements for transmission.
"""
global _sequence_number # pylint: disable=global-statement,invalid-name
for submeasurement in measurement.split(252 if extended else 31):
submeasurement.sequence_number = _sequence_number
_ble.start_advertising(submeasurement, scan_response=None)
time.sleep(broadcast_time)
_ble.stop_advertising()
_sequence_number = (_sequence_number + 1) % 256


device_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*_ble._adapter.address.address_bytes)
device_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format( # pylint: disable=invalid-name
*reversed(
list(_ble._adapter.address.address_bytes) # pylint: disable=protected-access
)
)
"""Device address as a string."""

_MANUFACTURING_DATA_ADT = const(0xff)
_MANUFACTURING_DATA_ADT = const(0xFF)
_ADAFRUIT_COMPANY_ID = const(0x0822)
_SENSOR_DATA_ID = const(0x0002)


class AdafruitSensorMeasurement(Advertisement):
"""Broadcast a single RGB color."""

# This prefix matches all
prefix = struct.pack("<BBH",
3,
_MANUFACTURING_DATA_ADT,
_ADAFRUIT_COMPANY_ID)
prefix = struct.pack("<BBH", 3, _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID)

manufacturer_data = LazyObjectField(ManufacturerData,
"manufacturer_data",
advertising_data_type=_MANUFACTURING_DATA_ADT,
company_id=_ADAFRUIT_COMPANY_ID,
key_encoding="<H")
manufacturer_data = LazyObjectField(
ManufacturerData,
"manufacturer_data",
advertising_data_type=_MANUFACTURING_DATA_ADT,
company_id=_ADAFRUIT_COMPANY_ID,
key_encoding="<H",
)

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

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

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

orientation = ManufacturerDataField(0x0a02, "<fff", ("x", "y", "z"))
orientation = ManufacturerDataField(0x0A02, "<fff", ("x", "y", "z"))
"""Absolution orientation as (x, y, z) tuple of floats in degrees."""

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

temperature = ManufacturerDataField(0x0a04, "<f")
temperature = ManufacturerDataField(0x0A04, "<f")
"""Temperature as a float in degrees centigrade."""

eCO2 = ManufacturerDataField(0x0a05, "<f")
eCO2 = ManufacturerDataField(0x0A05, "<f")
"""Equivalent CO2 as a float in parts per million."""

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

distance = ManufacturerDataField(0x0a07, "<f")
distance = ManufacturerDataField(0x0A07, "<f")
"""Distance as a float in centimeters."""

light = ManufacturerDataField(0x0a08, "<f")
light = ManufacturerDataField(0x0A08, "<f")
"""Brightness as a float without units."""

lux = ManufacturerDataField(0x0a09, "<f")
lux = ManufacturerDataField(0x0A09, "<f")
"""Brightness as a float in SI lux."""

pressure = ManufacturerDataField(0x0a0a, "<f")
pressure = ManufacturerDataField(0x0A0A, "<f")
"""Pressure as a float in hectopascals."""

relative_humidity = ManufacturerDataField(0x0a0b, "<f")
relative_humidity = ManufacturerDataField(0x0A0B, "<f")
"""Relative humidity as a float percentage."""

current = ManufacturerDataField(0x0a0c, "<f")
current = ManufacturerDataField(0x0A0C, "<f")
"""Current as a float in milliamps."""

voltage = ManufacturerDataField(0x0a0d, "<f")
voltage = ManufacturerDataField(0x0A0D, "<f")
"""Voltage as a float in Volts."""

color = ManufacturerDataField(0x0a0e, "<f")
color = ManufacturerDataField(0x0A0E, "<f")
"""Color as RGB integer."""

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

# datetime = ManufacturerDataField(0x0a10, "<f")
"""Date and time as a struct. Not supported."""
# """Date and time as a struct. Not supported."""

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

frequency = ManufacturerDataField(0x0a12, "<f")
frequency = ManufacturerDataField(0x0A12, "<f")
"""As integer Hertz"""

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

weight = ManufacturerDataField(0x0a14, "<f")
weight = ManufacturerDataField(0x0A14, "<f")
"""Weight as a float in grams."""

battery_voltage = ManufacturerDataField(0x0A15, "<H")
"""Battery voltage in millivolts. Saves two bytes over voltage and is more readable in bare
packets."""

def __init__(self, *, sequence_number=None):
super().__init__()
if sequence_number:
Expand All @@ -162,3 +165,29 @@ def __str__(self):
if value is not None:
parts.append("{}={}".format(attr, str(value)))
return "<{} {} >".format(self.__class__.__name__, " ".join(parts))

def split(self, max_packet_size=31):
"""Split the measurement into multiple measurements with the given max_packet_size. Yields
each submeasurement."""
current_size = 8 # baseline for mfg data and sequence number
if current_size + len(self.manufacturer_data) < max_packet_size:
yield self
return

original_data = self.manufacturer_data.data
submeasurement = None
for key in original_data:
value = original_data[key]
entry_size = 2 + len(value)
if not submeasurement or current_size + entry_size > max_packet_size:
if submeasurement:
yield submeasurement
submeasurement = self.__class__()
current_size = 8
submeasurement.manufacturer_data.data[key] = value
current_size += entry_size

if submeasurement:
yield submeasurement

return
Loading