Task-Driven, powersaving trial written, must test.

This commit is contained in:
2026-01-11 19:02:14 -06:00
parent 27a5e27972
commit 45fa356d66
18 changed files with 550 additions and 198 deletions

View File

@@ -1,3 +1,6 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "BLE.hpp"
#include "NimBLEDevice.h"
#include "WiFi.hpp"
@@ -6,7 +9,6 @@
#include "defines.h"
#include <mutex>
#include "bmHTTP.hpp"
#include <freertos/queue.h>
#include "setup.hpp"
#include "esp_mac.h"
@@ -26,6 +28,10 @@ static std::string UNAME = "";
// Global pointers to characteristics for notification support
std::atomic<NimBLECharacteristic*> ssidListChar = nullptr;
std::atomic<NimBLECharacteristic*> connectConfirmChar = nullptr;
// Forward declarations
bool attemptUseWiFiCreds();
bool tokenCheck();
std::atomic<NimBLECharacteristic*> authConfirmChar = nullptr;
std::atomic<NimBLECharacteristic*> credsChar = nullptr;
std::atomic<NimBLECharacteristic*> tokenChar = nullptr;
@@ -382,7 +388,7 @@ bool attemptUseWiFiCreds() {
if (!wifiConnect) {
// notify errored
notifyConnectionStatus(false);
return;
return false;
}
nvs_handle_t WiFiHandle;
@@ -391,7 +397,7 @@ bool attemptUseWiFiCreds() {
printf("ERROR Saving Credentials\n");
// notify errored
notifyConnectionStatus(false);
return;
return false;
}
else {
err = nvs_set_str(WiFiHandle, ssidTag, tmpSSID.c_str());
@@ -404,9 +410,11 @@ bool attemptUseWiFiCreds() {
if (err == ESP_OK) {
// notify connected
notifyConnectionStatus(true);
return true;
}
else {
// notify connected
// notify errored
notifyConnectionStatus(false);
return false;
}
}

View File

@@ -2,8 +2,9 @@
#include "esp_eap_client.h"
#include "cJSON.h" // Native replacement for ArduinoJson
#include "BLE.hpp"
#include "esp_wifi_he.h"
std::atomic<bool> WiFi::authFailed{false};
TaskHandle_t WiFi::awaitConnectHandle = NULL;
EventGroupHandle_t WiFi::s_wifi_event_group = NULL;
esp_netif_t* WiFi::netif = NULL;
esp_event_handler_instance_t WiFi::instance_any_id = NULL;
@@ -36,12 +37,16 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base,
case WIFI_REASON_AUTH_FAIL: // Reason 202
case WIFI_REASON_HANDSHAKE_TIMEOUT: // Reason 204
printf("ERROR: Likely Wrong Password!\n");
authFailed = true;
if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, false, eSetValueWithOverwrite);
}
break;
case WIFI_REASON_NO_AP_FOUND: // Reason 201
printf("ERROR: SSID Not Found\n");
authFailed = true;
if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, false, eSetValueWithOverwrite);
}
break;
case WIFI_REASON_ASSOC_LEAVE: // Reason 8 - Manual disconnect
@@ -51,11 +56,13 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base,
case WIFI_REASON_ASSOC_FAIL: // Reason 203 (Can be AP busy/rate limiting)
printf("Association failed, will retry...\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait 1 second before retry to avoid rate limiting
esp_netif_dhcpc_start(netif);
esp_wifi_connect();
break;
default:
printf("Retrying...\n");
esp_netif_dhcpc_start(netif);
esp_wifi_connect();
break;
}
@@ -72,7 +79,11 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base,
else 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;
printf("Got IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
esp_netif_dhcpc_stop(netif);
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, true, eSetValueWithOverwrite);
}
}
}
@@ -108,6 +119,7 @@ void WiFi::init() {
ESP_ERROR_CHECK(esp_wifi_start());
xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
awaitConnectHandle = NULL;
}
// --- CHECK STATUS ---
@@ -170,19 +182,46 @@ bool WiFi::attemptConnect(const std::string ssid, const std::string uname,
}
bool WiFi::awaitConnected() {
authFailed = false;
if (esp_wifi_connect() != ESP_OK) return false;
awaitConnectHandle = xTaskGetCurrentTaskHandle();
if (esp_wifi_connect() != ESP_OK) {
awaitConnectHandle = NULL;
return false;
}
uint8_t attempts = 0;
while (!isConnected() && attempts < 20) {
if (authFailed) {
uint32_t status;
uint8_t MAX_TIMEOUT = 10; //seconds
if (xTaskNotifyWait(0, ULONG_MAX, &status, pdMS_TO_TICKS(MAX_TIMEOUT * 1000)) == pdTRUE) {
awaitConnectHandle = NULL;
if (!status) {
printf("SSID/Password was wrong! Aborting connection attempt.\n");
return false;
}
vTaskDelay(500);
attempts++;
} else {
// Timeout - check if connected anyway
awaitConnectHandle = NULL;
}
if (isConnected()) {
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
uint8_t protocol_bitmap = 0;
esp_err_t err = esp_wifi_get_protocol(WIFI_IF_STA, &protocol_bitmap);
if (err == ESP_OK && (protocol_bitmap & WIFI_PROTOCOL_11AX)) {
// WiFi 6 (802.11ax) - Use Target Wake Time (TWT) for power saving
wifi_twt_setup_config_t twt_config = {
.setup_cmd = TWT_REQUEST,
.trigger = true,
.flow_type = 0, // Announced
.flow_id = 0,
.wake_invl_expn = 12, // Exponent for interval
.min_wake_dura = 255, // ~65ms (unit is 256 microseconds)
.wake_invl_mant = 14648, // Mantissa (mant * 2^exp = 60,000,000 us = 60s)
.timeout_time_ms = 5000,
};
esp_wifi_sta_itwt_setup(&twt_config);
}
return true;
}
if (isConnected()) return true;
return false;
}
@@ -190,8 +229,6 @@ bool WiFi::awaitConnected() {
void WiFi::scanAndUpdateSSIDList() {
printf("Starting WiFi Scan...\n");
// 1. Start Scan (Blocking Mode = true)
// In blocking mode, this function waits here until scan is done (~2 seconds)
esp_wifi_sta_enterprise_disable();
esp_wifi_disconnect();
@@ -222,7 +259,13 @@ void WiFi::processScanResults() {
printf("Heap allocation error in processScanResults\n");
return;
}
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_list));
esp_err_t err = esp_wifi_scan_get_ap_records(&ap_count, ap_list);
if (err != ESP_OK) {
printf("Failed to get scan records\n");
free(ap_list);
return;
}
// 3. Build JSON using cJSON
cJSON *root = cJSON_CreateArray();
@@ -254,4 +297,24 @@ void WiFi::processScanResults() {
free(ap_list);
cJSON_Delete(root); // This deletes all children (items) too
free(json_string); // cJSON_Print allocates memory, you must free it
}
bool WiFi::attemptDHCPrenewal() {
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
esp_netif_dhcpc_start(netif);
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT,
pdFALSE,
pdFALSE,
pdMS_TO_TICKS(4000));
if (bits & WIFI_CONNECTED_BIT) {
printf("renewal success");
// Stop the client again to save power.
esp_netif_dhcpc_stop(netif);
return true;
}
printf("DHCP Renewal failed. Reconnecting Wi-Fi...\n");
esp_wifi_disconnect();
return awaitConnected();
}

View File

@@ -14,9 +14,10 @@ class WiFi {
const std::string password, const wifi_auth_mode_t authMode);
static bool isConnected();
static void scanAndUpdateSSIDList();
static bool attemptDHCPrenewal();
private:
static TaskHandle_t awaitConnectHandle;
static void processScanResults();
static std::atomic<bool> authFailed;
static bool awaitConnected();
static esp_event_handler_instance_t instance_any_id;
static esp_event_handler_instance_t instance_got_ip;

View File

@@ -1,44 +0,0 @@
#include "bmEvents.hpp"
#include <portmacro.h>
#include <freertos/projdefs.h>
// blueprint for sending events... maybe I never use this. We'll see.
// bool send_app_event(app_event_t *event, QueueHandle_t event_queue) {
// if (event_queue == NULL) return false;
// // A. Are we in an Interrupt Service Routine (Hardware context)?
// if (xPortInIsrContext()) {
// BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// // Use the "FromISR" version
// BaseType_t result = xQueueSendFromISR(event_queue, event, &xHigherPriorityTaskWoken);
// // This is crucial for "Instant Wakeup"
// if (result == pdPASS) portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// return (result == pdPASS);
// }
// // B. Are we in a Standard Task (WiFi/BLE Callback context)?
// else {
// // Use the standard version
// // 10 ticks wait time is arbitrary; allows a small buffer if queue is full
// return (xQueueSend(event_queue, event, pdMS_TO_TICKS(10)) == pdPASS);
// }
// }
// blueprint for deleting more complex event queues
// void deinit_BLE_event_queue(QueueHandle_t& event_queue) {
// if (event_queue != NULL) {
// app_event_t temp_event;
// while (xQueueReceive(event_queue, &temp_event, 0) == pdTRUE) {
// if (temp_event.data != NULL) free(temp_event.data);
// }
// vQueueDelete(event_queue);
// event_queue = NULL;
// printf("Event queue deleted.\n");
// }
// }

View File

@@ -1,25 +0,0 @@
#ifndef BM_EVENTS_H
#define BM_EVENTS_H
#include <freertos/queue.h>
// bool send_app_event(app_event_t *event);
// void deinit_event_queue(QueueHandle_t& event_queue);
// // 1. Event Types: The "What"
// typedef enum {
// EVENT_BUTTON_PRESSED,
// EVENT_WIFI_CONNECTED,
// EVENT_WIFI_DISCONNECTED,
// EVENT_BLE_DATA_RECEIVED,
// EVENT_TIMER_TICK
// } event_type_t;
// // 2. The Message Structure: The "Payload"
// typedef struct {
// event_type_t type;
// void *data; // Optional: Pointer to data (buffer, string, etc.)
// int data_len; // Optional: Length of data
// } app_event_t;
#endif

View File

@@ -63,6 +63,59 @@ bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse) {
return success;
}
bool httpPOST(std::string endpoint, std::string token, cJSON* postData, cJSON* &JSONresponse) {
std::string url = urlBase + endpoint;
std::string responseBuffer = "";
// Convert JSON object to string
char* postString = cJSON_PrintUnformatted(postData);
if (postString == NULL) {
printf("Failed to serialize JSON for POST\n");
return false;
}
esp_http_client_config_t config = {};
config.url = url.c_str();
config.method = HTTP_METHOD_POST;
config.event_handler = _http_event_handler;
config.user_data = &responseBuffer;
if (secureSrv) {
config.transport_type = HTTP_TRANSPORT_OVER_SSL;
config.crt_bundle_attach = esp_crt_bundle_attach;
}
esp_http_client_handle_t client = esp_http_client_init(&config);
// Set headers
std::string authHeader = "Bearer " + token;
esp_http_client_set_header(client, "Authorization", authHeader.c_str());
esp_http_client_set_header(client, "Content-Type", "application/json");
// Set POST data
esp_http_client_set_post_field(client, postString, strlen(postString));
// Perform request
esp_err_t err = esp_http_client_perform(client);
bool success = false;
if (err == ESP_OK) {
int status_code = esp_http_client_get_status_code(client);
printf("Status = %d, Content Length = %d\n", status_code, esp_http_client_get_content_length(client));
if (status_code == 200 || status_code == 201) {
printf("Response: %s\n", responseBuffer.c_str());
JSONresponse = cJSON_Parse(responseBuffer.c_str());
if (JSONresponse) success = true;
}
} else {
printf("HTTP POST failed: %s\n", esp_err_to_name(err));
}
free(postString);
esp_http_client_cleanup(client);
return success;
}
void deleteWiFiAndTokenDetails() {
nvs_handle_t wifiHandle;
if (nvs_open(nvsWiFi, NVS_READWRITE, &wifiHandle) == ESP_OK) {

View File

@@ -6,6 +6,7 @@
extern std::string webToken;
bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse);
bool httpPOST(std::string endpoint, std::string token, cJSON* postData, cJSON* &JSONresponse);
void deleteWiFiAndTokenDetails();

View File

@@ -1,6 +1,16 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "calibration.hpp"
#include "defines.h"
#include "nvs_flash.h"
#include "socketIO.hpp"
#include <limits.h>
// Static member definitions
std::atomic<bool> Calibration::calibrated{false};
std::atomic<int32_t> Calibration::UpTicks{0};
std::atomic<int32_t> Calibration::DownTicks{0};
TaskHandle_t calibTaskHandle = NULL;
void Calibration::init() {
nvs_handle_t calibHandle;
@@ -109,4 +119,46 @@ uint8_t Calibration::convertToAppPos(int32_t ticks) {
// appPos between 0 and 10, convert to target encoder ticks.
int8_t retVal = (ticks - DownTicks) * 10 / (UpTicks - DownTicks);
return (retVal < 0) ? 0 : ((retVal > 10) ? 10 : retVal);
}
bool calibrate() {
calibTaskHandle = xTaskGetCurrentTaskHandle();
printf("Connecting to Socket.IO server for calibration...\n");
initSocketIO();
// Wait for device_init message from server with timeout
int timeout_count = 0;
const int MAX_TIMEOUT = 60; // seconds
uint32_t status;
// Wait for notification with timeout
if (xTaskNotifyWait(0, ULONG_MAX, &status, pdMS_TO_TICKS(MAX_TIMEOUT * 1000)) == pdTRUE) {
// Notification received within timeout
if (status) {
printf("Connected successfully, awaiting destroy command\n");
xTaskNotifyWait(0, ULONG_MAX, &status, portMAX_DELAY);
calibTaskHandle = NULL;
if (status == 2) { // calibration complete
printf("Calibration process complete\n");
stopSocketIO();
return true;
}
else { // unexpected disconnect
printf("Disconnected unexpectedly!\n");
stopSocketIO();
return false;
}
} else {
calibTaskHandle = NULL;
printf("Connection failed! Returning to setup.\n");
stopSocketIO();
return false;
}
} else {
// Timeout reached
calibTaskHandle = NULL;
printf("Timeout waiting for device_init - connection failed\n");
stopSocketIO();
return false;
}
}

View File

@@ -1,5 +1,7 @@
#ifndef CALIBRATION_H
#define CALIBRATION_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <atomic>
#include "encoder.hpp"
@@ -19,4 +21,7 @@ class Calibration {
static std::atomic<bool> calibrated;
};
extern TaskHandle_t calibTaskHandle;
bool calibrate();
#endif

172
include/mainEventLoop.cpp Normal file
View File

@@ -0,0 +1,172 @@
#include "mainEventLoop.hpp"
#include "calibration.hpp"
#include "setup.hpp"
#include "servo.hpp"
#include "bmHTTP.hpp"
#include "cJSON.h"
#include "encoder.hpp"
#include "WiFi.hpp"
TaskHandle_t wakeTaskHandle = NULL;
void wakeTimer(void* pvParameters) {
while (1) {
vTaskDelay(pdMS_TO_TICKS(60000));
main_event_type_t evt = EVENT_REQUEST_POS;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
}
}
bool postServoPos(uint8_t currentAppPos) {
// Create POST data
cJSON* posData = cJSON_CreateObject();
cJSON_AddNumberToObject(posData, "port", 1);
cJSON_AddNumberToObject(posData, "pos", currentAppPos);
// Send position update
cJSON* response = nullptr;
bool success = httpPOST("position", webToken, posData, response);
cJSON_Delete(posData);
if (success && response != nullptr) {
// Parse await_calib from response
cJSON* awaitCalibItem = cJSON_GetObjectItem(response, "await_calib");
bool awaitCalib = false;
if (cJSON_IsBool(awaitCalibItem)) {
awaitCalib = awaitCalibItem->valueint != 0;
}
printf("Position update sent: %d, await_calib=%d\n", currentAppPos, awaitCalib);
cJSON_Delete(response);
if (awaitCalib) {
Calibration::clearCalibrated();
if (!calibrate()) {
if (!WiFi::attemptDHCPrenewal())
setupAndCalibrate();
else {
if (!calibrate()) {
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
setupAndCalibrate();
}
}
}
}
}
return success;
}
bool getServoPos() {
cJSON* response = nullptr;
bool success = httpGET("position", webToken, response);
if (success && response != NULL) {
// Check if response is an array
if (cJSON_IsArray(response)) {
int arraySize = cJSON_GetArraySize(response);
// Condition 1: More than one object in array
if (arraySize > 1) {
printf("Multiple peripherals detected, entering setup.\n");
cJSON_Delete(response);
return false;
}
// Condition 2: Check peripheral_number in first object
else if (arraySize > 0) {
cJSON *firstObject = cJSON_GetArrayItem(response, 0);
if (firstObject != NULL) {
cJSON *peripheralNum = cJSON_GetObjectItem(firstObject, "peripheral_number");
if (cJSON_IsNumber(peripheralNum) && peripheralNum->valueint != 1) {
printf("Peripheral number is not 1, entering setup.\n");
cJSON_Delete(response);
return false;
}
printf("Verified new token!\n");
cJSON *awaitCalib = cJSON_GetObjectItem(firstObject, "await_calib");
if (cJSON_IsBool(awaitCalib)) {
if (awaitCalib->valueint) {
Calibration::clearCalibrated();
if (!calibrate()) {
if (!WiFi::attemptDHCPrenewal())
setupAndCalibrate();
else {
if (!calibrate()) {
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
setupAndCalibrate();
}
}
}
}
else {
cJSON* pos = cJSON_GetObjectItem(firstObject, "last_pos");
runToAppPos(pos->valueint);
}
}
cJSON_Delete(response);
}
}
}
}
return success;
}
QueueHandle_t main_event_queue = NULL;
void mainEventLoop() {
main_event_queue = xQueueCreate(10, sizeof(main_event_type_t));
main_event_type_t received_event_type;
while (true) {
if (xQueueReceive(main_event_queue, &received_event_type, portMAX_DELAY)) {
if (received_event_type == EVENT_CLEAR_CALIB) {
Calibration::clearCalibrated();
if (!calibrate()) {
if (!WiFi::attemptDHCPrenewal())
setupAndCalibrate();
else {
if (!calibrate()) {
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
setupAndCalibrate();
}
}
}
}
else if (received_event_type == EVENT_SAVE_POS) {
servoSavePos();
uint8_t currentAppPos = Calibration::convertToAppPos(topEnc->getCount());
if (!postServoPos(currentAppPos)) {
printf("Failed to send position update\n");
if (!WiFi::attemptDHCPrenewal()) {
setupAndCalibrate();
postServoPos(currentAppPos);
}
else {
if (!postServoPos(currentAppPos)) {
printf("renewed dhcp successfully, but still failed to post\n");
setupAndCalibrate();
}
}
}
}
else if (received_event_type == EVENT_REQUEST_POS) {
if (!getServoPos()) {
printf("Failed to send position update\n");
if (!WiFi::attemptDHCPrenewal()) {
setupAndCalibrate();
getServoPos();
}
else {
if (!getServoPos()) {
printf("renewed dhcp successfully, but still failed to post\n");
setupAndCalibrate();
}
}
}
}
}
}
vTaskDelete(NULL);
}

21
include/mainEventLoop.hpp Normal file
View File

@@ -0,0 +1,21 @@
#ifndef BM_EVENTS_H
#define BM_EVENTS_H
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
// Event Types
typedef enum {
EVENT_CLEAR_CALIB,
EVENT_SAVE_POS,
EVENT_REQUEST_POS
} main_event_type_t;
void mainEventLoop();
extern QueueHandle_t main_event_queue;
void wakeTimer(void* pvParameters);
extern TaskHandle_t wakeTaskHandle;
#endif

View File

@@ -1,10 +1,12 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "servo.hpp"
#include "driver/ledc.h"
#include "defines.h"
#include <freertos/FreeRTOS.h>
#include "esp_log.h"
#include "socketIO.hpp"
#include "nvs_flash.h"
#include "mainEventLoop.hpp"
std::atomic<bool> calibListen{false};
std::atomic<int32_t> baseDiff{0};
@@ -12,8 +14,6 @@ std::atomic<int32_t> target{0};
std::atomic<bool> runningManual{false};
std::atomic<bool> runningServer{false};
std::atomic<bool> clearCalibFlag{false};
std::atomic<bool> savePosFlag{false};
std::atomic<bool> startLess{false};
void servoInit() {
@@ -142,8 +142,10 @@ void initMainLoop() {
void IRAM_ATTR watchdogCallback(void* arg) {
if (runningManual || runningServer) {
// if we're trying to move and our timer ran out, we need to recalibrate
clearCalibFlag = true;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
main_event_type_t evt = EVENT_CLEAR_CALIB;
BaseType_t result = xQueueSendFromISR(main_event_queue, &evt, &xHigherPriorityTaskWoken);
if (result == pdPASS) portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
topEnc->pauseWatchdog();
// get ready for recalibration by clearing all these listeners
@@ -155,7 +157,10 @@ void IRAM_ATTR watchdogCallback(void* arg) {
else {
// if no movement is running, we're fine
// save current servo-encoder position for reinitialization
savePosFlag = true;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
main_event_type_t evt = EVENT_SAVE_POS;
BaseType_t result = xQueueSendFromISR(main_event_queue, &evt, &xHigherPriorityTaskWoken);
if (result == pdPASS) portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// clear running flags
runningManual = false;

View File

@@ -10,8 +10,6 @@
#define manual 0
extern std::atomic<bool> calibListen;
extern std::atomic<bool> clearCalibFlag;
extern std::atomic<bool> savePosFlag;
extern Encoder* topEnc;
extern Encoder* bottomEnc;

View File

@@ -1,3 +1,4 @@
#include "freertos/FreeRTOS.h"
#include "setup.hpp"
#include "BLE.hpp"
#include "WiFi.hpp"
@@ -5,17 +6,31 @@
#include "defines.h"
#include "bmHTTP.hpp"
#include "socketIO.hpp"
#include "calibration.hpp"
TaskHandle_t setupTaskHandle = NULL;
std::atomic<bool> awaitCalibration{false};
void initialSetup() {
printf("Entered Setup\n");
NimBLEAdvertising* pAdv = initBLE();
initBLE();
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
setupTaskHandle = NULL;
}
void setupLoop(void *pvParameters) {
TaskHandle_t parent_handle = (TaskHandle_t)pvParameters;
void setupAndCalibrate() {
while (1) {
setupLoop();
if (awaitCalibration) {
if (calibrate()) break;
}
else break;
}
}
void setupLoop() {
setupTaskHandle = xTaskGetCurrentTaskHandle();
bool initSuccess = false;
while(!initSuccess) {
nvs_handle_t WiFiHandle;
@@ -31,7 +46,9 @@ void setupLoop(void *pvParameters) {
// Make the RGB LED a certain color (Blue?)
nvs_close(WiFiHandle);
initialSetup();
} else if (WiFiPrefsError == ESP_OK) {
continue;
}
else if (WiFiPrefsError == ESP_OK) {
char ssid[ssidSize];
nvs_get_str(WiFiHandle, ssidTag, ssid, &ssidSize);
char pw[pwSize];
@@ -41,6 +58,7 @@ void setupLoop(void *pvParameters) {
// Make RGB LED certain color (Blue?)
printf("Found credentials, failed to connect.\n");
initialSetup();
continue;
}
else {
printf("Connected to WiFi from NVS credentials\n");
@@ -55,31 +73,86 @@ void setupLoop(void *pvParameters) {
// Use permanent device token to connect to Socket.IO
// The server will verify the token during connection handshake
webToken = std::string(token);
printf("Connecting to Socket.IO server with saved token...\n");
statusResolved = false;
initSocketIO();
// Wait for device_init message from server with timeout
int timeout_count = 0;
const int MAX_TIMEOUT = 60; // 10 seconds (20 * 500ms)
while (!statusResolved && timeout_count < MAX_TIMEOUT) {
printf("Waiting for device_init message... (%d/%d)\n", timeout_count, MAX_TIMEOUT);
vTaskDelay(pdMS_TO_TICKS(500));
timeout_count++;
}
if (timeout_count >= MAX_TIMEOUT) {
printf("Timeout waiting for device_init - connection failed\n");
stopSocketIO();
initSuccess = false;
cJSON* response = nullptr;
initSuccess = httpGET("position", webToken, response);
if (!initSuccess) {
initialSetup();
} else {
initSuccess = connected;
if (!initSuccess) {
printf("Device authentication failed - entering setup\n");
initialSetup();
}
continue;
}
initSuccess = false;
if (response != NULL) {
// Check if response is an array
if (cJSON_IsArray(response)) {
int arraySize = cJSON_GetArraySize(response);
// Condition 1: More than one object in array
if (arraySize > 1) {
printf("Multiple peripherals detected, entering setup.\n");
cJSON_Delete(response);
initialSetup();
continue;
}
// Condition 2: Check peripheral_number in first object
else if (arraySize > 0) {
cJSON *firstObject = cJSON_GetArrayItem(response, 0);
if (firstObject != NULL) {
cJSON *peripheralNum = cJSON_GetObjectItem(firstObject, "peripheral_number");
if (cJSON_IsNumber(peripheralNum) && peripheralNum->valueint != 1) {
printf("Peripheral number is not 1, entering setup.\n");
cJSON_Delete(response);
initialSetup();
continue;
}
// Valid single peripheral with number 1, continue with normal flow
initSuccess = true;
printf("Verified new token!\n");
cJSON *awaitCalib = cJSON_GetObjectItem(firstObject, "await_calib");
if (cJSON_IsBool(awaitCalib)) awaitCalibration = awaitCalib->valueint;
cJSON_Delete(response);
if (!awaitCalibration) {
// Create calibration status object
cJSON* calibPostObj = cJSON_CreateObject();
cJSON_AddNumberToObject(calibPostObj, "port", 1);
cJSON_AddBoolToObject(calibPostObj, "calibrated", Calibration::getCalibrated());
// Send calibration status to server
cJSON* calibResponse = nullptr;
bool calibSuccess = httpPOST("report_calib_status", webToken, calibPostObj, calibResponse);
if (calibSuccess && calibResponse != NULL) {
printf("Calibration status reported successfully\n");
cJSON_Delete(calibResponse);
} else {
printf("Failed to report calibration status\n");
}
cJSON_Delete(calibPostObj);
if (!Calibration::getCalibrated()) awaitCalibration = true;
}
}
else {
printf("null object\n");
cJSON_Delete(response);
initialSetup();
continue;
}
}
else {
printf("no items in array\n");
cJSON_Delete(response);
initialSetup();
continue;
}
}
else {
printf("Response not array\n");
cJSON_Delete(response);
initialSetup();
continue;
}
}
else printf("Failed to parse JSON response\n");
}
else {
printf("Token read unsuccessful, entering setup.\n");
@@ -103,6 +176,5 @@ void setupLoop(void *pvParameters) {
initialSetup();
}
}
xTaskNotifyGive(parent_handle);
vTaskDelete(NULL);
setupTaskHandle = NULL; // Clear handle on function exit (safety)
}

View File

@@ -1,10 +1,16 @@
#ifndef SETUP_H
#define SETUP_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <atomic>
extern TaskHandle_t setupTaskHandle;
extern SemaphoreHandle_t Setup_Complete_Semaphore;
extern std::atomic<bool> awaitCalibration;
void initialSetup();
void setupLoop(void *pvParameters);
void setupLoop();
void setupAndCalibrate();
#endif

View File

@@ -11,9 +11,7 @@
static esp_socketio_client_handle_t io_client;
static esp_socketio_packet_handle_t tx_packet = NULL;
std::atomic<bool> statusResolved{true};
std::atomic<bool> connected{false};
static bool stopSocketFlag = false;
// Event handler for Socket.IO events
static void socketio_event_handler(void *handler_args, esp_event_base_t base,
@@ -65,8 +63,8 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
}
// Mark connection as failed
connected = false;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
}
// Handle device_init event
else if (strcmp(eventName->valuestring, "device_init") == 0) {
@@ -91,25 +89,18 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
// TODO: UPDATE MOTOR/ENCODER STATES BASED ON THIS, as well as the successive websocket updates.
printf(" Port %d: pos=%d\n", port, lastPos);
if (port != 1) printf("ERROR: NON-1 PORT RECEIVED\n");
// Report back actual calibration status from device
else {
bool deviceCalibrated = Calibration::getCalibrated();
emitCalibStatus(deviceCalibrated);
printf(" Reported calibrated=%d for port %d\n", deviceCalibrated, port);
runToAppPos(lastPos);
}
}
}
// Now mark as connected
connected = true;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, true, eSetValueWithOverwrite);
} else {
printf("Device authentication failed\n");
Calibration::clearCalibrated();
deleteWiFiAndTokenDetails();
connected = false;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
}
}
}
@@ -125,8 +116,8 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
}
Calibration::clearCalibrated();
deleteWiFiAndTokenDetails();
connected = false;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
}
// Handle calib_start event
@@ -188,13 +179,21 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
}
else {
if (!servoCompleteCalib()) emitCalibError("Completion failed");
else emitCalibDone();
else {
emitCalibDone();
}
}
}
}
}
// Handle calib_done_ack event
else if (strcmp(eventName->valuestring, "calib_done_ack") == 0) {
printf("Server acknowledged calibration completion - safe to disconnect\n");
stopSocketFlag = true;
}
// Handle user_stage1_complete event
// Handle cancel_calib event
else if (strcmp(eventName->valuestring, "cancel_calib") == 0) {
printf("Canceling calibration process...\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
@@ -211,37 +210,6 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
}
}
}
// Handle server position change (manual or scheduled)
else if (strcmp(eventName->valuestring, "posUpdates") == 0) {
printf("Received position update from server\n");
cJSON *updateList = cJSON_GetArrayItem(json, 1);
if (cJSON_IsArray(updateList)) {
int updateCount = cJSON_GetArraySize(updateList);
printf("Processing %d position update(s)\n", updateCount);
for (int i = 0; i < updateCount; i++) {
cJSON *update = cJSON_GetArrayItem(updateList, i);
cJSON *periphNum = cJSON_GetObjectItem(update, "periphNum");
cJSON *pos = cJSON_GetObjectItem(update, "pos");
if (periphNum && cJSON_IsNumber(periphNum) &&
pos && cJSON_IsNumber(pos)) {
int port = periphNum->valueint;
int position = pos->valueint;
if (port != 1)
printf("ERROR: Received position update for non-1 port: %d\n", port);
else {
printf("Position update: position %d\n", position);
runToAppPos(position);
}
}
else printf("Invalid position update format\n");
}
}
}
}
}
@@ -293,8 +261,8 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
}
}
connected = false;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
break;
}
}
@@ -302,18 +270,21 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
// Handle WebSocket-level disconnections
if (data->websocket_event_id == WEBSOCKET_EVENT_DISCONNECTED) {
printf("WebSocket disconnected\n");
connected = false;
statusResolved = true;
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
}
if (stopSocketFlag) {
if (calibTaskHandle != NULL)
xTaskNotify(calibTaskHandle, 2, eSetValueWithOverwrite);
stopSocketFlag = false; // Clear flag after notifying once
}
}
const std::string uriString = std::string("ws") + (secureSrv ? "s" : "") + "://" + srvAddr + "/socket.io/?EIO=4&transport=websocket";
void initSocketIO() {
stopSocketFlag = false; // Reset flag for new connection
// Prepare the Authorization Header (Bearer format)
std::string authHeader = "Authorization: Bearer " + webToken + "\r\n";
statusResolved = false;
connected = false;
esp_socketio_client_config_t config = {};
config.websocket_config.uri = uriString.c_str();
@@ -338,8 +309,6 @@ void stopSocketIO() {
esp_socketio_client_destroy(io_client);
io_client = NULL;
tx_packet = NULL;
connected = false;
statusResolved = false;
}
}

