Skip to content

6. Bluetooth Connection

6-1. Introduction

In this sub-page, I will document my struggle in the app development and interconnection between the BluetoothLE central and peripheral devices.

[Note at the end of Week 14] In Week 13, where I mainly featured my experience in Xiao ESP32C3 via Arduino IDE, I learned how to have my peripheral board send out a beacon for BLE connection (See Individual Assignment for Week 13). Then in Week 14, I spent most of my time for individual assignment, developing the user interface with the MIT App Inventor. As of the end of Week 14, I could only do the BLE connection test twice with Raspberry Pi Pico W RP2040 board and Xiao ESP32C3 board. Despite a few technical glitches, it seemed that I had better stick to Xiao ESP32C3 as my main board (See Individual Assignment for Week14).

My design idea as of the beginning of the Week 13 was that:

  • Once the central and peripheral devices lose the BLE connection, the central (Mobile App) will play the siren and the peripheral (Alarm Device) will also start playing the siren.

  • Then the user (mobile phone holder) could send a signal message to the device to stop the siren.

However, here I realized that once they lose the BLE connection, there would be no way for the mobile phone holder to remote-control the device to turn off the siren of the Alarm Device. So I thought I should try the alternative approaches.

Then I should think of the first set-up of the central-peripheral connection. At this point of time, as soon as the passport-holder loads batteries on the device, it automatically starts playing siren and it keeps making noise until the user completes the Bluetooth connection set-up. Therefore, the users have to set up the Bluetooth connection as quickly as possible and for that reason, the operations on the mobile app should be as simple as possible.

So the design of the TOP page was made very simple: “Scan”, “Stop Scan”, “Connect” and “Disconnect”. Users are supposed to scan the RSSI (received signal strength indicators) and identify the device they want to connect. Then they stop scanning and press the “Connect” button. After the mobile phone and the device are paired up first and if they are disconnected, the app starts playing the siren on the central side and moves to the Second page, where the users could stop the siren of their mobile phone.

alt text

alt text

I added the “Disconnect” button to the TOP page in case the users want to know where the theire device is even it is located within the Bluetooth accessible range.

With these considerations, I have reached the final design as shown in the following two screenshots.

alt text alt text

[Note in the beginning of Week 17] Since I decided on my Final Project agenda, I had been thining of using the Bluetooth Low Energy (BLE) as a media for communication between the device attached to the passport and the mobile phone which many believe would be the last gadget we would leave behind.

For that reason, I spent the whole week in Week 14 “Interface and Application Programming” by developing a mobile app that could help users to set up the BLE connection with their device.

However, I left a few problems unsolved by the end of that week and they persisted as a problem until late May.

  • Length of Siren: With the first code, I was not successful in making it sound longer than 2 seconds. After a series of trials and errors, I finally came up with a solution. Instead of using just one “Play Siren” block, I found the “Player” icon and integrated the two blocks.

alt text

  • Failure in the Second Connection and Later: This problem persisted much longer. What I found was that even if we could disconnect the BLE peripheral device from the central device on the app, the peripheral kept on indicating that the connection was still effective. Therefore, I decided to take this for granted so that the disconnection could be made only when we switch off the battery on the Alarm Device side. Then if we switch on the device once again, we could repeat the connection procedure on the TOP page.

  • Design Failure on the TOP Page: This problem persisted longer, too. But I once stepped back and looked at the things in much broader perspective, I thought I should change the way I looked at the problem. (Also, later I found that this design failure also occurred to the Second page.) Therefore, instead of struggling to leave the label on the top, I decided to add another label under the top label. This solution worked.

alt text alt text

alt text alt text

If I may add a few more episodes on the design of the app interface, you could see a few slight changes from the first design I developed in Week 17.

  • Logo and Icon: I wanted to make the background color of the logos from white to transparent. So I went back to the Computer-Assisted Design of Week 2 and spent time to eraze the background with Adobe Photoshop. This technique also helped me when I made a slide for the Final Project presentation.

6-2. Another Issue: Central vs. Peripheral

