Skip to content

13. Networking and Communications

Weekly Digest (Thu. April 25 to Wed. May 1)

Date Time Place Activities
Thu. April 25 11:30-20:00 FabLab Kannai Follow-up on Week11 and 12
Fri. April 26 11:00-14:00 Home Documentation on Week12
15:00-18:30 Akihabara (Final Project) Shopping Electronics Parts
Sat. April 27 10:00-18:00 FabLab Kannai Local Session on Week13
21:00-01:00 Home Follow-up on Week11 (Distance Sensor x Arduino IDE) and Documentation
Sun. April 28 8:00-10:30
13:00-14:00
Home Documentation on Week13
Mon. April 29 9:00-17:00
20:00-23:00
Home Documentation on Week13,
(Final Project) BLE Connection Test and Documentation,
Recitation
Tue. April 30 16:00-20:00
21:00-23:00
Home (Final Project) BLE Connection Test and Documentation,
Asian Review
Wed. May 1 9:00-18:00 Home (Final Project) BLE Connection Test, Research on Buzzer Setting

Individual Assignments Overview

Here is the list of individual assignments given by the instructors. As for the Group Assignment, please see the Group Assignment page on the FabLab Kannai website:

  Design, build, and connect wired or wireless node(s) with network or bus addresses                  
  and local input &/or output device(s)

1. Familiarization to Bluetooth Low Energy (BLE)

While I was browsing the Internet, I have found the webpage Seeed Studio XIAO ESP32C3でBLE ② BLEのサンプルを動かす that shares the owner’s experience in running sketches for Xiao ESP32C3 using the Arduino IDE. Instead of the ArduinoBLE library, he uses ESP32’s BLE library.

alt text

In BLE, the device that transmits data, such as sensor readings, is called the peripheral, and the device that connects to the transmitter to receive data is called the central. The peripheral performs an advertising action to attract centrals to connect. The central performs a scanning action to discover devices that are advertising.

In this website, the owner tried to upload two sample sketches from the ESP32 BLE Arduino Library: “BLE_scan” and “BLE_uart”.

1-1. BLE_scan

The BLE_scan is the code for the Xiao ESP32C3 board to scan the active BLE transmitters. We could just open the sample sketch and upload it.

/*
   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
   Ported to Arduino ESP32 by Evandro Copercini
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

int scanTime = 5; //In seconds
BLEScan* pBLEScan;

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
    }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Scanning...");

  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
}

void loop() {
  // put your main code here, to run repeatedly:
  BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
  Serial.print("Devices found: ");
  Serial.println(foundDevices.getCount());
  Serial.println("Scan done!");
  pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory
  delay(2000);
}
You could see the test run results of the serial monitor, which shows that there are one or two active BLE transmitters around the Xiao ESP32C3 board.

1-2. BLE_uart

The BLE_uart is the code that makes the Xiao ESP32C3 board a BLE server that sends notifications once it receives them. We could just open the sample sketch and upload it. But this time, I changed the BLE device name to “KojiESP32” and ran it.

/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" 
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   In this example rxValue is the data received (only accessible inside that function).
   And txValue is the data to be sent, in this example just a byte incremented every second. 
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();

      if (rxValue.length() > 0) {
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};


void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("KojiESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
                                        CHARACTERISTIC_UUID_TX,
                                        BLECharacteristic::PROPERTY_NOTIFY
                                    );

  pTxCharacteristic->addDescriptor(new BLE2902());

  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
                                             CHARACTERISTIC_UUID_RX,
                                            BLECharacteristic::PROPERTY_WRITE
                                        );

  pRxCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {

    if (deviceConnected) {
        pTxCharacteristic->setValue(&txValue, 1);
        pTxCharacteristic->notify();
        txValue++;
        delay(10); // bluetooth stack will go into congestion, if too many packets are sent
    }

    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}
Immediately after the upload, I didn’t see any message on the serial monitor. Then I took my Android mobile phone out. I searched for a BLE connection platform app and decided to install LightBlue.

alt text alt text

Once installed, I tapped the LightBlue app and searched for my BLE “KojiESP32”. I found it and tapped “Connect” to the Advertisement Data page.

alt text

Then I scrolled down the page and tapped my UUID to proceed. There are two options: “Notify” and “Writable”. If you tap “Notify”, you could receive the serial data sent by the Xiao board. If you tap “Writable”, you could send a message to the board.

alt text alt text

This time I tapped “Writable”. After switching the data format to “UTF 8-string”, I typed “Hello!!” in the blank box under the “WRITTEN VALUES” headline and typed “Write”. Then I could see the texts both on my mobile app and the Arduino IDE serial monitor.

alt text alt text

alt text


2. Wireless Connection of Input Devices

2-1. Setup of the Temperature Sensor: MCP9808

The owner of the above website further tried to transmit the air pressure data via BLE Seeed Studio XIAO ESP32C3でBLE ③ I2CでセンサBMP280をつなぐ with BMP280 temperature and air pressure sensor. However, I didn’t have the same sensor with me and then decided to test the data transmission with another temperature sensor, Adafruit MCP9808, instead.

Here is the summary of the wire connection.

ESP32C3 MCP9808 Breakout Board
3V3 VIN
GND GND
SDA SDA
SCL SCL

alt text

Then I called the sample sketch “mcp9808test” from the Arduino IDE Library and uploaded.

/**************************************************************************/
/*!
This is a demo for the Adafruit MCP9808 breakout
----> http://www.adafruit.com/products/1782
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
*/
/**************************************************************************/

