LAB 2
Bluetooth

In the second lab of ECE 4960 Fast Robots, the main objective was to set up the Bluetooth communication between the Artemis board and our computers. This was an essential step, as in future labs we will primarily be using Bluetooth to communicate with and control the robot. This lab involved setting up the Bluetooth stack on the Artemis side (through Arduino), and the computer side (through Python). In this course, we use BLE (Bluetooth Low-Energy), which is optimized for low power usage at low data rates.

Setup

The first part of the setup involved setting up the Python working environment on my computer. I followed the steps outlined in the lab write-up to install Python 3.10.2, set up and activate a virtual environment, install necessary packages. On the Artemis side, most of the setup remained the same from Lab 1, except installing a new ArduinoBLE library from the library manager.

The sample code provided in ble_arduino/ble_arduino.ino and demo.ipynb was helpful to start with and check if the Bluetooth setup was done properly. After compiling and uploading the Arduino sketch onto Artemis, we can view the MAC address for our specific Artemis board by opening the Serial Monitor as shown. This address then needs to be updated in the ble_python/connection.yaml.

Figure: Artemis MAC address displayed on Serial monitor.

Running the first few cells on the Jupyter notebook should connect the computer to the Artemis board through Bluetooth with appropriate messages shown on both ends. However, while testing the connection, I ran into an issue where the Jupyter cell will not stop the execution, even after the connection has been established. After trying to debug this issue a few different ways and talking with the TA, I discovered that this is an issue with the backend Bluetooth drivers on Windows 11 that was also faced by other students working with Windows 11. I’m thankful for the week-long extension given by the course staff to complete this lab using the lab computers. I was also able to use my older computer with Windows 10 to work on parts of this lab.

Lab Tasks

After ensuring that the Bluetooth connection is working properly through the demo notebook, we needed to complete the implementation of the remaining robot commands that will be used in future labs. For our reference, the commands PING and SEND_TWO_INTS were already implemented in the sample code.

# From ble_python/cmd_types.py
class CMD(Enum):
    PING = 0
    SEND_TWO_INTS = 1
    SEND_THREE_FLOATS = 2
    ECHO = 3
    DANCE = 4
    SET_VEL = 5

Task 1

The first task involved implementing the ECHO command, which sends a piece of string from the computer to the Artemis. The Artemis acknowledges by responding with an enhanced text containing the original string. As shown in the example below, if we send an ECHO with “BIG RED”, the robot responds with “Robot says -> BIG RED”.

On the Python side, this only includes sending the ECHO and waiting to receive a string.

# Python code in demo.ipynb
ble.send_command(CMD.ECHO, "BIG RED")

# Expected to receive: "Robot says -> BIG RED"
s = ble.receive_string(ble.uuid['RX_STRING'])
LOG.info(s)
Figure: Task 1 output on Jupyter Lab.

In the Artemis code, I updated the case statement for the ECHO command to read the accompanying text, append “Robot says ->”, and transmit it back to the sender.

/* Artemis code in ble_arduino.ino */
case ECHO:
    char char_arr[MAX_MSG_SIZE];

    // Extract the next value from the command string as a character array
    success = robot_cmd.get_next_value(char_arr);
    if (!success)
        return;

    /*
     * Adds the prefix "Robot says ->" and sends it back to the computer
     */
    tx_estring_value.clear();
    tx_estring_value.append("Arty says -> ");
    tx_estring_value.append(char_arr);
    tx_characteristic_string.writeValue(tx_estring_value.c_str());

    Serial.print("Echoing: ");
    Serial.println(tx_estring_value.c_str());
                
    break;
Figure: Task 1 output on Arduino Serial Monitor.

Task 2

Next, we had to implement the command SEND_THREE_FLOATS, which is very similar to the already implemented SEND_TWO_INTS command. The robot receives a string of the form “2:3.2|45.6|24.35”, from which, it extracts the three floats 3.2, 45.6, and 24.35.

# Python code in demo.ipynb
ble.send_command(CMD.SEND_THREE_FLOATS, "42.0|0.05|16.2")
/* Artemis code in ble_arduino.ino */
case SEND_THREE_FLOATS:
    float float_a, float_b, float_c;

    // Extract the next value from the command string as an integer
    success = robot_cmd.get_next_value(float_a);
    if (!success)
        return;

    // Extract the next value from the command string as an integer
    success = robot_cmd.get_next_value(float_b);
    if (!success)
        return;

    // Extract the next value from the command string as an integer
    success = robot_cmd.get_next_value(float_c);
    if (!success)
        return;

    Serial.print("Three floats: ");
    Serial.print(float_a);
    Serial.print(", ");
    Serial.print(float_b);
    Serial.print(", ");
    Serial.println(float_c);

    break;
Figure: Task 2 output on Arduino Serial Monitor.

Task 3

In the third task, we set up a notification handler in the Python notebook that automatically reads and updates a global float variable whenever the computer receives a float value from the Artemis. This involved using a callback function that was linked to the BLE modules start_notify() function to be called every time a value is received on the RX_FLOAT UUID. As shown in the definition of the callback function below, we use the bytearray_to_float to convert the received data into a float value and store it into a global variable. While testing this, the computer successively receives a series of floats at 0.5 increments, which has already been configured in the sample code on the Artemis side.

# Python code in demo.ipynb
# Global variable to store the float characteristic
float_characteristic = 0.0

# Callback function to automatically receive the float value 
def float_receive_cb(uuid, float_value):
    global float_characteristic
    float_characteristic = ble.bytearray_to_float(float_value)
    LOG.info("Received new float value from Arty: " + str(float_characteristic))  

# Start notification period for 10s in which the computer continuously receives floats from Artemis
ble.start_notify(ble.uuid['RX_FLOAT'], float_receive_cb)
time.sleep(7)
ble.stop_notify(ble.uuid['RX_FLOAT'])
                    
Figure: Task 3 output on Jupyter lab.

Task 4

On the Artemis, the float datatype is defined as a fixed length datatype of 4 bytes. Such a float can range from -3.4E38 to 3.4E38. If the same float is converted to a string, however, then the string will include a separate character (1 byte) for each of the digits in the rational number, along with the ending ‘/0’ character. Therefore, any float can range from a few bytes to tens of bytes depending on the precision of the float.

In our system, we are concerned with transmitting these float values from the Artemis to the computer in Python. Depending on the datarate of the Bluetooth connection, sending a float as a string can lead to variable, slower latency. Moreover, since the Artemis BLE defines the max characteristic size to be 150 bytes, sending a very “long” float string can present more problems.