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
4635import adafruit_ble
4736from adafruit_ble .advertising import Advertisement , LazyObjectField
4837from 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
7170class 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