#include <Wire.h>
#include "Adafruit_MCP9808.h"

// Create the MCP9808 temperature sensor object
Adafruit_MCP9808 tempsensor = Adafruit_MCP9808();

void setup() {
  Serial.begin(9600);
  while (!Serial); //waits for serial terminal to be open, necessary in newer arduino boards.
  Serial.println("MCP9808 demo");

  // Make sure the sensor is found, you can also pass in a different i2c
  // address with tempsensor.begin(0x19) for example, also can be left in blank for default address use
  // Also there is a table with all addres possible for this sensor, you can connect multiple sensors
  // to the same i2c bus, just configure each sensor with a different address and define multiple objects for that
  //  A2 A1 A0 address
  //  0  0  0   0x18  this is the default address
  //  0  0  1   0x19
  //  0  1  0   0x1A
  //  0  1  1   0x1B
  //  1  0  0   0x1C
  //  1  0  1   0x1D
  //  1  1  0   0x1E
  //  1  1  1   0x1F
  if (!tempsensor.begin(0x18)) {
    Serial.println("Couldn't find MCP9808! Check your connections and verify the address is correct.");
    while (1);
  }

   Serial.println("Found MCP9808!");

  tempsensor.setResolution(3); // sets the resolution mode of reading, the modes are defined in the table bellow:
  // Mode Resolution SampleTime
  //  0    0.5°C       30 ms
  //  1    0.25°C      65 ms
  //  2    0.125°C     130 ms
  //  3    0.0625°C    250 ms
}

void loop() {
  Serial.println("wake up MCP9808.... "); // wake up MCP9808 - power consumption ~200 mikro Ampere
  tempsensor.wake();   // wake up, ready to read!

  // Read and print out the temperature, also shows the resolution mode used for reading.
  Serial.print("Resolution in mode: ");
  Serial.println (tempsensor.getResolution());
  float c = tempsensor.readTempC();
  float f = tempsensor.readTempF();
  Serial.print("Temp: "); 
  Serial.print(c, 4); Serial.print("*C\t and "); 
  Serial.print(f, 4); Serial.println("*F.");

  delay(2000);
  Serial.println("Shutdown MCP9808.... ");
  tempsensor.shutdown_wake(1); // shutdown MSP9808 - power consumption ~0.1 mikro Ampere, stops temperature sampling
  Serial.println("");
  delay(200);
}
Here is the result. This device was successfully connected to the ESP32C3 and started sending the serial temperature data.

alt text

2-2. Transmitting the Temperature Data to Mobile Phone