During Week 17, I came back to this BLE connection issue in order to write a code to read the RSSI (Received Signal Strength Indicator) on the Alarm Device side. I need to write a program in such a way that if the RSSI goes below the threshold dBm, the devices starts playing a buzzer loudly. Then I need to set the target threshold that indicates the passport (and the device) goes more than 10 meters away.

Then I wondered whether this device is a central BLE deivce or a peripheral device. Our common sense will be that the mobile is a central device and the anti-theft alarm is peripheral. If this is the case, I could read the signal strength on the mobile app interface I see when I search for the device advertizing.

6-2-1. Mobile (Central) / Alarm Device (Peripheral)

I also investigated whether the peripheral device, anti-theft alarm system in this case, could read the RSSI. Unfortunately I found that the peripheral device does not usually read the RSSI. Then what I could do to the device would be either embedding a program that the device would react to the trigger signal sent by the central device, or coming up with an alternative trigger on the device side alone.

I was not sure whether I could improve the existing app in a week or two before the Final Project presentation so that it could read the RSSI and give us warning about the weak signal strength while sending a wireless message to the BLE peripheral device to turn on the buzzer on the device side as well.

Scenario Mobile Alarm Device Actions to be Taken / Issues to be Addressed
1 BLE Central BLE Peripheral - Revise an mobile app to read the RSSI and send a message to turn on the buzzer.
- Need to check if the peripheral device could read the RSSI, too.
2 BLE Peripheral BLE Central - Still not sure if the could device could also read the RSSI. (Haven’t found a suitable code.)
- Need to write a code that could declare that it’s a peripheral device.

6-2-2. Alarm Device (Central) / Mobile (Peripheral)

I wondered if I could look at the alarm device as a BLE central device and the mobile phone as a peripheral device. Then the alarm device could read the RSSI and turn on the buzzer once it crosses the threshold dBm. I found this could be possible. The following two source codes are available for MicroPython.

ble_advertising.py

ble_simple_central.py

On the mobile side, I downloaded the nRF Connect app so that my mobile could advertise. It started advertizing. However, when I uploaded the above py files to Pico W, I found that it didn’t pick up any advertisement.

alt text


6-3. Conclusion for Final Project

So far, I have tried the above two possibilities described in Section 6-2 above. If I had enough time, I would have explored the second option. But it looked like a daunting task.

I have chosen to explore the first option, staying with the already developed app. The function to read the RSSI on the peripheral side is dropped. Instead, another function should be attached to the central device:

  1. Always monitor the RSSI sent from the peripheral;

  2. If the RSSI goes below the threshold dBm: (i) Play siren inside the central device (Mobile); and (ii) Send a signal to the peripheral device to turn on the buzzer (Alarm Device).

  3. There should be two switches to turn off the siren/buzzer: (i) Turn off the siren in the central device (Mobile); and (ii) Turn off the buzzer in the peripheral device (Alarm Device).

  4. There is no way for the peripheral device side alone to turn off the siren. This will make the theif panicked.


6-4. Final Code Blocks

In conclusion, the following are the set of the code blocks for the mobile app developed with the MIT App Inventor:

6-4-1. TOP Page

The TOP page is basically for setting up the connection via BluetoothLE and I developed the code blocks based on the tutorial available on the MIT App Inventor sites:

  1. Tutorial “MIT App Inventor IoT BLE Basic Connection

  2. Downloading Extension “Bluetooth low energy”

  3. Definition of the BLE Blocks

Initialize: As soon as the TOP page is called, it cancels the previous BLE connection. However, I have to confess that this block is not working after we resume the normal mode after the alarm sequence described in the Second page. I found this when I saw the console of the Thonny IDE.

alt text

Scan: The second block is for the button to start scanning the BLE advertising devices. Also, with another block, I programmed the listing of the BLE devices advertising.

alt text

alt text

Stop Scan and Connect: The next blocks are for the buttons to stop scanning and to set up the BLE connection with the BLE peripheral device we are looking for.

alt text

alt text

Read the RSSI: Once we get the BLE connection, we have to monitor the RSSI changes. For this to happen, I prepared this code block so that the RSSI continues to be indicated in the separate window.

alt text

Move to the Second Page: Once the RSSI reading goes down and crosses the threshold of -70 dBm, proxy of 10 meter, then the app opens the Second page to control sirens both on the central and peripheral devices.

