Skip to content

Commit 5c4aeb2

Browse files
committed
Add support for multiple values per sensor type.
1 parent f2cbf49 commit 5c4aeb2

File tree

4 files changed

+159
-62
lines changed

4 files changed

+159
-62
lines changed

adafruit_ble_broadcastnet.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,27 @@
4343
* Adafruit's BLE library: https://github.com/adafruit/Adafruit_CircuitPython_BLE
4444
"""
4545

46+
import adafruit_ble
4647
from adafruit_ble.advertising import Advertisement, LazyObjectField
4748
from adafruit_ble.advertising.standard import ManufacturerData, ManufacturerDataField
4849
import struct
50+
import time
4951

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

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
64+
65+
device_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*_ble._adapter.address.address_bytes)
66+
5367
_MANUFACTURING_DATA_ADT = const(0xff)
5468
_ADAFRUIT_COMPANY_ID = const(0x0822)
5569
_SENSOR_DATA_ID = const(0x0002)
@@ -71,16 +85,16 @@ class AdafruitSensorMeasurement(Advertisement):
7185
sequence_number = ManufacturerDataField(0x0003, "<B")
7286
"""Sequence number of the measurement. Used to detect missed packets."""
7387

74-
acceleration = ManufacturerDataField(0x0a00, "<fff")
88+
acceleration = ManufacturerDataField(0x0a00, "<fff", ("x", "y", "z"))
7589
"""Acceleration as (x, y, z) tuple of floats in meters per second per second."""
7690

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

80-
orientation = ManufacturerDataField(0x0a02, "<fff")
94+
orientation = ManufacturerDataField(0x0a02, "<fff", ("x", "y", "z"))
8195
"""Absolution orientation as (x, y, z) tuple of floats in degrees."""
8296

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

86100
temperature = ManufacturerDataField(0x0a04, "<f")
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""This is a complex sensor node that uses the sensors on a Clue and Feather Bluefruit Sense."""
2+
3+
import board
4+
import adafruit_bmp280
5+
import adafruit_sht31d
6+
import adafruit_apds9960.apds9960
7+
import adafruit_lis3mdl
8+
import adafruit_lsm6ds
9+
import adafruit_ble_broadcastnet
10+
import time
11+
12+
print("This is BroadcastNet sensor:", adafruit_ble_broadcastnet.device_address)
13+
14+
i2c = board.I2C()
15+
16+
# Define sensors:
17+
# Accelerometer/gyroscope:
18+
lsm6ds = adafruit_lsm6ds.LSM6DS33(i2c)
19+
20+
# Magnetometer:
21+
lis3mdl = adafruit_lis3mdl.LIS3MDL(i2c)
22+
23+
# DGesture/proximity/color/light sensor:
24+
# TODO: How do we get the light level?
25+
# apds9960 = adafruit_apds9960.apds9960.APDS9960(i2c)
26+
# apds9960.enable_color = True
27+
28+
# Humidity sensor:
29+
sht31d = adafruit_sht31d.SHT31D(i2c)
30+
31+
# Barometric pressure sensor:
32+
bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)
33+
34+
last_sht31d_temperature = None
35+
last_sht31d_relative_humidity = None
36+
last_bmp280_temperature = None
37+
last_bmp280_pressure = None
38+
last_lsm6ds_acceleration = None
39+
last_lis3mdl_magnetic = None
40+
41+
while True:
42+
measurement = adafruit_ble_broadcastnet.AdafruitSensorMeasurement()
43+
44+
# Handle the two temperature measurements. We must always provide both because they split into
45+
# feeds based on the position in the field.
46+
sht31d_temperature = round(sht31d.temperature, 0)
47+
bmp280_temperature = round(bmp280.temperature, 0)
48+
if (sht31d_temperature != last_sht31d_temperature or
49+
bmp280_temperature != last_bmp280_temperature):
50+
measurement.temperature = (sht31d_temperature, bmp280_temperature)
51+
last_sht31d_temperature = sht31d_temperature
52+
last_bmp280_temperature = bmp280_temperature
53+
54+
# Relative humidity, rounded to the nearest whole integer.
55+
sht31d_relative_humidity = round(sht31d.relative_humidity, 0)
56+
if sht31d_relative_humidity != last_sht31d_relative_humidity:
57+
measurement.relative_humidity = sht31d_relative_humidity
58+
last_sht31d_relative_humidity = sht31d_relative_humidity
59+
60+
# Pressure, round to one decimal place.
61+
bmp280_pressure = round(bmp280.pressure, 1)
62+
if bmp280_pressure != last_bmp280_pressure:
63+
measurement.pressure = bmp280_pressure
64+
last_bmp280_pressure = bmp280_pressure
65+
66+
# Acceleration, each axis rounded to one decimal place.
67+
lsm6ds_acceleration = tuple([round(axis, 1) for axis in lsm6ds.acceleration])
68+
if lsm6ds_acceleration != last_lsm6ds_acceleration:
69+
measurement.acceleration = lsm6ds_acceleration
70+
last_lsm6ds_acceleration = lsm6ds_acceleration
71+
72+
# Magnetic, each axis rounded to two decimal places.
73+
lis3mdl_magnetic = tuple([round(axis, 2) for axis in lis3mdl.magnetic])
74+
if lis3mdl_magnetic != last_lis3mdl_magnetic:
75+
measurement.magnetic = lis3mdl_magnetic
76+
last_lis3mdl_magnetic = lis3mdl_magnetic
77+
78+
if measurement:
79+
print(measurement)
80+
adafruit_ble_broadcastnet.broadcast(measurement)
81+
time.sleep(10)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""This is a basic sensor node that uses the internal temperature sensor."""
2+
3+
import adafruit_ble_broadcastnet
4+
import microcontroller
5+
import time
6+
7+
print("This is BroadcastNet sensor:", adafruit_ble_broadcastnet.device_address)
8+
9+
last_temperature = None
10+
while True:
11+
temp = microcontroller.cpu.temperature
12+
# Round the temperature to the nearest degree to reduce how often it is
13+
# broadcast.
14+
temp = round(temp, 0)
15+
if temp != last_temperature:
16+
measurement = adafruit_ble_broadcastnet.AdafruitSensorMeasurement()
17+
measurement.temperature = temp
18+
print(measurement)
19+
adafruit_ble_broadcastnet.broadcast(measurement)
20+
last_temperature = temp
21+
time.sleep(10)

examples/ble_broadcastnet_wifi_bridge.py

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,9 @@
66
from adafruit_esp32spi import adafruit_esp32spi
77
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
88
from secrets import secrets
9-
from adafruit_ble import BLERadio
10-
11-
from adafruit_ble.advertising import Advertisement, LazyObjectField
12-
from adafruit_ble.advertising.standard import ManufacturerData, ManufacturerDataField
13-
import struct
14-
import collections
15-
16-
_MANUFACTURING_DATA_ADT = const(0xff)
17-
_ADAFRUIT_COMPANY_ID = const(0x0822)
18-
_SENSOR_DATA_ID = const(0x0002)
19-
20-
class AdafruitSensorMeasurement(Advertisement):
21-
"""Broadcast a single RGB color."""
22-
# This prefix matches all
23-
prefix = struct.pack("<BBH",
24-
3,
25-
_MANUFACTURING_DATA_ADT,
26-
_ADAFRUIT_COMPANY_ID)
27-
28-
manufacturer_data = LazyObjectField(ManufacturerData,
29-
"manufacturer_data",
30-
advertising_data_type=_MANUFACTURING_DATA_ADT,
31-
company_id=_ADAFRUIT_COMPANY_ID,
32-
key_encoding="<H")
33-
34-
sequence_number = ManufacturerDataField(0x0003, "<B")
35-
"""Color to broadcast as RGB integer."""
36-
37-
temperature = ManufacturerDataField(0x0a00, "<f")
38-
"""Color to broadcast as RGB integer."""
39-
40-
def __init__(self, *, sensor_device_id=0xff, sequence_number=0xff):
41-
super().__init__()
42-
# self.sensor_device_id = sensor_device_id
43-
# self.sequence_number = sequence_number
44-
45-
def __str__(self):
46-
parts = []
47-
for attr in dir(self.__class__):
48-
attribute_instance = getattr(self.__class__, attr)
49-
if issubclass(attribute_instance.__class__, ManufacturerDataField):
50-
value = getattr(self, attr)
51-
if value is not None:
52-
parts.append("{}={}".format(attr, str(value)))
53-
return "<{} {} >".format(self.__class__.__name__, " ".join(parts))
9+
from adafruit_ble.advertising.standard import ManufacturerDataField
10+
import adafruit_ble
11+
import adafruit_ble_broadcastnet
5412

5513
esp32_cs = DigitalInOut(board.D13)
5614
esp32_ready = DigitalInOut(board.D11)
@@ -84,6 +42,8 @@ def create_group(name):
8442
def create_feed(group_key, name):
8543
response = aio_post("/groups/{}/feeds".format(group_key), json={"feed": {"name": name}})
8644
if response.status_code != 201:
45+
print(name)
46+
print(response.content)
8747
print(response.status_code)
8848
raise RuntimeError("unable to create new feed")
8949
return response.json()["key"]
@@ -99,20 +59,22 @@ def create_data(group_key, data):
9959
raise RuntimeError("unable to create new data")
10060
response.close()
10161

102-
def convert_to_feed_data(values, attribute_name):
62+
def convert_to_feed_data(values, attribute_name, attribute_instance):
10363
feed_data = []
104-
if not isinstance(values, tuple) or hasattr(values, "_fields"):
64+
# Wrap single value entries for enumeration.
65+
if (not isinstance(values, tuple) or
66+
(attribute_instance.element_count > 1 and not isinstance(values[0], tuple))):
10567
values = (values, )
10668
for i, value in enumerate(values):
107-
key = attribute_name + str(i)
69+
key = attribute_name.replace("_", "-") + "-" + str(i)
10870
if isinstance(value, tuple):
109-
for field in value._fields:
110-
feed_data.append({"key": key + field, "value": getattr(value, field)})
71+
for j in range(attribute_instance.element_count):
72+
feed_data.append({"key": key + "-" + attribute_instance.field_names[j], "value": value[j]})
11173
else:
11274
feed_data.append({"key": key, "value": value})
11375
return feed_data
11476

115-
ble = BLERadio()
77+
ble = adafruit_ble.BLERadio()
11678
address = ble._adapter.address
11779
bridge_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*address.address_bytes)
11880
print("This is BroadcastNet bridge:", bridge_address)
@@ -132,17 +94,17 @@ def convert_to_feed_data(values, attribute_name):
13294

13395
print(existing_feeds)
13496

135-
counter = 0
136-
137-
ble = BLERadio()
13897
print("scanning")
13998
sequence_numbers = {}
14099
# By providing Advertisement as well we include everything, not just specific advertisements.
141-
for measurement in ble.start_scan(AdafruitSensorMeasurement):
100+
for measurement in ble.start_scan(adafruit_ble_broadcastnet.AdafruitSensorMeasurement):
142101
sensor_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*measurement.address.address_bytes)
143-
print(sensor_address, measurement)
144102
if sensor_address not in sequence_numbers:
145103
sequence_numbers[sensor_address] = measurement.sequence_number - 1 % 256
104+
# Skip if we are getting the same broadcast more than once.
105+
if measurement.sequence_number == sequence_numbers[sensor_address]:
106+
continue
107+
print(sensor_address, measurement)
146108
number_missed = measurement.sequence_number - sequence_numbers[sensor_address] - 1
147109
if number_missed < 0:
148110
number_missed += 256
@@ -161,8 +123,8 @@ def convert_to_feed_data(values, attribute_name):
161123
if issubclass(attribute_instance.__class__, ManufacturerDataField):
162124
if attribute != "sequence_number":
163125
values = getattr(measurement, attribute)
164-
165-
data.extend(convert_to_feed_data(values, attribute))
126+
if values is not None:
127+
data.extend(convert_to_feed_data(values, attribute, attribute_instance))
166128

167129
for feed_data in data:
168130
if feed_data["key"] not in existing_feeds[sensor_address]:
@@ -177,3 +139,22 @@ def convert_to_feed_data(values, attribute_name):
177139
print()
178140

179141
print("scan done")
142+
143+
# while True:
144+
# try:
145+
# print("Posting data...", end='')
146+
# data = counter
147+
# response = aio_post("/feeds/test/data", json={'value':data})
148+
# if response.status_code == 404:
149+
# response = aio_post("/feeds", json={'feed': {"name": "test"}})
150+
# print("error", response.status_code)
151+
# print(dir(response), response.socket)
152+
# json = response.json()
153+
# counter = counter + 1
154+
# print("OK")
155+
# except (ValueError, RuntimeError) as e:
156+
# print("Failed to get data, retrying\n", e)
157+
# wifi.reset()
158+
# continue
159+
# response = None
160+
# time.sleep(15)

0 commit comments

Comments
 (0)