Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
| ||||||
|
Professional runners have dedicated pace setters to help the runner maintain a specific pace. Staying at a specific pace is very important to runners and it can be a game changer to runners who know how to maintain the most optimal pace. The PaceLight is meant to substitute pace setters and helps runners maintain their pace by using lights. It is as simple as putting in the desired parameters, pressing start, and following the colors!
paceLightEventually.ino
ArduinoThis is the main function. Upload this function to your esp32. You will need to change the wifi credentials on lines 13-14. It is best to use a hotspot on your phone and run with your phone. You will need to create a separate MQTT and establish the feed names (line 38-42) to run this code. Lastly, you will need to use IFTTT to create a button that will send data to the paceLight_Control feed.
To use:
1. text in your tolerance (pace range you want to stay in), pace (pace you want to maintain), period (period that lights changes to update you on your pace)
Example: I want to be in a tolerance of 2min/mile, pace of 10min/mile and sample every 15 seconds.
#tol 2 #pace 10 #period 15
2. Press the IFTTT button to begin sampling the GPS data
3. Run and follow the lights.
Blinking Red - Too Slow
Red - Slow
Green - On Pace
Blue - Fast
Blinkiing Blue - Too Fast
To use:
1. text in your tolerance (pace range you want to stay in), pace (pace you want to maintain), period (period that lights changes to update you on your pace)
Example: I want to be in a tolerance of 2min/mile, pace of 10min/mile and sample every 15 seconds.
#tol 2 #pace 10 #period 15
2. Press the IFTTT button to begin sampling the GPS data
3. Run and follow the lights.
Blinking Red - Too Slow
Red - Slow
Green - On Pace
Blue - Fast
Blinkiing Blue - Too Fast
/***************LIBRARY INCLUSION****************/
// bring in libraries
#include <WiFi.h>
#include <Adafruit_GPS.h>
#include <Adafruit_MQTT.h>
#include <Adafruit_MQTT_Client.h>
#include <Eventually.h>
#include <NeoPixelBus.h>
#define colorSaturation 128
/***************WIFI CREDENTIALS****************/
// assign wifi credentials
const char* ssid = "logisticnexus";
const char* password = "lcpl6414";
/**************MQTT CREDENTIALS*****************/
// assign MQTT credentials
#define aio_server "io.adafruit.com"
#define aio_serverport 1883
#define aio_username "jskelley"
#define aio_key "ac57937d116a444089f1c500e3310459"
/**************GPS SERIAL*********************/
// assign, connect to GPS serial
#define GPSSerial Serial2
Adafruit_GPS GPS(&GPSSerial);
//disable data echo to serial
#define GPSECHO false
EvtManager mgr;
// this establishes method for data xmission, receive
WiFiClient client;
// Establish mqtt object, and subscribe / publish channels
Adafruit_MQTT_Client mqtt(&client, aio_server, aio_serverport, aio_username, aio_key);
Adafruit_MQTT_Subscribe ctrl = Adafruit_MQTT_Subscribe(&mqtt, aio_username "/feeds/paceLight_Control");
Adafruit_MQTT_Subscribe pace = Adafruit_MQTT_Subscribe(&mqtt, aio_username "/feeds/paceLight_pace");
Adafruit_MQTT_Subscribe period = Adafruit_MQTT_Subscribe(&mqtt, aio_username "/feeds/paceLight_period");
Adafruit_MQTT_Subscribe tolerance = Adafruit_MQTT_Subscribe(&mqtt, aio_username "/feeds/paceLight_tolerance");
Adafruit_MQTT_Publish dataz = Adafruit_MQTT_Publish(&mqtt, aio_username "/feeds/paceLight_data");
bool stateHIGH = LOW;
bool stateMEDIUM = LOW;
const uint16_t PixelCount = 30;
const uint8_t PixelPin = 4;
NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> strip(PixelCount, PixelPin);
bool c = 0; // c tracks logging state, 1 = logging, 0 = not logging
double P; // P is user-input desired pace
int f; // f is user-input desired feedback period (seconds between update)
double tol; // tol represents size of tolerance bands between zones
double D = 1.15078; // D is the conversion factor between knots and MPH
double B;
double Bt = 0;
double dP;
double t_mqtt = 1000; // Time between MQTT data read / connection check
double t_read = 1; // Time between GPS read events
double t_parse = 2000; // Time between GPS parse events
double t_ping = 150000; // Time between MQTT ping events
int count;
int nSplit;
bool geeps_status;
bool wifi_status = 0;
int Pbool = 0;
int fbool = 0;
int tolbool = 0;
RgbColor red(colorSaturation, 0, 0);
RgbColor green(0, colorSaturation, 0);
RgbColor blue(0, 0, colorSaturation);
RgbColor white(colorSaturation);
RgbColor black(0);
RgbColor yellow(colorSaturation,colorSaturation,0);
RgbColor purple(colorSaturation, 0, colorSaturation);
/*****************COLOR WIPE*******************/
// Fill the ALL dots with a color
void colorWipe(RgbColor c,uint32_t start,uint32_t num) {
for(uint16_t i=start; i<(num-1); i++) {
strip.SetPixelColor(i, c);
}
strip.Show();
}
/****************BLINK MEDIUM****************/
bool blinkmeMEDIUM() {
stateMEDIUM = !stateMEDIUM; // Switch light states
if (c == 0 || geeps_status == 0){
colorWipe(black,5,31); // OFF No Running
return false;
}
// Check if too fast by 1xTolerance
else if (dP>tol && dP <2*tol){
if (stateMEDIUM == true){colorWipe(red,5,31);} // BLUE
else{colorWipe(black,5,31);} // OFF
}
else if (dP<-tol && dP > -2*tol){
if (stateMEDIUM == true){colorWipe(blue,5,31);} // RED
else{colorWipe(black,5,31);} // OFF
}
return false; // Allow the event chain to continue
}
/****************BLINK HIGH******************/
bool blinkmeHIGH() {
stateHIGH = !stateHIGH; // Switch light states
if (c == 0 || geeps_status == 0){
colorWipe(black,5,31); // OFF No Running
return false;
}
// Check if too fast by 2xTolerance
else if (dP >2*tol){
if (stateHIGH == true){colorWipe(red,5,31);} // BLUE
else{colorWipe(black,5,31);} // OFF
}
else if (dP < -2*tol){
if (stateHIGH == true){colorWipe(blue,5,31);} // RED
else{colorWipe(black,5,31);} // OFF
}
else if (dP>=-tol && dP <= tol){
colorWipe(green,5,31); // GREEN
}
return false; // Allow the event chain to continue
}
// This is for debugging purposes but it allows the user to type in a float value for the dP value
bool readme(){
if (Serial.available()>0){
float tempX = Serial.parseFloat();
if (tempX != 0) {
if (tempX == 1) wifi_status =0;
else if(tempX == 2) wifi_status = 1;
else if(tempX == 3) geeps_status = 0;
else if(tempX == 4) geeps_status = 1;
else if(tempX == 5) dP = 10;
else if(tempX == 6) dP = 15;
else if(tempX == 7) dP = 25;
else if(tempX == 8) dP = -10;
else if(tempX == 9) dP = -15;
else if(tempX == 10) dP = -25;
else if(tempX == 11) c = 0;
else if(tempX == 12) c = 1;
else if(tempX == 13) Pbool = 0;
else if(tempX == 14) Pbool = 1;
else if(tempX == 15) Pbool = 2;
else if(tempX == 16) fbool = 0;
else if(tempX == 17) fbool = 1;
else if(tempX == 18) fbool = 2;
else if(tempX == 19) tolbool = 0;
else if(tempX == 20) tolbool = 1;
else if(tempX == 21) tolbool = 2;
}
}
}
/*****************CALLBACK*******************/
// Function callback is used to enable or disable GPS logging
// callback is triggered each time paceLight_control receives a message
void callback(double y) {
if (c == 0 && tolbool == 2 && fbool ==2 && Pbool ==2) {
Serial.println("Starting GPS logging");
c = !c;
}
else if (c == 1) {
Serial.println("Ceasing GPS logging");
c = !c;
}
else {
Serial.println("you're dumb");
}
}
/***************CALLBACK_PACE****************/
// Function callback_pace is used to define a user's desired pace
// callback_pace is triggered each time paceLight_pace receives a message
void callback_pace(double z) {
if (c==0) {
mqtt.readSubscription(t_mqtt);
P = atof((char *)pace.lastread); // Note: if invalid (non-double) data is read, P = 0
if (P > 0) {
Pbool = 2;
colorWipe(white,2,4); // green red
strip.Show();
Serial.print("\n Target Pace: ");
Serial.print(P);
}
else {
Pbool = 1;
Serial.println("invalid pace input");
colorWipe(red,2,4); // BAD red
}
}
}
/***************CALLBACK_PERIOD**************/
// Function callback_period is used to define a user's desired feedback period
// callback_period is triggered each time paceLight_period receives a message
void callback_period(double s) {
if (c == 0) {
mqtt.readSubscription(t_mqtt);
f = atoi((char *)period.lastread); // Note: if invalid (non-double) data is read, f = 0
if (f > 0) { // If input is valid...
fbool = 2;
Serial.print("\nFeedback period: ");
Serial.print(f);
colorWipe(white,3,5); // green red
}
else { // If input is invalid...
fbool = 1;
Serial.println("invalid period input");
colorWipe(red,3,5); // BAD red
}
}
}
/***************CALLBACK_TOLERANCE**************/
// Function callback_tolerance is used to define a user's desired pace tolerance
// callback_tolerance is triggered each time paceLight_tolerance receives a message
void callback_tolerance(double n) {
if (c == 0) {
mqtt.readSubscription(t_mqtt);
tol = atof((char *)tolerance.lastread); // Note: if invalid (non-double) data is read, tol = 0
tol = (tol/60);
if (tol > 0) {
tolbool = 2;
Serial.print("\nTolerance: ");
Serial.print(tol);
colorWipe(white,4,6); // green red
}
else {
tolbool = 1;
Serial.print("invalid tolerance input");
colorWipe(red,4,6); // BAD red
}
}
}
/**************MQTT CONNECT*******************/
void MQTT_connect() {
int8_t ret;
// If connected to MQTT, continue script
if (mqtt.connected()) {
return;
}
Serial.print("Connecting to MQTT... ");
// 3 attempts. Check status of MQTT, return string of error, if applicable, die if > attempts
uint8_t retries = 3;
while ((ret = mqtt.connect()) !=0) {
Serial.println(mqtt.connectErrorString(ret));
Serial.println("Retrying MQTT connection in 5 seconds...");
mqtt.disconnect();
delay(5000);
retries--;
if (retries == 0){
while (1);
}
}
Serial.println("MQTT connected");
}
/******************MQTT PING*****************/
void mqtt_ping() {
mqtt.ping();
Serial.println("pinging MQTT");
}
/*******************MQTT LISTEN***************/
void mqtt_listen() {
MQTT_connect();
mqtt.processPackets(1000);
}
/*******************GEEPS READ****************/
void geeps_read() {
GPS.read();
}
/*******************GEEPS PARSE***************/
void geeps_parse() {
GPS.parse(GPS.lastNMEA());
if (GPS.fix) {
geeps_status = 1;
//Serial.println("the fix is in");
colorWipe(white,0,2);// WHITE GOOD
//Serial.println("IS IT ON? no.");
}
else {
geeps_status = 0;
colorWipe(red,0,2); // PURPLE BAD
}
if (c==1) {
if (GPS.fix) {
count ++;
//Serial.print("\ncount: ");
//Serial.print(count);
B = (60/(D*GPS.speed)); // B = actual pace, minutes / mile
//Serial.print("\npace: ");
//Serial.print(B);
Bt = Bt+B; // Total pace values for use with average
Serial.println(Bt);
if (count == (f / (t_parse/1000))) {
//nSplit ++;
B = Bt / count;
//Bs[nSplit] = B;
dP = B - P;
//Serial.print("\nsplit: ");
//Serial.print(nSplit);
Serial.print("\navg pace: ");
Serial.print(B);
Serial.print("\ndiscrepancy");
Serial.print(dP);
Bt = 0;
count = 0;
if (! dataz.publish(B)) {
//Serial.println(F("Failed"));
} else {
//Serial.println(F("OK!"));
}
}
}
}
}
/********************SETUP*********************/
void setup() {
strip.Begin();
colorWipe(black,0,31); // TURN OFF 2 front LED
//Begin wifi and serial commo
WiFi.begin(ssid, password);
colorWipe(red,1,3); // BAD YELLOW
Serial.begin(115200);
GPS.begin(9600);
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
// If wifi isn't connected, pause, yell at user until connected
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("this shit doesn't work, idiot");
}
Serial.println("connected to " + WiFi.SSID());
colorWipe(white,1,3); // WHITE GOOD
wifi_status = 1;
ctrl.setCallback(callback);
pace.setCallback(callback_pace);
period.setCallback(callback_period);
tolerance.setCallback(callback_tolerance);
// Setup sub for control feed
mqtt.subscribe(&ctrl);
mqtt.subscribe(&pace);
mqtt.subscribe(&period);
mqtt.subscribe(&tolerance);
mgr.addListener(new EvtTimeListener(t_mqtt, true, (EvtAction)mqtt_listen));
mgr.addListener(new EvtTimeListener(t_read, true, (EvtAction)geeps_read));
mgr.addListener(new EvtTimeListener(t_parse, true, (EvtAction)geeps_parse));
//mgr.addListener(new EvtTimeListener(t_ping, true, (EvtAction)mqtt_ping));
mgr.addListener(new EvtTimeListener(200, true, (EvtAction)blinkmeMEDIUM));
mgr.addListener(new EvtTimeListener(50, true, (EvtAction)blinkmeHIGH));
delay(5000);
}
/**************** LOOP **********************/
USE_EVENTUALLY_LOOP(mgr)
Kyelo Torres
9 projects • 5 followers
Hi, my name is Kyelo and I am a 4th-year mechanical engineer at UC Berkeley. My class, club and personal projects will be posted here!
Comments