alt text

Disconnection: When we want to move to the Second page manually, we tap this button, no matter if you are connected to any BLE peripheral device. However, this doesn’t mean that the peripheral device is disconnected by this button. I need time to rectify this issue.

alt text

6-4-2. Second Page “Siren On”

Global Variables: In the following sets of code blocks, we have to input the same data twice. In order to avoid typing mistakes, I have used the global variable blocks for MAC address, Service UUID and Characteristics UUID of our peripheral device board (Raspberry Pi PicoW RP2040). Perhaps the UUIDs will be the same across all the PicoW boards. But the MAC address varies from one device to another.

alt text

I learned this when I tried to do the connection test with another PicoW board. I failed again and again and had no idea on what was wrong with it. I found the fact that each PicoW board had its unique MAC address when I saw there were two BLE devices on the scanned BLE list which have the same device name. I learned that they had different MAC addresses.

Therefore one big issue on my program is how the users could type their unique MAC address in the app. I gave up this for the Final Project presentation. But this is a very critical issue for this system to be more user-friendly.

Initialize: I was not sure if this would work, but I placed the block for the BLE connection first with the device by the specific MAC address. Then the next two blocks are for playing the siren on the Mobile phone side as mentioned in 6-1 above. Also, I made sure that all the buttons and indicators were working by the next four blocks.

alt text

Siren On for Alarm Device: After the initial procedure above, the BLE central device starts reading the RSSIs on this page. Once the RSSI reading crosses the -70 dBm threshold, it will send the text message to the BLE peripheral device to turn on the buzzer on the Alarm Device. Also, it keeps reading the RSSIs so that the users could check the distance of the Alarm Device from them. This will help the users to find their valuable or get it back.

alt text

Once the buzzer starts playing, there is no way to stop it on the Alarm Device side alone. Also, no matter if the RSSI reading returns within the comfortable range, the buzzer beat will not stop. After a few struggle on the design of this sequence, my conclusion was that I drop this automatic on/off switching from this code block.

Siren Off for Mobile: I prepared the code blocks to turn off the siren sound separately for the Mobile phone. It’s because it’s annoying to keep playing it.

alt text

Siren Off for Alarm Device: When the users think they could stop the buzzer on the Alarm Device, they could tap the Device-Stop Siren” button to turn off.

alt text

This is the most difficult part of my app development process. Although it looks almost like a slight modification of the “Siren On” block, it took me the longest time before I came to the conclusion that I could leave it blank for the text value block to send a message to the peripheral. This is the core step of this app.

Return to the TOP Page: When the users think it’s okay to close the “Siren On” sequence, they could tap this button and close the screen. Since the TOP page is still functioning in the back end, they could automatically return to the TOP page.

alt text

6-4-3. Precaution

Due to the time we were allowed to spend for the Final Project for Fab Academy and also the last-minute rush to the presentation, I could not take much time for the further exploration for the improvement of the app. At this point of time at the end of the Final Project, there still exist a lot of issues on the reliability and operability of the mobile app. I hereby list up the issues I left unexplored on this app.

  • Actions During the Sleep Mode: While I was developing the app, I observed that once the Mobile screen blacked out, it automatically stopped playing siren sound. And once I recovered from the sleep mode, the siren sound resumed. Therefore, after I wrote the above details of my final codes, I did a small test: What if my smartphone enters the sleep mode after it’s connected to the BLE peripheral device? Strangely enough, I got mixed results. I found that the phone was continuing the RSSI reading even during the sleep mode. Once the RSSI crossed the -70 dBm threshold, both the siren of the mobile and the buzzer of the Alarm Device started making sounds. However, when I tried to recover from the sleep mode, I faced mixed results: In one circumistances, the recovered TOP page was inactive and I couldn’t tap any of the buttons. Also, it sounded as if a few Second pages were called and started playing sirens, ending up with the mixed siren sound. Again I couldn’t close the windows. In other circumstances, I could call the Second page, but the “Mobile-Stop Siren” button was inactive. In still other circumstances, I could tap the “Mobile-Stop Siren” button and stop the siren on my mobile. But I found that the “Device-Stop Siren” button was still inactive and I couldn’t stop the buzzer on the Alarm Device. I haven’t found the reason for this unexpected reaction of the app.

  • Debugging of the MIT App Inventor: Because of the problems I faced during the test described above, I opened the MIT App Inventor IDE to review my program. Then I found that there were fewer icons of the Built-in Blocks in the Block Palette. In addition, I saw many icons in the Block Palette under the Layout, Media and Sensors, were also missing. That made it impossible to revise my program. Even for the exisiting icons, such as BluetoothLE, List and Clock, although their icons were not missing, I was not able to call all the block options by clicking the respective icons in the Block Palette. I searched for the solutions for debugging but was not successful.

