This is an old revision of the document!
The RunTracker32 Android application is a real-time health and location monitoring tool designed to interface with a custom Bluetooth-enabled fitness device. The app displays live data such as heart rate (BPM), blood oxygen level (SpO₂), GPS coordinates, and movement speed (in km/h), allowing users to track their vitals and position during physical activity. Additionally, the app offers quick access to Google Maps for viewing the current location.
The system is built around an ESP32 development board, which serves as the central controller. It integrates two key sensors:
The sensors are powered via the breadboard's power rails, and communication is established using serial (for GPS) and I2C (for MAX30100).
GPS (Neo-6M) Connections
TX → GPIO16 — Serial RX (ESP32 receives) RX → GPIO17 — Serial TX (ESP32 sends) VCC → 3.3V — Power GND → GND — Ground
MAX30100 Connections
SDA → GPIO21 — I2C Data SCL → GPIO22 — I2C Clock VCC → 3.3V — Power GND → GND — Ground
The system consists of two main software components: the embedded firmware running on the ESP32, developed using the Arduino IDE, and the Android application built with Kotlin. The ESP32 collects real-time health and location data from connected sensors, formats the information, and transmits it over Bluetooth. On the other side, the Android app receives this data, parses it, and presents it in a user-friendly interface for live monitoring.
#include <Wire.h> #include "MAX30100_PulseOximeter.h" #include "BluetoothSerial.h" // Define the RX and TX pins for Serial 2 #define RXD 16 #define TXD 17 #define SDA 21 #define SCL 22 #define GPS_BAUD 9600 // Create an instance of the HardwareSerial class for Serial 2 HardwareSerial gpsSerial(2); String gpsBuffer = ""; // Pulse sensor PulseOximeter pox; unsigned long lastPulseRead = 0; const unsigned long pulseReadInterval = 1000; // 1 sec BluetoothSerial SerialBT; void setup(){ // Serial Monitor Serial.begin(57600); SerialBT.begin("RunTracker32"); Serial.println("Bluetooth started..."); // Start Serial 2 with the defined RX and TX pins and a baud rate of 9600 gpsSerial.begin(GPS_BAUD, SERIAL_8N1, RXD, TXD); Serial.println("GPS Reader started..."); Wire.begin(SDA, SCL); if (!pox.begin()) { Serial.println("Failed to initialize MAX30100"); while (1); } pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA); Serial.println("Pulse Oximeter started..."); } void loop(){ while (gpsSerial.available() > 0){ // get the byte data from the GPS char gpsData = gpsSerial.read(); if (gpsData == '\n') { processGPSLine(gpsBuffer); gpsBuffer = ""; } else if (gpsData != '\r') { gpsBuffer += gpsData; } } // --- Pulse sensor --- pox.update(); if (millis() - lastPulseRead > pulseReadInterval) { lastPulseRead = millis(); Serial.print("BPM: "); Serial.print(pox.getHeartRate()); Serial.print(" SpO2: "); Serial.println(pox.getSpO2()); SerialBT.print("BPM: "); SerialBT.print(pox.getHeartRate()); SerialBT.print(" SpO2: "); SerialBT.println(pox.getSpO2()); } } void processGPSLine(String line) { if (line.startsWith("$GPRMC")) { Serial.println("GPRMC line: " + line); String parts[12]; int index = 0; int fromIndex = 0; int commaIndex; while ((commaIndex = line.indexOf(',', fromIndex)) != -1 && index < 12) { parts[index++] = line.substring(fromIndex, commaIndex); fromIndex = commaIndex + 1; } // Extract latitude and longitude (if available) String latRaw = parts[3]; String latDir = parts[4]; String lonRaw = parts[5]; String lonDir = parts[6]; if (latRaw != "" && lonRaw != "") { float lat = convertToDecimalDegrees(latRaw, latDir); float lon = convertToDecimalDegrees(lonRaw, lonDir); Serial.print("Latitude: "); Serial.print(lat, 6); Serial.print(" Longitude: "); Serial.println(lon, 6); SerialBT.print("Latitude: "); SerialBT.print(lat, 6); SerialBT.print(" Longitude: "); SerialBT.println(lon, 6); } else { Serial.println("Waiting for valid GPS fix..."); } } } float convertToDecimalDegrees(String raw, String dir) { int degreeLength = (dir == "N" || dir == "S") ? 2 : 3; float deg = raw.substring(0, degreeLength).toFloat(); float min = raw.substring(degreeLength).toFloat(); float result = deg + (min / 60.0); if (dir == "S" || dir == "W") result = -result; return result; }