27
27
28
28
29
29
* 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
44
30
"""
45
31
32
+ import struct
33
+ import time
34
+ from micropython import const
46
35
import adafruit_ble
47
36
from adafruit_ble .advertising import Advertisement , LazyObjectField
48
37
from adafruit_ble .advertising .standard import ManufacturerData , ManufacturerDataField
49
- import struct
50
- import time
51
38
52
39
__version__ = "0.0.0-auto.0"
53
40
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_BroadcastNet.git"
54
41
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
+
64
58
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."""
66
65
67
- _MANUFACTURING_DATA_ADT = const (0xff )
66
+ _MANUFACTURING_DATA_ADT = const (0xFF )
68
67
_ADAFRUIT_COMPANY_ID = const (0x0822 )
69
- _SENSOR_DATA_ID = const ( 0x0002 )
68
+
70
69
71
70
class AdafruitSensorMeasurement (Advertisement ):
72
71
"""Broadcast a single RGB color."""
72
+
73
73
# 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 )
78
75
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
+ )
84
83
85
84
sequence_number = ManufacturerDataField (0x0003 , "<B" )
86
85
"""Sequence number of the measurement. Used to detect missed packets."""
87
86
88
- acceleration = ManufacturerDataField (0x0a00 , "<fff" , ("x" , "y" , "z" ))
87
+ acceleration = ManufacturerDataField (0x0A00 , "<fff" , ("x" , "y" , "z" ))
89
88
"""Acceleration as (x, y, z) tuple of floats in meters per second per second."""
90
89
91
- magnetic = ManufacturerDataField (0x0a01 , "<fff" , ("x" , "y" , "z" ))
90
+ magnetic = ManufacturerDataField (0x0A01 , "<fff" , ("x" , "y" , "z" ))
92
91
"""Magnetism as (x, y, z) tuple of floats in micro-Tesla."""
93
92
94
- orientation = ManufacturerDataField (0x0a02 , "<fff" , ("x" , "y" , "z" ))
93
+ orientation = ManufacturerDataField (0x0A02 , "<fff" , ("x" , "y" , "z" ))
95
94
"""Absolution orientation as (x, y, z) tuple of floats in degrees."""
96
95
97
- gyro = ManufacturerDataField (0x0a03 , "<fff" , ("x" , "y" , "z" ))
96
+ gyro = ManufacturerDataField (0x0A03 , "<fff" , ("x" , "y" , "z" ))
98
97
"""Gyro motion as (x, y, z) tuple of floats in radians per second."""
99
98
100
- temperature = ManufacturerDataField (0x0a04 , "<f" )
99
+ temperature = ManufacturerDataField (0x0A04 , "<f" )
101
100
"""Temperature as a float in degrees centigrade."""
102
101
103
- eCO2 = ManufacturerDataField (0x0a05 , "<f" )
102
+ eCO2 = ManufacturerDataField (0x0A05 , "<f" )
104
103
"""Equivalent CO2 as a float in parts per million."""
105
104
106
- TVOC = ManufacturerDataField (0x0a06 , "<f" )
105
+ TVOC = ManufacturerDataField (0x0A06 , "<f" )
107
106
"""Total Volatile Organic Compounds as a float in parts per billion."""
108
107
109
- distance = ManufacturerDataField (0x0a07 , "<f" )
108
+ distance = ManufacturerDataField (0x0A07 , "<f" )
110
109
"""Distance as a float in centimeters."""
111
110
112
- light = ManufacturerDataField (0x0a08 , "<f" )
111
+ light = ManufacturerDataField (0x0A08 , "<f" )
113
112
"""Brightness as a float without units."""
114
113
115
- lux = ManufacturerDataField (0x0a09 , "<f" )
114
+ lux = ManufacturerDataField (0x0A09 , "<f" )
116
115
"""Brightness as a float in SI lux."""
117
116
118
- pressure = ManufacturerDataField (0x0a0a , "<f" )
117
+ pressure = ManufacturerDataField (0x0A0A , "<f" )
119
118
"""Pressure as a float in hectopascals."""
120
119
121
- relative_humidity = ManufacturerDataField (0x0a0b , "<f" )
120
+ relative_humidity = ManufacturerDataField (0x0A0B , "<f" )
122
121
"""Relative humidity as a float percentage."""
123
122
124
- current = ManufacturerDataField (0x0a0c , "<f" )
123
+ current = ManufacturerDataField (0x0A0C , "<f" )
125
124
"""Current as a float in milliamps."""
126
125
127
- voltage = ManufacturerDataField (0x0a0d , "<f" )
126
+ voltage = ManufacturerDataField (0x0A0D , "<f" )
128
127
"""Voltage as a float in Volts."""
129
128
130
- color = ManufacturerDataField (0x0a0e , "<f" )
129
+ color = ManufacturerDataField (0x0A0E , "<f" )
131
130
"""Color as RGB integer."""
132
131
133
132
# 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."""
135
134
136
135
# datetime = ManufacturerDataField(0x0a10, "<f")
137
- """Date and time as a struct. Not supported."""
136
+ # """Date and time as a struct. Not supported."""
138
137
139
- duty_cycle = ManufacturerDataField (0x0a11 , "<f" )
138
+ duty_cycle = ManufacturerDataField (0x0A11 , "<f" )
140
139
"""16-bit PWM duty cycle. Independent of frequency."""
141
140
142
- frequency = ManufacturerDataField (0x0a12 , "<f" )
141
+ frequency = ManufacturerDataField (0x0A12 , "<f" )
143
142
"""As integer Hertz"""
144
143
145
- value = ManufacturerDataField (0x0a13 , "<f" )
144
+ value = ManufacturerDataField (0x0A13 , "<f" )
146
145
"""16-bit unit-less value. Used for analog values and for booleans."""
147
146
148
- weight = ManufacturerDataField (0x0a14 , "<f" )
147
+ weight = ManufacturerDataField (0x0A14 , "<f" )
149
148
"""Weight as a float in grams."""
150
149
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
+
151
154
def __init__ (self , * , sequence_number = None ):
152
155
super ().__init__ ()
153
156
if sequence_number :
@@ -162,3 +165,29 @@ def __str__(self):
162
165
if value is not None :
163
166
parts .append ("{}={}" .format (attr , str (value )))
164
167
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