alt text alt text

If you are interested in my app and undertake any review and remixing my original code to serve for your own purpose, please download the following ABB file: Data (ABB file)


6-5. MicroPython Code for BLE Peripheral

On the BLE peripheral side (Alarm Device), I decided to work with the Raspberry Pi Pico W RP2040 board to control the actions. I already documented the code set required to run the alarm system in the Week 13 Networking and Communications.

The system consists of three codes:

  1. ble_simple_peripheral.py;

  2. ble_advertising.py;

  3. main.py.

All the three py files should be saved in the Alarm Device and main.py should be treated as the main file for the operations. Once they are saved in the device, you turn on the battery switches. Then the main file will automatically be activated and the device will start advertising.

6-5-1. ble_simple_peripheral.py

# This example demonstrates a UART periperhal.

import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

_FLAG_READ = const(0x0002)
_FLAG_WRITE_NO_RESPONSE = const(0x0004)
_FLAG_WRITE = const(0x0008)
_FLAG_NOTIFY = const(0x0010)

_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
    bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_READ | _FLAG_NOTIFY,
)
_UART_RX = (
    bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (
    _UART_UUID,
    (_UART_TX, _UART_RX),
)


class BLESimplePeripheral:
    def __init__(self, ble, name="kyamada"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
        self._connections = set()
        self._write_callback = None
        self._payload = advertising_payload(name=name, services=[_UART_UUID])
        self._advertise()

    def _irq(self, event, data):
        # Track connections so we can send notifications.
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            print("New connection", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            print("Disconnected", conn_handle)
            self._connections.remove(conn_handle)
            # Start advertising again to allow a new connection.
            self._advertise()
        elif event == _IRQ_GATTS_WRITE:
            conn_handle, value_handle = data
            value = self._ble.gatts_read(value_handle)
            if value_handle == self._handle_rx and self._write_callback:
                self._write_callback(value)

    def send(self, data):
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._handle_tx, data)

    def is_connected(self):
        return len(self._connections) > 0

    def _advertise(self, interval_us=500000):
        print("Starting advertising")
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

    def on_write(self, callback):
        self._write_callback = callback


def demo():
    ble = bluetooth.BLE()
    p = BLESimplePeripheral(ble)

    def on_rx(v):
        print("RX", v)

    p.on_write(on_rx)

    i = 0
    while True:
        if p.is_connected():
            # Short burst of queued notifications.
            for _ in range(3):
                data = str(i) + "_"
                #print("TX", data)
                p.send(data)
                i += 1
        time.sleep_ms(100)


if __name__ == "__main__":
    demo()

Almost all the code is as par the sample sketch, except for the change of the device name from default “mpy-uart” to my original “kyamada”.

Also, the service UUID and characteristic UUID for the MIT App Inventor should be the same as the above MicroPython code.

6-5-2. ble_advertising.py

# Helpers for generating BLE advertising payloads.

from micropython import const
import struct
import bluetooth

# Advertising payloads are repeated packets of the following form:
#   1 byte data length (N + 1)
#   1 byte type (see constants below)
#   N bytes type-specific data

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)


# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
    payload = bytearray()

    def _append(adv_type, value):
        nonlocal payload
        payload += struct.pack("BB", len(value) + 1, adv_type) + value

    _append(
        _ADV_TYPE_FLAGS,
        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
    )

    if name:
        _append(_ADV_TYPE_NAME, name)

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                _append(_ADV_TYPE_UUID16_COMPLETE, b)
            elif len(b) == 4:
                _append(_ADV_TYPE_UUID32_COMPLETE, b)
            elif len(b) == 16:
                _append(_ADV_TYPE_UUID128_COMPLETE, b)

    # See org.bluetooth.characteristic.gap.appearance.xml
    if appearance:
        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))

    return payload