Now that I confirmed the code for getting the serial data from the input device, the next step is to make ESP32C3 a server to send the serial data to my mobile phone via BLE. As of afternoon, Monday, April 29, I have not been successful in setting up the Bluetooth connection between the ESP32C3 board and my mobile phone so that I could receive the temperature data with my mobile. I have faced a series of compiling errors and have been stacked by the last one. Therefore, as temproary measure, I hereby document what happened to my exercise.

I opened the sample sketch “BLE_notify” from the “ESP32 BLE Arduino” Library and uploaded successfully.

/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updated by chegewara

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
   And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   A connect hander associated with the server starts a background task that performs notification
   every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};



void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
    // notify changed value
    if (deviceConnected) {
        pCharacteristic->setValue((uint8_t*)&value, 4);
        pCharacteristic->notify();
        value++;
        delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}
Now, based on this sample code, I tried to integrate the above two codes. Please note that all the library files have been surely included.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include "Adafruit_MCP9808.h"

// Create the MCP9808 temperature sensor object
Adafruit_MCP9808 tempsensor = Adafruit_MCP9808();

float temp = 0;

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

void setup() {
  Serial.begin(115200);
  while (!Serial); //waits for serial terminal to be open, necessary in newer arduino boards.
  Serial.println("MCP9808 demo");

  if (!tempsensor.begin(0x18)) {
    Serial.println("Couldn't find MCP9808! Check your connections and verify the address is correct.");
    while (1);
  }

   Serial.println("Found MCP9808!");

  tempsensor.setResolution(3); // sets the resolution mode of reading, the modes are defined in the table bellow:

  BLEDevice::init("KojiESP32");
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
                                 CHARACTERISTIC_UUID,
                                 BLECharacteristic::PROPERTY_READ |
                                 BLECharacteristic::PROPERTY_WRITE|
                                 BLECharacteristic::PROPERTY_NOTIFY|
                                 BLECharacteristic::PROPERTY_INDICATE
                                );

  pCharacteristic->addDescriptor(new BLE2902());

  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");

}

