├── main.py ├── LICENSE ├── README.md ├── ble_advertising.py ├── ble_simple_peripheral.py └── ble_simple_central.py /main.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, ADC 2 | import bluetooth 3 | from ble_simple_peripheral import BLESimplePeripheral 4 | import time 5 | 6 | # Create a Bluetooth Low Energy (BLE) object 7 | ble = bluetooth.BLE() 8 | 9 | # Create an instance of the BLESimplePeripheral class with the BLE object 10 | sp = BLESimplePeripheral(ble) 11 | adc = ADC(4) 12 | 13 | while True: 14 | if sp.is_connected(): # Check if a BLE connection is established 15 | # Read the value from the internal temperature sensor 16 | temperature = adc.read_u16() * 3.3 / (65535 * 0.8) 17 | 18 | # Transmit the temperature value over BLE 19 | temperature_data = str(temperature).encode() 20 | sp.send(temperature_data) 21 | time.sleep(1) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Make Use Of 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MicroPython Bluetooth Examples and Modules for Raspberry Pi Pico W/WH 3 | 4 | This repository contains a collection of MicroPython examples and modules for utilizing Bluetooth on the Raspberry Pi Pico W and WH. These resources aim to assist you in leveraging the Bluetooth capabilities of the board for your projects. 5 | 6 | ## Article: How to Read Sensor Values on Raspberry Pi Pico W Using Bluetooth 7 | 8 | To complement this repository, we have an article that demonstrates [how to read sensor values on the Raspberry Pi Pico W using Bluetooth](https://www.makeuseof.com/raspberry-pi-pico-w-read-sensor-using-bluetooth/). It provides a step-by-step guide to help you understand and implement Bluetooth communication with sensors on the board. 9 | 10 | Please refer to the article for a detailed walkthrough on using Bluetooth with the Raspberry Pi Pico W. 11 | 12 | ## Repository Contents 13 | 14 | 1. `examples/`: This directory contains various MicroPython code examples that showcase different aspects of Bluetooth functionality on the Raspberry Pi Pico W/WH. 15 | 16 | 2. `modules/`: Here, you'll find reusable MicroPython modules that provide simplified functions and utilities for Bluetooth-related tasks on the board. 17 | 18 | Feel free to explore the code examples and modules provided in this repository. They serve as starting points for integrating Bluetooth features into your own projects on the Raspberry Pi Pico W/WH. 19 | 20 | ## Contributing 21 | 22 | We welcome contributions to this repository. If you have additional examples, modules, or improvements to share, please submit a pull request. Let's collaborate and expand the resources available for the MicroPython community on the Raspberry Pi Pico W/WH. 23 | 24 | 25 | If you have any questions or feedback, please don't hesitate to reach out. Happy coding with MicroPython and Bluetooth on the Raspberry Pi Pico W/WH! 26 | 27 | *Note: For the detailed guide on reading sensor values with Bluetooth, please refer to the associated article.* -------------------------------------------------------------------------------- /ble_advertising.py: -------------------------------------------------------------------------------- 1 | from micropython import const 2 | import struct 3 | import bluetooth 4 | 5 | _ADV_TYPE_FLAGS = const(0x01) 6 | _ADV_TYPE_NAME = const(0x09) 7 | _ADV_TYPE_UUID16_COMPLETE = const(0x3) 8 | _ADV_TYPE_UUID32_COMPLETE = const(0x5) 9 | _ADV_TYPE_UUID128_COMPLETE = const(0x7) 10 | _ADV_TYPE_UUID16_MORE = const(0x2) 11 | _ADV_TYPE_UUID32_MORE = const(0x4) 12 | _ADV_TYPE_UUID128_MORE = const(0x6) 13 | _ADV_TYPE_APPEARANCE = const(0x19) 14 | 15 | 16 | # Generate a payload to be passed to gap_advertise(adv_data=...). 17 | def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0): 18 | payload = bytearray() 19 | 20 | def _append(adv_type, value): 21 | nonlocal payload 22 | payload += struct.pack("BB", len(value) + 1, adv_type) + value 23 | 24 | _append( 25 | _ADV_TYPE_FLAGS, 26 | struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)), 27 | ) 28 | 29 | if name: 30 | _append(_ADV_TYPE_NAME, name) 31 | 32 | if services: 33 | for uuid in services: 34 | b = bytes(uuid) 35 | if len(b) == 2: 36 | _append(_ADV_TYPE_UUID16_COMPLETE, b) 37 | elif len(b) == 4: 38 | _append(_ADV_TYPE_UUID32_COMPLETE, b) 39 | elif len(b) == 16: 40 | _append(_ADV_TYPE_UUID128_COMPLETE, b) 41 | 42 | if appearance: 43 | _append(_ADV_TYPE_APPEARANCE, struct.pack(" 0 70 | 71 | def _advertise(self, interval_us=500000): 72 | print("Starting advertising") 73 | self._ble.gap_advertise(interval_us, adv_data=self._payload) 74 | 75 | def on_write(self, callback): 76 | self._write_callback = callback 77 | 78 | 79 | def demo(): 80 | ble = bluetooth.BLE() 81 | p = BLESimplePeripheral(ble) 82 | 83 | def on_rx(v): 84 | print("RX", v) 85 | 86 | p.on_write(on_rx) 87 | 88 | i = 0 89 | while True: 90 | if p.is_connected(): 91 | # Short burst of queued notifications. 92 | for _ in range(3): 93 | data = str(i) + "_" 94 | print("TX", data) 95 | p.send(data) 96 | i += 1 97 | time.sleep_ms(100) 98 | 99 | 100 | if __name__ == "__main__": 101 | demo() -------------------------------------------------------------------------------- /ble_simple_central.py: -------------------------------------------------------------------------------- 1 | # This example finds and connects to a peripheral running the 2 | # UART service (e.g. ble_simple_peripheral.py). 3 | 4 | import bluetooth 5 | import random 6 | import struct 7 | import time 8 | import micropython 9 | 10 | from ble_advertising import decode_services, decode_name 11 | 12 | from micropython import const 13 | 14 | _IRQ_CENTRAL_CONNECT = const(1) 15 | _IRQ_CENTRAL_DISCONNECT = const(2) 16 | _IRQ_GATTS_WRITE = const(3) 17 | _IRQ_GATTS_READ_REQUEST = const(4) 18 | _IRQ_SCAN_RESULT = const(5) 19 | _IRQ_SCAN_DONE = const(6) 20 | _IRQ_PERIPHERAL_CONNECT = const(7) 21 | _IRQ_PERIPHERAL_DISCONNECT = const(8) 22 | _IRQ_GATTC_SERVICE_RESULT = const(9) 23 | _IRQ_GATTC_SERVICE_DONE = const(10) 24 | _IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) 25 | _IRQ_GATTC_CHARACTERISTIC_DONE = const(12) 26 | _IRQ_GATTC_DESCRIPTOR_RESULT = const(13) 27 | _IRQ_GATTC_DESCRIPTOR_DONE = const(14) 28 | _IRQ_GATTC_READ_RESULT = const(15) 29 | _IRQ_GATTC_READ_DONE = const(16) 30 | _IRQ_GATTC_WRITE_DONE = const(17) 31 | _IRQ_GATTC_NOTIFY = const(18) 32 | _IRQ_GATTC_INDICATE = const(19) 33 | 34 | _ADV_IND = const(0x00) 35 | _ADV_DIRECT_IND = const(0x01) 36 | _ADV_SCAN_IND = const(0x02) 37 | _ADV_NONCONN_IND = const(0x03) 38 | 39 | _UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") 40 | _UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") 41 | _UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") 42 | 43 | 44 | class BLESimpleCentral: 45 | def __init__(self, ble): 46 | self._ble = ble 47 | self._ble.active(True) 48 | self._ble.irq(self._irq) 49 | 50 | self._reset() 51 | 52 | def _reset(self): 53 | # Cached name and address from a successful scan. 54 | self._name = None 55 | self._addr_type = None 56 | self._addr = None 57 | 58 | # Callbacks for completion of various operations. 59 | # These reset back to None after being invoked. 60 | self._scan_callback = None 61 | self._conn_callback = None 62 | self._read_callback = None 63 | 64 | # Persistent callback for when new data is notified from the device. 65 | self._notify_callback = None 66 | 67 | # Connected device. 68 | self._conn_handle = None 69 | self._start_handle = None 70 | self._end_handle = None 71 | self._tx_handle = None 72 | self._rx_handle = None 73 | 74 | def _irq(self, event, data): 75 | if event == _IRQ_SCAN_RESULT: 76 | addr_type, addr, adv_type, rssi, adv_data = data 77 | if adv_type in (_ADV_IND, _ADV_DIRECT_IND) and _UART_SERVICE_UUID in decode_services( 78 | adv_data 79 | ): 80 | # Found a potential device, remember it and stop scanning. 81 | self._addr_type = addr_type 82 | self._addr = bytes( 83 | addr 84 | ) # Note: addr buffer is owned by caller so need to copy it. 85 | self._name = decode_name(adv_data) or "?" 86 | self._ble.gap_scan(None) 87 | 88 | elif event == _IRQ_SCAN_DONE: 89 | if self._scan_callback: 90 | if self._addr: 91 | # Found a device during the scan (and the scan was explicitly stopped). 92 | self._scan_callback(self._addr_type, self._addr, self._name) 93 | self._scan_callback = None 94 | else: 95 | # Scan timed out. 96 | self._scan_callback(None, None, None) 97 | 98 | elif event == _IRQ_PERIPHERAL_CONNECT: 99 | # Connect successful. 100 | conn_handle, addr_type, addr = data 101 | if addr_type == self._addr_type and addr == self._addr: 102 | self._conn_handle = conn_handle 103 | self._ble.gattc_discover_services(self._conn_handle) 104 | 105 | elif event == _IRQ_PERIPHERAL_DISCONNECT: 106 | # Disconnect (either initiated by us or the remote end). 107 | conn_handle, _, _ = data 108 | if conn_handle == self._conn_handle: 109 | # If it was initiated by us, it'll already be reset. 110 | self._reset() 111 | 112 | elif event == _IRQ_GATTC_SERVICE_RESULT: 113 | # Connected device returned a service. 114 | conn_handle, start_handle, end_handle, uuid = data 115 | print("service", data) 116 | if conn_handle == self._conn_handle and uuid == _UART_SERVICE_UUID: 117 | self._start_handle, self._end_handle = start_handle, end_handle 118 | 119 | elif event == _IRQ_GATTC_SERVICE_DONE: 120 | # Service query complete. 121 | if self._start_handle and self._end_handle: 122 | self._ble.gattc_discover_characteristics( 123 | self._conn_handle, self._start_handle, self._end_handle 124 | ) 125 | else: 126 | print("Failed to find uart service.") 127 | 128 | elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: 129 | # Connected device returned a characteristic. 130 | conn_handle, def_handle, value_handle, properties, uuid = data 131 | if conn_handle == self._conn_handle and uuid == _UART_RX_CHAR_UUID: 132 | self._rx_handle = value_handle 133 | if conn_handle == self._conn_handle and uuid == _UART_TX_CHAR_UUID: 134 | self._tx_handle = value_handle 135 | 136 | elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: 137 | # Characteristic query complete. 138 | if self._tx_handle is not None and self._rx_handle is not None: 139 | # We've finished connecting and discovering device, fire the connect callback. 140 | if self._conn_callback: 141 | self._conn_callback() 142 | else: 143 | print("Failed to find uart rx characteristic.") 144 | 145 | elif event == _IRQ_GATTC_WRITE_DONE: 146 | conn_handle, value_handle, status = data 147 | print("TX complete") 148 | 149 | elif event == _IRQ_GATTC_NOTIFY: 150 | conn_handle, value_handle, notify_data = data 151 | if conn_handle == self._conn_handle and value_handle == self._tx_handle: 152 | if self._notify_callback: 153 | self._notify_callback(notify_data) 154 | 155 | # Returns true if we've successfully connected and discovered characteristics. 156 | def is_connected(self): 157 | return ( 158 | self._conn_handle is not None 159 | and self._tx_handle is not None 160 | and self._rx_handle is not None 161 | ) 162 | 163 | # Find a device advertising the environmental sensor service. 164 | def scan(self, callback=None): 165 | self._addr_type = None 166 | self._addr = None 167 | self._scan_callback = callback 168 | self._ble.gap_scan(2000, 30000, 30000) 169 | 170 | # Connect to the specified device (otherwise use cached address from a scan). 171 | def connect(self, addr_type=None, addr=None, callback=None): 172 | self._addr_type = addr_type or self._addr_type 173 | self._addr = addr or self._addr 174 | self._conn_callback = callback 175 | if self._addr_type is None or self._addr is None: 176 | return False 177 | self._ble.gap_connect(self._addr_type, self._addr) 178 | return True 179 | 180 | # Disconnect from current device. 181 | def disconnect(self): 182 | if self._conn_handle is None: 183 | return 184 | self._ble.gap_disconnect(self._conn_handle) 185 | self._reset() 186 | 187 | # Send data over the UART 188 | def write(self, v, response=False): 189 | if not self.is_connected(): 190 | return 191 | self._ble.gattc_write(self._conn_handle, self._rx_handle, v, 1 if response else 0) 192 | 193 | # Set handler for when data is received over the UART. 194 | def on_notify(self, callback): 195 | self._notify_callback = callback 196 | 197 | 198 | def demo(): 199 | ble = bluetooth.BLE() 200 | central = BLESimpleCentral(ble) 201 | 202 | not_found = False 203 | 204 | def on_scan(addr_type, addr, name): 205 | if addr_type is not None: 206 | print("Found peripheral:", addr_type, addr, name) 207 | central.connect() 208 | else: 209 | nonlocal not_found 210 | not_found = True 211 | print("No peripheral found.") 212 | 213 | central.scan(callback=on_scan) 214 | 215 | # Wait for connection... 216 | while not central.is_connected(): 217 | time.sleep_ms(100) 218 | if not_found: 219 | return 220 | 221 | print("Connected") 222 | 223 | def on_rx(v): 224 | print("RX", v) 225 | 226 | central.on_notify(on_rx) 227 | 228 | with_response = False 229 | 230 | i = 0 231 | while central.is_connected(): 232 | try: 233 | v = str(i) + "_" 234 | print("TX", v) 235 | central.write(v, with_response) 236 | except: 237 | print("TX failed") 238 | i += 1 239 | time.sleep_ms(400 if with_response else 30) 240 | 241 | print("Disconnected") 242 | 243 | 244 | if __name__ == "__main__": 245 | demo() --------------------------------------------------------------------------------