def decode_field(payload, adv_type):
    i = 0
    result = []
    while i + 1 < len(payload):
        if payload[i + 1] == adv_type:
            result.append(payload[i + 2 : i + payload[i] + 1])
        i += 1 + payload[i]
    return result


def decode_name(payload):
    n = decode_field(payload, _ADV_TYPE_NAME)
    return str(n[0], "utf-8") if n else ""


def decode_services(payload):
    services = []
    for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):
        services.append(bluetooth.UUID(u))
    return services


def demo():
    payload = advertising_payload(
        name="micropython",
        services=[bluetooth.UUID(0x181A), bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")],
    )
    print(payload)
    print(decode_name(payload))
    print(decode_services(payload))


if __name__ == "__main__":
    demo()

It’s just simply the copy of the sample sketch.

6-5-3. main.py

This is the original code I wrote for this program based on the sample code available on the following site: Pi Pico W でBluetooth Low Energy(BLE)を試してみる.

The sample code for main.py file in the above website was for turning on/off the on-board LED on the PicoW board, using the toggle. Therefore, I tested the on-board blinking with my device and then started adding or modifying the sample sketch. Here are my three main efforts:

  1. Adding Buzzer Pin;

  2. Adding PWM Effect on the Buzzer Sound;

  3. Separating the Messages Read for Turning On and Turning Off the Buzzer.

After a series of struggles, I settled down with the following code for main.py.

# Import necessary modules
from machine import Pin, PWM
import time
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral

# Create a Bluetooth Low Energy (BLE) object
ble = bluetooth.BLE()

# Create an instance of the BLESimplePeripheral class with the BLE object
sp = BLESimplePeripheral(ble)

# Create a Pin object for the onboard LED, configure it as an output
led = machine.Pin("LED", machine.Pin.OUT)

buzzer_pwm = PWM(Pin(21))
buzzer_pwm.freq(1000)

# Initialize the LED state to 0 (off)
led_state = 0
buzzer_on = False # To keep track of the buzzer state

def fade_buzzer():
    for duty_cycle in range(0, 65535, 256):  # 16-bit PWM resolution
        if not buzzer_on:
            return      # Exit the function if buzzer should be off
        buzzer_pwm.duty_u16(duty_cycle)
        time.sleep_ms(5)    # Adjust the sleep time for the desired fading speed

    for duty_cycle in range(65535, 0, -256):
        if not buzzer_on:
            return      # Exit the function if buzzer should be off
        buzzer_pwm.duty_u16(duty_cycle)
        time.sleep_ms(5)    # Adjust the sleep time for the desired facing speed


# Define a callback function to handle received data
def on_rx(data):
    global buzzer_on
    print("Data received: ", data)  # Print the received data
    if data == b'1\x00':  # Check if the received data is "1"
        led.value(1)
        buzzer_on = True
    elif data == b'\x00': # Check if the received data is "0"
        led.value(0)
        buzzer_on = False
        buzzer_pwm.duty_u16(0)

# Start an infinite loop
while True:
    if sp.is_connected():  # Check if a BLE connection is established
        sp.on_write(on_rx)  # Set the callback function for data reception
    if buzzer_on:
        fade_buzzer()       # Run the fade_buzzer function if buzzer is on
    else:
        time.sleep_ms(100)  # Add a small delay to prevent busy-waiting

The most difficult part of this programming exercise was the time I had to spend until I came to know that for the peripheral device to receive the “0” message, the MicroPython code should be written as “b’\x00’” and code block in App Inventor as “”(blank). This was a contrast to my earlier experience that the device could receive “1” text message from the Mobile App if the MicroPython code was written as “b‘1\x00”.

Once I settled down with these code set, the Alarm Device actions became expectable and reliable. The only remaining concern is about the code blocks in the MIT App Inventor IDE.


6-6. BLE Peripheral-Central Connection

You can see how the system would work by watching the following video.


Last update: September 5, 2024