void loop() {
   // notify changed value
  if (deviceConnected) {
    pCharacteristic->setValue((uint8_t*)&value, 4);
    pCharacteristic->notify();
    value++;
    delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
  }
  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
      // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}
Then I tried to compile it and then, after rectifying a couple of different errors, I started suffering from the following compilation error saying “Expected Type-Specifier before ‘BLE2902’“.

alt text


3. Alternative Scenario with Raspberry Pi PicoW

With the possibility of using Raspberry Pi PicoW (hereinafter referred to as “RPi PicoW” or “PicoW” as a board for my final project, I also tried to replicate the same serial communication with PicoW via Thonny IDE. I browsed the Internet and searched for the way to set up the BLE connection. For the exercieses I did for the documentation of this section, I mainly followed the steps described in the following website Pi Pico W でBluetooth Low Energy(BLE)を試してみる.

As a matter of course, the first step described in the above reference site was about the installing of the MicroPython UF2 file Raspberry Pi Documentation to the device.

3-1. Preparation for Wireless Connection via BLE

I learned that for setting up the BLE connection we should create the following three files and save them in the RPi PicoW device.

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()

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="mpy-uart"):
        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()

main.py

Now in the main file, I prepared the code to record the button push on the mobile app, LightBlue. For this, I prepared the simple wire connection between the PicoW board and the toggle switch.

alt text

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

# Create a Bluetooth Low Energy (BLE) object
ble = bluetooth.BLE()
# Create an instance of the BLESimplePeripheral class with the BLE object
sp = BLESimplePeripheral(ble)

# Set the debounce time to 0. Used for switch debouncing
debounce_time=0

# Create a Pin object for Pin 0, configure it as an input with a pull-up resistor
pin = Pin(0, Pin.IN, Pin.PULL_UP)

while True:
    # Check if the pin value is 0 and if debounce time has elapsed (more than 300 milliseconds)
    if ((pin.value() is 0) and (time.ticks_ms()-debounce_time) > 300):
        # Check if the BLE connection is established
        if sp.is_connected():
            # Create a message string
            msg="pushbutton pressed\n"
            # Send the message via BLE
            sp.send(msg)
        # Update the debounce time    
        debounce_time=time.ticks_ms()

Wireless Connection

With the above three files onboard, I ran the program and immediately the python shell showed that the the board started advertising.

Next I took up my mobile phone and opened the LightBlue app. Soon I could find the “MPR-URT” signal.

alt text

Once I tapped “Connect”, then the mobile page automatically changed to the profile of the MPR-URT, and at the same time, I could see a new message in the python shell as well.

alt text

Then I pressed the toggle button a few times on the peripheral side. This was recorded on the central side and I could see at what time the button was pressed.

alt text

3-2. Monitoring of the Built-in Temperature Sensor of the Board

Based on the above connection setting, I tried to collect the temperature data of the board with the built-in sensor.

I used the sample sketches from raspberrypi/pico-micropython-examples. There were three py files to download and I opened all of them on the Thonny IDE: ble_advertising.py / picow_ble_temp_reader.py / picow_ble_temp_sensor.py. Due to the length of my weekly assignment page, I will skip posting the whole codes herewith.

Once I started running the program, the python shell started listing the temperature data the sensor collected periodically. Also, I could see that the periphery name and its MAC address were indicated in the python shell, too.

alt text

I took my mobile phone and opened the LightBlue app. I could find the periphery name and its MAc address easily. I tapped “Connect” and opened the profile of the periphery. Down in the profile, I found the sub-title headline “ENVIRONMENTAL SENSING” and the “Temperature” was ready for tapping. When I tapped “Temperature”, I moved to the Temperature page and saw the serial data keep coming into the center.

alt text

However, default data format “Hex” was not suitable to show serial temperature data. After trying a few different format options, I finally found that “Signed Little-Endian” could be the best data format for temperature, but still it was 100 times larger than the actual degrees Celsius.

alt text

Updates on June 21, 2024

As pointed out by our Instructor, who said that I should replicate this with the board I developed, I once tried to test it with my board. Here is the result.

I simply uploaded the three files I mentioned in the 3-2 above. Here is the result photo and video.

alt text

Also, you could see how the temp data on the central and peripheral devices were recorded. First, please see the steps I took to start reading the temperature with the LightBlue app on my mobile. Then please see how it was recorded on the python shell of the Thonny IDE.

alt text alt text alt text

alt text

With this, I feel that I could meet all the requirements for the Individual Assignment for Week13. But there still remain the following problems:

  • When I scanned for I2C address for the MCP9808 sensor with Thonny IDE and PicoW board, I saw there were too many I2C addresses. This made it difficult to identify the I2C address for the sensor. This happened even though the MCP9808 was the only sensor connected to my PicoW board.

  • I came up with an assumption that the MCP9808 sensor was damaged, and tried to flash the same codes I used in 2-1 and 2-2 above with Arduino IDE and Xiao ESP32C3 board. I saw the message that the program was not able to detect the MCP9808 sensor. I tried the same with another laptop and faced the same result. I concluded that there was something wrong with the sensor.

In order to continue to work with MCP9808 temperature sensor, I need to wait until I get a new sensor I ordered on the e-commerce platform.

3-3. Switching On/Off LED

Then as a warm-up exercise for my final project, I tried to set up a communication for the central side to control the peripheral side. The first step would be to turn on/off the LED connected to the Pico W board.

alt text

I expected that I could use the same files: “ble_advertising.py” and “ble_simple_peripheral.py”. I thought that the only thing I should do is to prepare the main code for LED blinking and communication with the central in terms of the “main.py” file.

There were a few predecesors who have already tried this and shared their experience online. Many of them tried to turn on/off the onboard LED of the PicoW. Therefore, I assigned another Pin for output and added an LED circuit on the breadboard.

Also, I wanted to assign two different messages, say “1” and “0” to turn on and off the LED respectively.

The following is the code I wrote and tried for test run:

# Import necessary modules
from machine import Pin 
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 LED, configure it as an output
led = Pin( 15, Pin.OUT)

# Initialize the LED state to 0 (off)
led.off()

# Define a callback function to handle received data
def on_rx(data):
    print("Data received: ", data)  # Print the received data
    if data == b'1\r\n':  # Check if the received data is "1"
        led.on()          # LED on
        sp.send("LED ON\n")  # Send a confirmation message
    elif data == b'0\r\n':  # Check if the received data is "0"
        led.off()           # LED off
        sp.send("LED OFF\n")  # Send a confirmation message
    else:  # Check if the received data is other than 0 or 1
        sp.send("Do Nothing\n")  # Send a confirmation message

# 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

Unfortunately, as of the evening of Tuesday, April 30, it’s never been successful. It’s connected to my Android phone via BLE. But nothing happened to the LED. Also, my Android app and the python shell on my laptop only showed the messages (1, 0, other numbers/texts) I wrote from the central, but didn’t show the messages that the peripheral sent back to the central.

alt text

Updates on June 21, 2024

I left the Week13 Individual Assignment without rectifying this problem. It was not a good idea to document what went wrong without writing about what I did to solve it. Therefore, before the closure of the local evaluation, I took time to write what I did for this issue.

Test1 Toggle Switch:I started with the toggle-switching to turn on/off the on-board LED and the buzzer. In fact, I did succeed on this earlier as the first step to reach the final code for my Final Project. Please see the Final Projet - Bluetooth Connection.

alt text

This is the code I modified for toggling LED on and off. I also added a few lines to switch on/off the buzzer together with the on-board LED.

    # Import necessary modules
    from machine import Pin 
    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 and the buzzer configure it as an output
    led = machine.Pin("LED", machine.Pin.OUT)
    buzzer = machine.Pin(0, machine.Pin.OUT)

    # Initialize the LED state to 0 (off)
    led_state = 0
    buzzer_state = 0

    # Define a callback function to handle received data
    def on_rx(data):
        print("Data received: ", data)  # Print the received data
        global led_state  # Access the global variable led_state
        global buzzer_state
        if data == b'1\r\n':  # Check if the received data is "1"
            led.value(not led_state)  # Toggle the LED state (on/off)
            buzzer.value(not buzzer_state)  # toggle the Buzzer state (on/off)
            led_state = 1 - led_state  # Update the LED state
            buzzer_state = 1 - buzzer_state  # Update the Buzzer state
            sp.send("LED and Buzzer Switched\n")  # Send a confirmation message

    # 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

On the BLE central device side (mobile phone), I downloaded the Serial Bluetooth Terminal app as it’s easier to find the same MAC address once it’s paired up with that device.

Once I let the peripheral device start adversizing, the app detected the peripheral device. I selected the one to pair up and then the new terminal window appeared. I could read the record of serial communication between the BLE central and peripheral devices. It succeeded in turning on and off the on-board LED and the buzzer by writing “1” to send to the peripheral device. Then the peripheral device also retured the message to the central device.

alt text alt text

Comparing the program with the previous one which didn’t work, one big difference was that I added “machine” to the line to assign specific pin as output pin.

led = machine.Pin("LED", machine.Pin.OUT)

Test2 Control On/Off with Different Messages: The next step is to modify the program so that I could control the switch with two different texts: “1” for switching on and “0” for switching off. To do this, I added two more branches for the received data.

    # Import necessary modules
    from machine import Pin 
    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 and the buzzer configure it as an output
    led = machine.Pin("LED", machine.Pin.OUT)
    buzzer = machine.Pin(0, machine.Pin.OUT)

    # Initialize the LED state to 0 (off)
    led_state = 0
    buzzer_state = 0

    # Define a callback function to handle received data
    def on_rx(data):
        print("Data received: ", data)  # Print the received data
        if data == b'1\r\n':  # Check if the received data is "1"
            led.on()
            buzzer.on()
            sp.send("LED/Buzzer ON\n")  # Send a confirmation message
        elif data == b'0\r\n':  # Check if the received data is "0"

            led.off()           # LED off
            buzzer.off()
            sp.send("LED/Buzzer OFF\n")  # Send a confirmation message
        else:  # Check if the received data is other than 0 or 1
            sp.send("Do Nothing\n")  # Send a confirmation message

    # 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

Then this is the result I got. Looks like I succeeded in turning on by sending “1”, and off by sending “0”. Also, if I send the other texts, the peripheral device returned the message “Do Nothing”.

alt text

Sorry about the video which only shows the reaction of the peripheral device after I sent the message to turn it on. That’s all I could do with the only one on-board camera available.

Test3: Replicate It with My Board: Finally, the last test is to replicate it with the board I developed. First, I connected the wires and the active buzzer between the bread board and the Alarm Device already made for my Final Project. I assigned the Pin1 as the output pin for the active buzzer.

alt text

For this, I copy-and-pasted the above code to the main.py file for my board with a slight change in line 14 so that I could allocate pin1 for output pin for the buzzer.

buzzer = machine.Pin(1, machine.Pin.OUT)

When I uploaded the program and connected it with my mobile app, both the app and the pyothon shell of the Thonny IDE started recording the messages transmitted between the two devices.

alt text alt text

Here is the video to show how the on-board LED and the buzzer were responding. (My apologies again. I started recording the video after I sent the turn-on message to the peripheral device from my mobile.)

3-4. Preparation for Group Assignment (Updates on June 30, 2024)

In addition to exchanging messages between my board and the mobile phone in 3-3 above, I also did some preparatory work for the reading/writing messages between the boards we designed as part of our Group Assignment for Week13.

One of our boards was Seeed Xiao ESP32C3 programmed by Arduino IDE. Therefore, the Group Assignment should be between MicroPython language (for PicoW with Thonny IDE) and C++ language (for Xiao ESP32C3 with Arduino IDE). Before we directly jumped at this BLE connection between the two different languages, I thought that it would be a good idea to prepare programs for the BLE communication between two PicoW boards programmed in MicroPython languages.

Therefore, I prepared the two codes with Thonny IDE and saved them in each board.

PicoW Board BLE Role Assignment Actions
Board for Final Project Peripheral - Read the temperature data from the on-board sensor.
- Send messages about the temp data
New PicoW with Breadboard Cental - Receive messages about temp data.
- Print the messages on the Serial Plotter.

alt text

alt text

BLE Peripheral

The following program is prepared in the file name main.py and saved in the PicoW board used for my Final Project. Once I finished flashing. I disconnected it from the USB cable so that it could start reading the temp sensor and also BLE advertising once I switch on the coin battery holders.

    #BLE Peripheral Reading the On-Board Temp Sensor Values
    from micropython import const
    import uasyncio as asyncio
    import aioble
    import bluetooth
    import struct


    # UUID Definition
    _ENV_SENSE_UUID = bluetooth.UUID(0x181A)  # org.bluetooth.service.environmental_sensing
    _ENV_SENSE_TEMP_UUID = bluetooth.UUID(0x2A6E)  # org.bluetooth.characteristic.temperature
    _ADV_APPEARANCE_GENERIC_THERMOMETER = const(768)  # org.bluetooth.characteristic.gap.appearance.xml

    _ADV_INTERVAL_MS = 250_000  # Advertising interval (ms)


    # Registration of GATT Server
    temp_service = aioble.Service(_ENV_SENSE_UUID)  # Define the service
    temp_characteristic = aioble.Characteristic(
        temp_service, _ENV_SENSE_TEMP_UUID, read=True, notify=True
    )  # Setting of the Temp Read Characteristic (reading possible, notify possible)
    aioble.register_services(temp_service)  # Register temp server

    sensor_temp = machine.ADC(4)  # Define PIN for taking data from On-board Temp Sensor
    conversion_factor = 3.3 / (65535)

    # Define temp read from On-board Temp Sensor
    def _read_temperature():
        reading = sensor_temp.read_u16() * conversion_factor
        temperature = 27 - (reading - 0.706)/0.001721
        return temperature


    def _encode_temperature(temp_deg_c):
        # Encode temp sensor values to the transmittable format
        # To be more specific, it goes throught the following process.
        # - Multiply Temp Sensor Values by 100 so that they could be read as integer value
        # - Convert the bytearray to binary code for packing

        return struct.pack("<h", int(temp_deg_c * 100))


    async def sensor_task():
        # Renew the temp sensor values after writing
        # - Read temp sensor values
        # - Write them in the Characteristics
        # - Pause for 1000ms
        while True:
            t = _read_temperature()  # Read temp sensor 
            temp_characteristic.write(_encode_temperature(t))
            await asyncio.sleep_ms(1000)


    async def peripheral_task():
        # Keep advertising until connected
        # When connected, write its MAC address on the BLE central device
        # Wait until it's disconnected
        while True:
            async with await aioble.advertise(
                _ADV_INTERVAL_MS,
                name="mpy-temp",
                services=[_ENV_SENSE_UUID],
                appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER,
            ) as connection:
                print("Connection from", connection.device)
                await connection.disconnected()


    async def main():
        # Define the function to undertake the two tasks in async
        # t1(task1):Renewal of the Temp Sensor read
        # t2(task2):Tas as BLE Peripheral 
        t1 = asyncio.create_task(sensor_task())
        t2 = asyncio.create_task(peripheral_task())
        await asyncio.gather(t1, t2)


    asyncio.run(main())

BLE Central

The other code is for the BLE central device, another PicoW board on the breadboard. It could be connected to the PC via USB cable. This code is saved in the PicoW board as temp_client.py file.

    #BLE Central Device to Receive Temp Data and Plot it on the Console
    from micropython import const
    import uasyncio as asyncio
    import aioble
    import bluetooth
    import struct

    # UUID definition
    _ENV_SENSE_UUID = bluetooth.UUID(0x181A)  # org.bluetooth.service.environmental_sensing
    _ENV_SENSE_TEMP_UUID = bluetooth.UUID(0x2A6E)  # org.bluetooth.characteristic.temperature


    # Helper to decode the temperature characteristic encoding (sint16, hundredths of a degree).
    def _decode_temperature(data):
        # Function to decode the received data to temp sensor value
        # More specifically, it defines the following tasks:
        #・unpack the binary code 
        #・divide the temp sensor value by 100
        return struct.unpack("<h", data)[0] / 100


    async def find_temp_sensor():
        # Scan the advertising devices for 5 sec.
        async with aioble.scan(5000, interval_us=30000, window_us=30000, active=True) as scanner:
            async for result in scanner:
                # If the target device is named "mpy-temp" and incuding env sensing, returns the device info.
                if result.name() == "mpy-temp" and _ENV_SENSE_UUID in result.services():
                    return result.device
        return None


    async def main():
        device = await find_temp_sensor()
        if not device:  # If it fails in detecting the target device
            print("Temperature sensor not found")
            return

        # If the target device is detected, the following taskes will also be undertaken
        try:
            print("Connecting to", device)
            connection = await device.connect()  # Connecting to the BLE peripheral device
        except asyncio.TimeoutError:
            print("Timeout during connection")  # Post message if the connection timeout error occurs
            return

        # When successful in device.connect(), proceed to the following tasks as well
        async with connection:
            try:
                temp_service = await connection.service(_ENV_SENSE_UUID)  # Environmental sensing service
                temp_characteristic = await temp_service.characteristic(_ENV_SENSE_TEMP_UUID)
            except asyncio.TimeoutError:
                print("Timeout discovering services/characteristics")
                return

            # If the BLE central device reads the characteristics of the peripheral, proceed to the following tasks.
            while True:
                temp_deg_c = _decode_temperature(await temp_characteristic.read())  # Decode temp sensor data from received data
                print("Temperature: {:.2f}".format(temp_deg_c))  # Plotting the temp read on the Plotter
                await asyncio.sleep_ms(1000)  # Sleep for 1000ms


    asyncio.run(main())

Result

Once both devices were ready, I switched on the Alarm Device (BLE peripheral), and then clicked “RUN” on the Thonny IDE for the other PicoW on the breadboard (BLE central). Immediately the Python Shell started printing the serial data and also the Plotter started plotting the temperature data transition on the Thonny IDE.

Based on these program set, I proceeded to the Group Assignment and the test result was documented in the Group Assignment page.

Acknowledgement: This practice was made possible with the sample codes available on the website Raspberry Pi Pico WでBLE通信を使ってみる(micropython).


Last update: June 30, 2024