ble_broadcastnet_blinka_bridge.py
1 """This example bridges from BLE to Adafruit IO on a Raspberry Pi.""" 2 from secrets import secrets # pylint: disable=no-name-in-module 3 import time 4 import requests 5 from adafruit_ble.advertising.standard import ManufacturerDataField 6 import adafruit_ble 7 import adafruit_ble_broadcastnet 8 9 aio_auth_header = {"X-AIO-KEY": secrets["aio_key"]} 10 aio_base_url = "https://io.adafruit.com/api/v2/" + secrets["aio_username"] 11 12 13 def aio_post(path, **kwargs): 14 kwargs["headers"] = aio_auth_header 15 return requests.post(aio_base_url + path, **kwargs) 16 17 18 def aio_get(path, **kwargs): 19 kwargs["headers"] = aio_auth_header 20 return requests.get(aio_base_url + path, **kwargs) 21 22 23 # Disable outer names check because we frequently collide. 24 # pylint: disable=redefined-outer-name 25 26 27 def create_group(name): 28 response = aio_post("/groups", json={"name": name}) 29 if response.status_code != 201: 30 print(name) 31 print(response.content) 32 print(response.status_code) 33 raise RuntimeError("unable to create new group") 34 return response.json()["key"] 35 36 37 def create_feed(group_key, name): 38 response = aio_post( 39 "/groups/{}/feeds".format(group_key), json={"feed": {"name": name}} 40 ) 41 if response.status_code != 201: 42 print(name) 43 print(response.content) 44 print(response.status_code) 45 raise RuntimeError("unable to create new feed") 46 return response.json()["key"] 47 48 49 def create_data(group_key, data): 50 response = aio_post("/groups/{}/data".format(group_key), json={"feeds": data}) 51 if response.status_code == 429: 52 print("Throttled!") 53 return False 54 if response.status_code != 200: 55 print(response.status_code, response.json()) 56 raise RuntimeError("unable to create new data") 57 response.close() 58 return True 59 60 61 def convert_to_feed_data(values, attribute_name, attribute_instance): 62 feed_data = [] 63 # Wrap single value entries for enumeration. 64 if not isinstance(values, tuple) or ( 65 attribute_instance.element_count > 1 and not isinstance(values[0], tuple) 66 ): 67 values = (values,) 68 for i, value in enumerate(values): 69 key = attribute_name.replace("_", "-") + "-" + str(i) 70 if isinstance(value, tuple): 71 for j in range(attribute_instance.element_count): 72 feed_data.append( 73 { 74 "key": key + "-" + attribute_instance.field_names[j], 75 "value": value[j], 76 } 77 ) 78 else: 79 feed_data.append({"key": key, "value": value}) 80 return feed_data 81 82 83 ble = adafruit_ble.BLERadio() 84 bridge_address = adafruit_ble_broadcastnet.device_address 85 print("This is BroadcastNet bridge:", bridge_address) 86 print() 87 88 print("Fetching existing feeds.") 89 90 existing_feeds = {} 91 response = aio_get("/groups") 92 for group in response.json(): 93 if "-" not in group["key"]: 94 continue 95 pieces = group["key"].split("-") 96 if len(pieces) != 4 or pieces[0] != "bridge" or pieces[2] != "sensor": 97 continue 98 _, bridge, _, sensor_address = pieces 99 if bridge != bridge_address: 100 continue 101 existing_feeds[sensor_address] = [] 102 for feed in group["feeds"]: 103 feed_key = feed["key"].split(".")[-1] 104 existing_feeds[sensor_address].append(feed_key) 105 106 print(existing_feeds) 107 108 print("scanning") 109 print() 110 sequence_numbers = {} 111 # By providing Advertisement as well we include everything, not just specific advertisements. 112 for measurement in ble.start_scan( 113 adafruit_ble_broadcastnet.AdafruitSensorMeasurement, interval=0.5 114 ): 115 reversed_address = [measurement.address.address_bytes[i] for i in range(5, -1, -1)] 116 sensor_address = "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*reversed_address) 117 if sensor_address not in sequence_numbers: 118 sequence_numbers[sensor_address] = measurement.sequence_number - 1 % 256 119 # Skip if we are getting the same broadcast more than once. 120 if measurement.sequence_number == sequence_numbers[sensor_address]: 121 continue 122 number_missed = measurement.sequence_number - sequence_numbers[sensor_address] - 1 123 if number_missed < 0: 124 number_missed += 256 125 group_key = "bridge-{}-sensor-{}".format(bridge_address, sensor_address) 126 if sensor_address not in existing_feeds: 127 create_group("Bridge {} Sensor {}".format(bridge_address, sensor_address)) 128 create_feed(group_key, "Missed Message Count") 129 existing_feeds[sensor_address] = ["missed-message-count"] 130 131 data = [{"key": "missed-message-count", "value": number_missed}] 132 for attribute in dir(measurement.__class__): 133 attribute_instance = getattr(measurement.__class__, attribute) 134 if issubclass(attribute_instance.__class__, ManufacturerDataField): 135 if attribute != "sequence_number": 136 values = getattr(measurement, attribute) 137 if values is not None: 138 data.extend( 139 convert_to_feed_data(values, attribute, attribute_instance) 140 ) 141 142 for feed_data in data: 143 if feed_data["key"] not in existing_feeds[sensor_address]: 144 create_feed(group_key, feed_data["key"]) 145 existing_feeds[sensor_address].append(feed_data["key"]) 146 147 start_time = time.monotonic() 148 print(group_key, data) 149 # Only update the previous sequence if we logged successfully. 150 if create_data(group_key, data): 151 sequence_numbers[sensor_address] = measurement.sequence_number 152 153 duration = time.monotonic() - start_time 154 print("Done logging measurement to IO. Took {} seconds".format(duration)) 155 print() 156 157 print("scan done")