View on GitHub

ece4960-fast-robots

Lab Reports and Assignments for ECE 4960: Fast Robots

Lab 2: Bluetooth Communication

Click here to return to home page

Key code snippets are on this site, but the complete files can be found on on GitHub.

Installation

Before, I was running macOS Mojave 10.14, and the Artemis gave me its MAC address after uploading the sketch provided.

mac address visible Artemis board, tell me your secrets

I then ran into an issue where running jupyter lab causes a segfault:

terminal segfault Throwback to learning C in CS 3410

After making a discussion post, I was told that my OS version was too old. Although I thought upgrading my OS would create issues with other projects, I looked into them and realized there wouldn’t be any issues if I made the upgrade. So I backed up my computer, waited a few hours for the upgrade to take place, and ran everything as normal.

jupyter notebook works Hooray, it works!

The notebook still didn’t run when I tried (as my mac failed to connect to my Artemis board). I went to lab hours for help, and TA Vivek helped me debug the issue. We discovered that there’s a known bug where the code the lab comes with has a line that will report an incorrect macOS version, so Vivek edited my code to override that line. After that, the entire notebook ran as normal. He also told me that I could use Visual Studio Code (my favorite text editor) to do this instead of a browser if I liked, so I used that to my advantage to help show that the entire demo ran smoothly.

demo runs normally At long last, everything works normally now


Lab Tasks

These are all readable from this site, but I think the GitHub preview is prettier.

Setup

This code is ripped straight from the demo. It imports requirements and connects the board to the computer.

%load_ext autoreload
%autoreload 2

from ble import get_ble_controller
from base_ble import LOG
from cmd_types import CMD
import time
import numpy as np

LOG.propagate = False

# Get ArtemisBLEController object
ble = get_ble_controller()

# Connect to the Artemis Device
ble.connect()
2022-02-01 16:50:19,827 | INFO     |: Looking for Artemis Nano Peripheral Device: C0:07:21:8D:B3:44
2022-02-01 16:50:23,755 | INFO     |: Connected to C0:07:21:8D:B3:44

Task 1

Send an ECHO command with a string value from the computer to the Artemis board, and receive an augmented string on the computer.

Arduino Code Changes

/*
* Add a prefix and postfix to the string value extracted from the command string
*/
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;

  /*
  * Your code goes here.
  */
  tx_estring_value.clear();
  tx_estring_value.append("Robot says: \"");
  tx_estring_value.append(char_arr);
  tx_estring_value.append("\"");
  tx_characteristic_string.writeValue(tx_estring_value.c_str());

  Serial.print("Sent back: ");
  Serial.println(tx_estring_value.c_str());

  break;

Python Command and Test

ble.send_command(CMD.ECHO, "Greetings, human")
msg = ble.receive_string(ble.uuid['RX_STRING'])
print(msg)
Robot says: "Greetings, human"

Task 2

Send three floats to the Artemis board using the SEND_THREE_FLOATS command and extract the three float values in the Arduino sketch.

Arduino Code Changes

/*
* Extract three floats from the command string
*/
case SEND_THREE_FLOATS:
  /*
  * Your code goes here.
  */
  float float_1, float_2, float_3;
  // Extract 3 floats in sequence, and fail if any one of them fails
  success = robot_cmd.get_next_value(float_1);
  if (!float_1) return;
  success = robot_cmd.get_next_value(float_2);
  if (!float_2) return;
  success = robot_cmd.get_next_value(float_3);
  if (!float_3) return;

  Serial.print("Three floats: ");
  Serial.print(float_1);
  Serial.print(", ");
  Serial.print(float_2);
  Serial.print(", ");
  Serial.println(float_3);
  
  break;

Python Command and Test

ble.send_command(CMD.SEND_THREE_FLOATS, "10.23|9.21|7.11")

three floats received

Task 3

Setup a notification handler in Python to receive the float value (the BLEFloatCharactersitic in Arduino) from the Artemis board. In the callback function, store the float value into a (global) variable such that it is updated every time the characteristic value changes. This way we do not have to explicitly use the receive_float() function to get the float value.

Python Code Changes

Arduino code not modified

current_float_recv = 0.0

# Record received float notifications 
def handle_recv_float(uuid, val):
  global current_float_recv
  print(ble.bytearray_to_float(val))

ble.start_notify(ble.uuid['RX_FLOAT'], handle_recv_float)

# Wait for three seconds before stopping notifications
# to avoid flooding the notebook output
time.sleep(3)
ble.stop_notify(ble.uuid['RX_FLOAT'])
2850.5
2851.0
2851.5
2852.0
2852.5
2853.0

Task 4

In your report, briefly explain the difference between the two approaches:

  1. Receive a float value in Python using receive_float() on a characteristic that is defined as BLEFloatCharactersitic in the Arduino side
  2. Receive a float value in Python using receive_string() (and subsequently converting it to a float type in Python) on a characteristic that is defined as a BLECStringCharactersitic in the Arduino side

It’s better to use Approach 1. receive_float() is named in a way to suggest it is designed to receive floating point numbers, and receive_string() is designed for strings. The naming implies there exists an API dictating what these functions do, and how it does it is left to the implementation that is not exposed to us. If someone were to optimize the implementations for each type, using the correct one (i.e. using receive_float() to receive floats as in Approach 1) would allow one to benefit from such an optimization, while Approach 2 does not receive the same benefits.

To illustrate this principle, let’s consider a possible implementation:

Approach 1 receives the float by directly interpreting the byte array the Arduino sends as a floating-point value, likely according to IEEE 754.

Approach 2 (naively) receives the “float” by receiving a string and interpreting that as a float by a character-wise conversion, which restricts one to sending numbers with a number of characters less or equal to the longest possible string one can send.

From this, we can conclude we can express more with the optimized Approach 1:

Based on these assumptions, it’s better to use Approach 1. One could of course convert each byte character-wise to match the IEEE 754 data and send that as a string, but that would be needlessly complicated.