BLE Week 5
Final project progress

The chosen idea, from last week:

A box that can be used to drop off messages for a physical office, event, etc. It be like an anonymous feedback box or like leaving flyers on a fridge. Content moderation, access, and security are a lot easier if using Bluetooth or NFC instead of having messages be submitted online, because we know that anyone who is leaving messages necessarily has physical proximity to the box. This can potentially be also used for voting ("tap here to take a ballot so you can cast it later") and line queueing ("tap here to take a ticket").

I made a small proof of concept for a BLE device that can collect votes — nearby users can vote on their computers or phones!

Demo
Code

poll.ino:

Code
/*
  LED

  This example creates a BLE peripheral with service that contains a
  characteristic to control an LED.

  The circuit:
  - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
    Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.

  You can use a generic BLE central app, like LightBlue (iOS and Android) or
  nRF Connect (Android), to interact with the services and characteristics
  created in this sketch.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

BLEService
    ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service

// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by
// central
BLEByteCharacteristic
    switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214",
                         BLERead | BLEWrite);

const int ledPin = LED_BUILTIN; // pin to use for the LED

#define N_CANDIDATES 5
int votes[N_CANDIDATES] = {0, 0, 0, 0, 0};

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  // set LED pin to output mode
  pinMode(ledPin, OUTPUT);

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1)
      ;
  }

  // set advertised local name and service UUID:
  BLE.setLocalName("Voting Booth");
  BLE.setAdvertisedService(ledService);

  // add the characteristic to the service
  ledService.addCharacteristic(switchCharacteristic);

  // add service
  BLE.addService(ledService);

  // set the initial value for the characeristic:
  switchCharacteristic.writeValue(0);

  // start advertising
  BLE.advertise();

  Serial.println("Voting Booth");
}

int value = 0;

void updateValue() {
  if (switchCharacteristic.written()) {
    value = switchCharacteristic.value();
    votes[value]++;
    Serial.print("New vote: ");
    Serial.print(value);
    Serial.println();

    Serial.print("Current votes: ");
    Serial.println();
    for (int i = 0; i < N_CANDIDATES; i++) {
      Serial.print("  Candidate ");
      Serial.print(i);
      Serial.print(" has ");
      Serial.print(votes[i]);
      Serial.print(" votes");
      Serial.println();
    }
    Serial.println();
  }
}

void loop() {
  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
      updateValue();
      delay(value * 100);
      analogWrite(A0, 255);

      updateValue();
      delay(value * 100);
      analogWrite(A0, 0);
    }

    // when the central disconnects, print it out:
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
  }
}

index.html:

Code
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
    <script
      src="https://unpkg.com/p5ble@0.0.4/dist/p5.ble.js"
      type="text/javascript"
    ></script>
  </head>
  <body>
    <fieldset>
      <legend>Connect</legend>
      <button id="connect">Connect</button>
    </fieldset>
    <fieldset>
      <legend>Vote</legend>
      <ul>
        <li>
          <button class="vote" disabled data-value="0">
            Vote for candidate 0
          </button>
        </li>
        <li>
          <button class="vote" disabled data-value="1">
            Vote for candidate 1
          </button>
        </li>
        <li>
          <button class="vote" disabled data-value="2">
            Vote for candidate 2
          </button>
        </li>
        <li>
          <button class="vote" disabled data-value="3">
            Vote for candidate 3
          </button>
        </li>
        <li>
          <button class="vote" disabled data-value="4">
            Vote for candidate 4
          </button>
        </li>
      </ul>
    </fieldset>
    <script src="sketch.js"></script>
  </body>
</html>

sketch.js:

Code
// The serviceUuid must match the serviceUuid of the device you would like to connect
const serviceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214";

let myBLE;
let myCharacteristic;

const $id = (id) => document.getElementById(id);
const $ = (el) => document.querySelectorAll(el);

function setup() {
  myBLE = new p5ble();

  $id("connect").addEventListener("click", connectToBle);

  for (let button of $("button.vote")) {
    button.addEventListener("click", (e) =>
      sendValueToBle(e.target.getAttribute("data-value"))
    );
  }
}

function connectToBle() {
  $id("connect").innerText = "Connecting";
  // Connect to a device by passing the service UUID
  myBLE.connect(serviceUuid, gotCharacteristics);
}

function gotCharacteristics(error, characteristics) {
  if (error) console.log("error: ", error);
  console.log("characteristics: ", characteristics);
  // Set the first characteristic as myCharacteristic
  myCharacteristic = characteristics[0];

  $id("connect").innerText = "Connected";
  for (let button of $("button.vote")) {
    button.disabled = false;
  }
}

function sendValueToBle(value) {
  // Write the value of the input to the myCharacteristic
  myBLE.write(myCharacteristic, parseInt(value));
}
What's left:

I'm still figuring out what the application of the final project should be. It could be:

  • A voting booth, which is what I've currently prototyped,
  • An anonymous comment drop box (contents are intended to be private) or a virtual bulletin board (contents are intended to be public), or
  • A ticket-queueing system.

Here are some observations I've made, based on the prototype, that could help me decide which idea to work on:

  • There is an obvious flaw in the proof of concept, which is that there is no way to prevent someone from voting multiple times, even by accident! Having such a system designed is critical to make the voting booth idea seem realistic.
  • I haven't tried sending strings or other variable-length data over Bluetooth yet. If doing this proves to be hard, it might make the comment box idea less feasible. I'm also not sure how well Arduinos can handle storing sizeable amounts of data, so I'm also investigating how to make a computer act as a peripheral device.
  • The ticket queuing system also requires storing a variable amount of data, although of a quantity that can probably fit in Arduino memory, at least for a demo.
Feedback
  • How to prevent double-voting/ticketing? Use MAC address, or use an ID that is received earlier in a handshake process.
  • How to send strings? BLE supports string characteristics, which are limited to 20 characters. Also beware of buffering issues.