View File

@@ -2,9 +2,6 @@
#define SOCKETIO_HPP
#include <atomic>
extern std::atomic<bool> statusResolved;
extern std::atomic<bool> connected;
// Initialize Socket.IO client and connect to server
void initSocketIO();

View File

@@ -9,17 +9,12 @@
#include "encoder.hpp"
#include "calibration.hpp"
#include "esp_pm.h"
#include "mainEventLoop.hpp"
// Global encoder instances
Encoder* topEnc = new Encoder(ENCODER_PIN_A, ENCODER_PIN_B);
Encoder* bottomEnc = new Encoder(InputEnc_PIN_A, InputEnc_PIN_B);
// Global encoder pointers (used by servo.cpp)
void MainTask(void *pvParameters) {
}
void mainApp() {
esp_err_t ret = nvs_flash_init(); // change to secure init logic soon!!
// 2. If NVS is full or corrupt (common after flashing new code), erase and retry
@@ -37,8 +32,11 @@ void mainApp() {
bottomEnc->init();
servoInit();
xTaskCreate(setupLoop, "Setup", 8192, xTaskGetCurrentTaskHandle(), 5, &setupTaskHandle);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
setupAndCalibrate();
xTaskCreate(wakeTimer, "wakeTimer", 1024, NULL, 5, &wakeTaskHandle);
mainEventLoop();
// TOMORROW!!!
// statusResolved = false;