Language(s) Used: C, PHP, SQL
Microcontroller(s) Used: ESP32-C6
Additional Component(s) used: IR breaker beam sensor
Description: Hangboards are most often used as a way to train your fingers for rock climbing. The training typically consists of individuals hanging off the board on a particular edge for a particular amount of time. The goal of this project was to provide me with a way to keep track of all my training data without needing to physically input anything myself.
How it works: The overall design is pretty simple. There is an IR beam sensor that goes across the front of the hangboard. Once that beam is broken, it assumes the hang has begun and begins the timer. Once the beam becomes unobstructed again, it assumes you are done hanging. It then takes that time, creates an HTTP request with your data, and sends this over WiFi to our local SQL server.
Below is a finite state machine that represents all the states the onboard timers can be in based on whether the beam is broken or not. Timer X is the main timer that counts how long an individual has been hanging. Timer Y is used to help identify false positives and false negatives on whether the hang has actually begun or ended.
Project Code
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "time.h"
//////WIFI SETUP/////////
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_http_client.h"
///////////////////////////////
/** DEFINES **/
#define WIFI_SUCCESS 1 << 0
#define WIFI_FAILURE 1 << 1
#define TCP_SUCCESS 1 << 0
#define TCP_FAILURE 1 << 1
#define MAX_FAILURES 10
/** GLOBALS **/
// event group to contain status information
static EventGroupHandle_t wifi_event_group;
// retry tracker
static int s_retry_num = 0;
// task tag
static const char *TAG = "WIFI";
#define BLINK_LED 8
#define SENSOR_PIN 10
const char *numberSpelling[10] = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
char date[50];
float hangData[10];
int hangNumber = 0;
/** FUNCTIONS **/
//event handler for wifi events
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI(TAG, "Connecting to AP...");
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (s_retry_num < MAX_FAILURES)
{
ESP_LOGI(TAG, "Reconnecting to AP...");
esp_wifi_connect();
s_retry_num++;
} else {
xEventGroupSetBits(wifi_event_group, WIFI_FAILURE);
}
}
}
//event handler for ip events
static void ip_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "STA IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(wifi_event_group, WIFI_SUCCESS);
}
}
// connect to wifi and return the result
esp_err_t connect_wifi()
{
int status = WIFI_FAILURE;
/** INITIALIZE ALL THE THINGS **/
//initialize the esp network interface
ESP_ERROR_CHECK(esp_netif_init());
//initialize default esp event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
//create wifi station in the wifi driver
esp_netif_create_default_wifi_sta();
//setup wifi station with the default wifi configuration
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/** EVENT LOOP CRAZINESS **/
wifi_event_group = xEventGroupCreate();
esp_event_handler_instance_t wifi_handler_event_instance;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &wifi_handler_event_instance));
esp_event_handler_instance_t got_ip_event_instance;
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handle, NULL, &got_ip_event_instance));
/** START THE WIFI DRIVER **/
wifi_config_t wifi_config = {
.sta = {
.ssid = /*enter wifi ssid here*/,
.password = /*enter wifi password here*/,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
// set the wifi controller to be a station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
// set the wifi config
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
// start the wifi driver
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "STA initialization complete");
/** NOW WE WAIT **/
EventBits_t bits = xEventGroupWaitBits(wifi_event_group, WIFI_SUCCESS | WIFI_FAILURE, pdFALSE, pdFALSE, portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_SUCCESS) {
ESP_LOGI(TAG, "Connected to ap");
status = WIFI_SUCCESS;
} else if (bits & WIFI_FAILURE) {
ESP_LOGI(TAG, "Failed to connect to ap");
status = WIFI_FAILURE;
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
status = WIFI_FAILURE;
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, got_ip_event_instance));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_handler_event_instance));
vEventGroupDelete(wifi_event_group);
return status;
}
esp_err_t client_event_get_handler(esp_http_client_event_handle_t evt)
{
switch (evt->event_id)
{
case HTTP_EVENT_ON_DATA:
printf("HTTP_EVENT_ON_DATA: %.*s
", evt->data_len, (char *)evt->data);
break;
default:
break;
}
return ESP_OK;
}
static void rest_get()
{
char urlString[1000] = "http://10.0.0.4/hangboard_project/hangboard_connection.php?";
strcat(urlString, date);
for (int i = 0; i < hangNumber; i++) {
char temp[100];
snprintf(temp, 50, "&time_%s=%f", numberSpelling[i], hangData[i]);
strcat(urlString,temp);
}
ESP_LOGI(TAG, "%s", urlString);
esp_http_client_config_t config_get = {
.url = urlString,
.port = 80,
.method = HTTP_METHOD_GET,
//.cert_pem = NULL,
.event_handler = client_event_get_handler};
esp_http_client_handle_t client = esp_http_client_init(&config_get);
esp_http_client_perform(client);
esp_http_client_cleanup(client);
}
void app_main(void)
{
time_t t = time(NULL);
struct tm tm = *localtime(&t);
snprintf(date, 50, "&date=%d-%d-%d", tm.tm_mon+1, tm.tm_mday, tm.tm_year+1900);
//WIFI SETTINGS//////////////////////
esp_err_t status = WIFI_FAILURE;
//initialize storage
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// connect to wireless AP
status = connect_wifi();
if (WIFI_SUCCESS != status)
{
ESP_LOGI(TAG, "Failed to associate to AP, dying...");
return;
}
int sensorState = 1;
int prevSensorState = 1;
///////TIMER 1//////////////////////////
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000,
};
gptimer_new_timer(&timer_config, &gptimer);
gptimer_enable(gptimer);
///////TIMER 2/////////////////////////
gptimer_handle_t gptimerError = NULL;
gptimer_new_timer(&timer_config, &gptimerError);
gptimer_enable(gptimerError);
//////////////////////////////////////
gpio_reset_pin(BLINK_LED);
gpio_set_direction(BLINK_LED, GPIO_MODE_OUTPUT);
gpio_reset_pin(SENSOR_PIN);
gpio_set_direction(SENSOR_PIN, GPIO_MODE_INPUT);
gpio_set_level(SENSOR_PIN, 1);
uint64_t count;
uint64_t errorCount;
bool inError = false;
while (1) {
prevSensorState = sensorState;
sensorState = gpio_get_level(SENSOR_PIN);
if (sensorState == 0) { //SENSOR IS BLOCKED (PROBABLY HANGING)
gpio_set_level(BLINK_LED, 1);
if (prevSensorState == 1 && !inError) {//if previous state was unobstructed beam
gptimer_set_raw_count(gptimer, 0);//reset timer
gptimer_start(gptimer);//start timer
gptimer_set_raw_count(gptimerError, 0);//reset error timer
errorCount = 0;
}
} else { //SENSOR IS NOT BLOCKED, CLEAR SIGHT BETWEEN LIGHT (PROBABLY NOT HANGING)
gpio_set_level(BLINK_LED, 0);
if (prevSensorState == 0) { //just stopped hanging
gptimer_get_raw_count(gptimer, &count);
if (count < 1000000) {
gptimer_set_raw_count(gptimerError, 0);//reset error timer
continue;
}
gptimer_set_raw_count(gptimerError, 0);//reset error timer
gptimer_start(gptimerError);
errorCount = 0;
inError = true;
}
if (inError) {
gptimer_get_raw_count(gptimerError, &errorCount);
}
if (errorCount > 3000000) {
gptimer_stop(gptimer); //stop timer
gptimer_get_raw_count(gptimer, &count);
ESP_LOGI("Time in hang: " ,"%llu", count - 3000000);
gptimer_stop(gptimerError);
errorCount = 0;
inError = false;
hangData[hangNumber] = (count - 3000000) / (float)1000000;
hangNumber = hangNumber + 1;
rest_get();
}
}
}
}
html>
<body>
<?php
$dbname = "hangboard_data";
$dbuser = "root";
$dbpass = "";
$dbhost = "localhost";
$connect = @mysqli_connect($dbhost,$dbuser,$dbpass,$dbname);
if(!$connect){
echo "Error: " . mysqli_connect_error();
exit();
}
echo "Connection Success!<br><br>";
$date = $_GET["date"];
$time_one = $_GET["time_one"];
$time_two = $_GET["time_two"];
$time_three = $_GET["time_three"];
$time_four = $_GET["time_four"];
$time_five = $_GET["time_five"];
$time_six = $_GET["time_six"];
$time_seven = $_GET["time_seven"];
$time_eight = $_GET["time_eight"];
$time_nine = $_GET["time_nine"];
$time_ten = $_GET["time_ten"];
//$query = "UPDATE hangboard_data SET hangtime=100, dat='$dat' WHERE hangtime = 500";
if (($result = mysqli_query($connect, "SELECT date FROM hangboard_time_data WHERE date = '$date'")) -> num_rows > 0) {
$query = "UPDATE hangboard_time_data
SET date = '$date',
time_one = '$time_one',
time_two = '$time_two',
time_three = '$time_three',
time_four = '$time_four',
time_five = '$time_five',
time_six = '$time_six',
time_seven = '$time_seven',
time_eight = '$time_eight',
time_nine = '$time_nine',
time_ten = '$time_ten'
WHERE date = '$date'";
} else {
$query = "INSERT INTO hangboard_time_data (date, time_one, time_two, time_three, time_four, time_five, time_six, time_seven, time_eight, time_nine, time_ten)
VALUES ('$date','$time_one','$time_two','$time_three','$time_four','$time_five','$time_six', '$time_seven','$time_eight','$time_nine','$time_ten')";
}
//$query = "INSERT INTO hangboard_data (hangtime, dat) VALUES ('$hangtime', '$dat')";
$result = mysqli_query($connect,$query);
echo $result;
echo "Insertion Success!<br>";
//}
?>
</body>
</html>