Empowering Tetraplegic Individuals with iCycle: A BLE-Enabled Virtual Cycling Experience
- dov azogui
- 5 sept. 2024
- 2 min de lecture

As part of the iCycle project at University College London (UCL), I had the opportunity to enhance the virtual cycling experience for tetraplegic users. My contribution focused on developing a custom Bluetooth Low Energy (BLE) sensor using an ESP32 module, designed specifically to simulate wheel rotations and integrate seamlessly with virtual cycling platforms like Zwift.
Technical Highlights:
Advanced BLE Sensor Integration: I engineered a BLE sensor that accurately captures and transmits real-time cycling data. This technology is crucial for translating minimal physical interactions into dynamic movements within the virtual environment, thereby enhancing user engagement and simulation realism.
Seamless Virtual Environment Interaction: The sensor’s integration with virtual platforms allows for real-time feedback and adjustment based on the user’s input, ensuring a fluid and realistic cycling experience. This synchronization is vital for immersing users in a virtual world that mirrors the physical dynamics of cycling.
Code Explanation and Implementation: The setup involves configuring the ESP32 as a BLE server with specific UUIDs for cycling speed and cadence. The implementation handles real-time data such as wheel and crank revolutions, which are essential for realistic virtual cycling dynamics.
Here’s an overview of the code structure:
#include <Arduino.h>#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>// Define the UUIDs for BLE service and characteristics#define SERVICE_UUID "00001816-0000-1000-8000-00805f9b34fb" // Cycling Speed and Cadence Service#define CHARACTERISTIC_UUID "00002a5b-0000-1000-8000-00805f9b34fb" // CSC Measurement CharacteristicBLECharacteristic *pCharacteristic;bool deviceConnected = false;uint32_t lastSentTime = 0;uint16_t wheelRevolutions = 0;uint16_t crankRevolutions = 0;uint16_t lastWheelEventTime = 0;uint16_t lastCrankEventTime = 0;class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; Serial.println("Device connected"); }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("Device disconnected"); }};void setup() { Serial.begin(115200); // Start serial communication BLEDevice::init("ESP32 CSC"); // Initialize BLE device BLEServer *pServer = BLEDevice::createServer(); // Create BLE server pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristic->addDescriptor(new BLE2902()); pService->start(); // Start the service pServer->getAdvertising()->start(); // Start advertising Serial.println("Waiting for a client connection to notify...");}void loop() { if (deviceConnected) { if (millis() - lastSentTime > 1000) { uint8_t value[9] = {0x03, lowByte(wheelRevolutions), highByte(wheelRevolutions), lowByte(lastWheelEventTime), highByte(lastWheelEventTime), lowByte(crankRevolutions), highByte(crankRevolutions), lowByte(lastCrankEventTime), highByte(lastCrankEventTime)}; pCharacteristic->setValue(value, sizeof(value)); pCharacteristic->notify(); lastSentTime = millis(); Serial.println("Notification sent"); wheelRevolutions++; crankRevolutions++; lastWheelEventTime += 1024; lastCrankEventTime += 512; } } if (Serial.available() > 0) { int newSpeed = Serial.readStringUntil('\n').toInt(); wheelRevolutions = newSpeed; Serial.print("New speed set to: "); Serial.println(newSpeed); } delay(100);}





