add resetGPIO

This commit is contained in:
2026-01-15 13:28:23 -06:00
parent bec3d91d98
commit 56d8a0f2cf
11 changed files with 15 additions and 9 deletions

View File

@@ -1,370 +0,0 @@
#include "BLE.hpp"
#include "NimBLEDevice.h"
#include "WiFi.hpp"
#include "nvs_flash.h"
#include "socketIO.hpp"
#include "defines.h"
#include <mutex>
#include "bmHTTP.hpp"
#include "esp_mac.h"
std::atomic<bool> flag_scan_requested{false};
std::atomic<bool> credsGiven{false};
std::atomic<bool> tokenGiven{false};
std::atomic<bool> isBLEClientConnected{false};
std::atomic<bool> scanBlock{false};
std::atomic<bool> finalAuth{false};
std::mutex dataMutex;
wifi_auth_mode_t auth;
static std::string SSID = "";
static std::string TOKEN = "";
static std::string PASS = "";
static std::string UNAME = "";
// Global pointers to characteristics for notification support
std::atomic<NimBLECharacteristic*> ssidListChar = nullptr;
std::atomic<NimBLECharacteristic*> connectConfirmChar = nullptr;
std::atomic<NimBLECharacteristic*> authConfirmChar = nullptr;
std::atomic<NimBLECharacteristic*> credsChar = nullptr;
std::atomic<NimBLECharacteristic*> tokenChar = nullptr;
std::atomic<NimBLECharacteristic*> ssidRefreshChar = nullptr;
std::atomic<NimBLECharacteristic*> deviceInfoChar = nullptr;
NimBLEAdvertising* initBLE() {
finalAuth = false;
NimBLEDevice::init("BlindMaster-C6");
// Optional: Boost power for better range (ESP32-C6 supports up to +20dBm)
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
// Set security
NimBLEDevice::setSecurityAuth(false, false, true); // bonding=false, mitm=false, sc=true (Secure Connections)
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); // No input/output capability
NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
pServer->advertiseOnDisconnect(true); // Automatically restart advertising on disconnect
NimBLEService *pService = pServer->createService("181C");
// Create all characteristics with callbacks
MyCharCallbacks* charCallbacks = new MyCharCallbacks();
// 0x0000 - SSID List (READ)
ssidListChar = pService->createCharacteristic(
"0000",
NIMBLE_PROPERTY::READ
);
ssidListChar.load()->createDescriptor("2902"); // Add BLE2902 descriptor for notifications
// 0x0001 - Credentials JSON (WRITE) - Replaces separate SSID/Password/Uname
// Expected JSON format: {"ssid":"network","password":"pass"}
credsChar = pService->createCharacteristic(
"0001",
NIMBLE_PROPERTY::WRITE
);
credsChar.load()->setCallbacks(charCallbacks);
// 0x0002 - Token (WRITE)
tokenChar = pService->createCharacteristic(
"0002",
NIMBLE_PROPERTY::WRITE
);
tokenChar.load()->setCallbacks(charCallbacks);
// 0x0003 - Auth Confirmation (READ + NOTIFY)
authConfirmChar = pService->createCharacteristic(
"0003",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
authConfirmChar.load()->createDescriptor("2902"); // Add BLE2902 descriptor for notifications
// 0x0004 - SSID Refresh
ssidRefreshChar = pService->createCharacteristic(
"0004",
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
ssidRefreshChar.load()->setCallbacks(charCallbacks);
// 0x0005 - Connect Confirmation (READ + NOTIFY)
connectConfirmChar = pService->createCharacteristic(
"0005",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
connectConfirmChar.load()->createDescriptor("2902"); // Add BLE2902 descriptor for notifications
// 0x0006 - Device Info (READ) - MAC address and other device details
deviceInfoChar = pService->createCharacteristic(
"0006",
NIMBLE_PROPERTY::READ
);
// Build device info JSON with MAC address
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
cJSON *infoRoot = cJSON_CreateObject();
cJSON_AddStringToObject(infoRoot, "mac", macStr);
cJSON_AddStringToObject(infoRoot, "firmware", "1.0.0");
cJSON_AddStringToObject(infoRoot, "model", "BlindMaster-C6");
char *infoJson = cJSON_PrintUnformatted(infoRoot);
deviceInfoChar.load()->setValue(std::string(infoJson));
cJSON_Delete(infoRoot);
free(infoJson);
// Start
pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID("181C");
pAdvertising->setName("BlindMaster-C6");
pAdvertising->enableScanResponse(true);
pAdvertising->setPreferredParams(0x06, 0x12); // Connection interval preferences
pAdvertising->start();
printf("BLE Started. Waiting...\n");
return pAdvertising;
}
void notifyConnectionStatus(bool success) {
NimBLECharacteristic* tmpConfChar = connectConfirmChar.load();
tmpConfChar->setValue(success ? "Connected" : "Error");
tmpConfChar->notify();
tmpConfChar->setValue(""); // Clear value after notify
}
void notifyAuthStatus(bool success) {
NimBLECharacteristic* tmpConfChar = authConfirmChar.load();
tmpConfChar->setValue(success ? "Authenticated" : "Error");
tmpConfChar->notify();
tmpConfChar->setValue(""); // Clear value after notify
}
bool BLEtick(NimBLEAdvertising* pAdvertising) {
printf("BleTick\n");
if(flag_scan_requested) {
flag_scan_requested = false;
if (!scanBlock) {
scanBlock = true;
printf("Scanning WiFi...\n");
bmWiFi.scanAndUpdateSSIDList();
}
else printf("Duplicate scan request\n");
}
else if (credsGiven) {
std::string tmpSSID;
std::string tmpUNAME;
std::string tmpPASS;
wifi_auth_mode_t tmpAUTH;
{
std::lock_guard<std::mutex> lock(dataMutex);
tmpSSID = SSID;
tmpUNAME = UNAME;
tmpPASS = PASS;
tmpAUTH = auth;
credsGiven = false;
}
bool wifiConnect;
if (tmpAUTH == WIFI_AUTH_WPA2_ENTERPRISE || tmpAUTH == WIFI_AUTH_WPA3_ENTERPRISE)
wifiConnect = bmWiFi.attemptConnect(tmpSSID.c_str(), tmpUNAME.c_str(), tmpPASS.c_str(), tmpAUTH);
else wifiConnect = bmWiFi.attemptConnect(tmpSSID.c_str(), tmpPASS.c_str(), tmpAUTH);
if (!wifiConnect) {
// notify errored
notifyConnectionStatus(false);
return false;
}
nvs_handle_t WiFiHandle;
esp_err_t err = nvs_open(nvsWiFi, NVS_READWRITE, &WiFiHandle);
if (err != ESP_OK) {
printf("ERROR Saving Credentials\n");
// notify errored
notifyConnectionStatus(false);
return false;
}
else {
err = nvs_set_str(WiFiHandle, ssidTag, tmpSSID.c_str());
if (err == ESP_OK) err = nvs_set_str(WiFiHandle, passTag, tmpPASS.c_str());
if (err == ESP_OK) err = nvs_set_str(WiFiHandle, unameTag, tmpUNAME.c_str());
if (err == ESP_OK) err = nvs_set_u8(WiFiHandle, authTag, (uint8_t)tmpAUTH);
if (err == ESP_OK) nvs_commit(WiFiHandle);
nvs_close(WiFiHandle);
}
if (err == ESP_OK) {
// notify connected
notifyConnectionStatus(true);
}
else {
// notify connected
notifyConnectionStatus(false);
}
}
else if (tokenGiven) {
tokenGiven = false;
if (!bmWiFi.isConnected()) {
printf("ERROR: token given without WiFi connection\n");
notifyAuthStatus(false);
return false;
}
// HTTP request to verify device with token
std::string tmpTOKEN;
{
std::lock_guard<std::mutex> lock(dataMutex);
tmpTOKEN = TOKEN;
}
cJSON *responseRoot;
bool success = httpGET("verify_device", tmpTOKEN, responseRoot);
if (!success) return false;
success = false;
if (responseRoot != NULL) {
cJSON *tokenItem = cJSON_GetObjectItem(responseRoot, "token");
if (cJSON_IsString(tokenItem) && tokenItem->valuestring != NULL) {
printf("New token received: %s\n", tokenItem->valuestring);
// Save token to NVS
nvs_handle_t AuthHandle;
esp_err_t nvs_err = nvs_open(nvsAuth, NVS_READWRITE, &AuthHandle);
if (nvs_err == ESP_OK) {
nvs_err = nvs_set_str(AuthHandle, tokenTag, tokenItem->valuestring);
if (nvs_err == ESP_OK) {
nvs_commit(AuthHandle);
success = true;
webToken = tokenItem->valuestring;
}
else printf("ERROR: could not save webToken to NVS\n");
nvs_close(AuthHandle);
}
else printf("ERROR: Couldn't open NVS for auth token\n");
}
cJSON_Delete(responseRoot);
}
else printf("Failed to parse JSON response\n");
finalAuth = true;
notifyAuthStatus(success);
if (success) NimBLEDevice::deinit(true); // deinitialize BLE
return success;
}
return false;
}
void reset() {
esp_wifi_scan_stop();
if (!finalAuth) esp_wifi_disconnect();
scanBlock = false;
flag_scan_requested = false;
credsGiven = false;
tokenGiven = false;
}
void MyServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
isBLEClientConnected = true;
printf("Client connected\n");
reset();
};
void MyServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
isBLEClientConnected = false;
printf("Client disconnected - reason: %d\n", reason);
reset();
}
void MyCharCallbacks::onRead(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) {
printf("Characteristic Read\n");
}
void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) {
std::string val = pChar->getValue();
std::string uuidStr = pChar->getUUID().toString();
printf("onWrite called! UUID: %s, Value length: %d\n", uuidStr.c_str(), val.length());
// Load atomic pointers for comparison
NimBLECharacteristic* currentCredsChar = credsChar.load();
NimBLECharacteristic* currentTokenChar = tokenChar.load();
NimBLECharacteristic* currentRefreshChar = ssidRefreshChar.load();
// Check which characteristic was written to
if (pChar == currentCredsChar) {
// Credentials JSON characteristic
if (val.length() > 0) {
printf("Received JSON: %s\n", val.c_str());
// Parse JSON using cJSON
cJSON *root = cJSON_Parse(val.c_str());
if (root != NULL) {
cJSON *ssid = cJSON_GetObjectItem(root, "ssid");
cJSON *password = cJSON_GetObjectItem(root, "password");
cJSON *authType = cJSON_GetObjectItem(root, "auth");
cJSON *uname = cJSON_GetObjectItem(root, "uname");
bool enterprise = false;
bool open = false;
bool error = false;
if (cJSON_IsNumber(authType)) {
enterprise = authType->valueint == WIFI_AUTH_WPA2_ENTERPRISE ||
authType->valueint == WIFI_AUTH_WPA3_ENTERPRISE;
open = authType->valueint == WIFI_AUTH_OPEN;
error = authType->valueint < 0 || authType->valueint >= WIFI_AUTH_MAX;
}
else error = true;
if (error) {
printf("ERROR: Invalid Auth mode passed in with JSON.\n");
credsGiven = false;
cJSON_Delete(root);
return;
}
bool ssidPresent = cJSON_IsString(ssid) && ssid->valuestring != NULL;
bool passPresent = cJSON_IsString(password) && password->valuestring != NULL;
bool unamePresent = cJSON_IsString(uname) && uname->valuestring != NULL;
bool tempCredsGiven = ssidPresent && (passPresent || open) &&
(unamePresent || !enterprise);
if (tempCredsGiven) {
printf("Received credentials, will attempt connection\n");
std::lock_guard<std::mutex> lock(dataMutex);
auth = (wifi_auth_mode_t)(authType->valueint);
SSID = ssid->valuestring;
PASS = passPresent ? password->valuestring : "";
UNAME = unamePresent ? uname->valuestring : "";
credsGiven = tempCredsGiven; // update the global flag.
}
else printf("ERROR: Did not receive necessary credentials.\n");
cJSON_Delete(root);
} else {
printf("Failed to parse JSON\n");
credsGiven = false;
}
}
}
else if (pChar == currentTokenChar) {
if (val.length() > 0) {
printf("Received Token: %s\n", val.c_str());
std::lock_guard<std::mutex> lock(dataMutex);
TOKEN = val;
tokenGiven = true;
}
}
else if (pChar == currentRefreshChar) {
if (val == "Start") {
// Refresh characteristic
printf("Refresh Requested\n");
flag_scan_requested = true;
}
else if (val == "Done") {
printf("Data read complete\n");
scanBlock = false;
}
}
else printf("Unknown UUID: %s\n", uuidStr.c_str());
}

View File

@@ -1,259 +0,0 @@
#include "WiFi.hpp"
#include "esp_eap_client.h"
#include "cJSON.h" // Native replacement for ArduinoJson
#include "BLE.hpp"
std::atomic<bool> WiFi::authFailed{false};
EventGroupHandle_t WiFi::s_wifi_event_group = NULL;
esp_netif_t* WiFi::netif = NULL;
esp_event_handler_instance_t WiFi::instance_any_id = NULL;
esp_event_handler_instance_t WiFi::instance_got_ip = NULL;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_STARTED_BIT BIT1
WiFi bmWiFi;
// The Event Handler (The engine room)
void WiFi::event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
// WiFi driver has finished initialization
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
printf("WiFi initialized and ready\n");
xEventGroupSetBits(s_wifi_event_group, WIFI_STARTED_BIT);
}
// We got disconnected -> Retry automatically
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
// 1. Cast the data to the correct struct
wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data;
printf("WiFi Disconnected. Reason Code: %d\n", event->reason);
// 2. Check specific Reason Codes
switch (event->reason) {
case WIFI_REASON_AUTH_EXPIRE: // Reason 2
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // Reason 15 (Most Common for Wrong Pass)
case WIFI_REASON_BEACON_TIMEOUT: // Reason 200
case WIFI_REASON_AUTH_FAIL: // Reason 202
case WIFI_REASON_HANDSHAKE_TIMEOUT: // Reason 204
printf("ERROR: Likely Wrong Password!\n");
authFailed = true;
break;
case WIFI_REASON_NO_AP_FOUND: // Reason 201
printf("ERROR: SSID Not Found\n");
authFailed = true;
break;
case WIFI_REASON_ASSOC_LEAVE: // Reason 8 - Manual disconnect
printf("Manual disconnect, not retrying\n");
break;
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_wifi_connect();
break;
default:
printf("Retrying...\n");
esp_wifi_connect();
break;
}
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
// This is triggered when the scan finishes!
printf("Scan complete, processing results...\n");
// Call a function to process results and notify BLE
processScanResults();
}
// 3. We got an IP Address -> Success!
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));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void WiFi::init() {
s_wifi_event_group = xEventGroupCreate();
// 1. Init Network Interface
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
netif = esp_netif_create_default_wifi_sta();
// 2. Init WiFi Driver
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 3. Set Mode to Station (Client)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, // The "Topic" (Base)
ESP_EVENT_ANY_ID, // The specific "Subject" (Any ID)
&event_handler, // The Function to call
NULL, // Argument to pass to the function (optional)
&instance_any_id // Where to store the registration handle
));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip
));
ESP_ERROR_CHECK(esp_wifi_start());
xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}
// --- CHECK STATUS ---
bool WiFi::isConnected() {
if (s_wifi_event_group == NULL) return false;
EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
return (bits & WIFI_CONNECTED_BIT);
}
// --- GET IP AS STRING ---
std::string WiFi::getIP() {
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(netif, &ip_info);
char buf[20];
sprintf(buf, IPSTR, IP2STR(&ip_info.ip));
return std::string(buf);
}
bool WiFi::attemptConnect(const std::string ssid, const std::string password,
const wifi_auth_mode_t authMode) {
esp_wifi_sta_enterprise_disable();
esp_wifi_disconnect();
wifi_config_t wifi_config = {};
snprintf((char*)wifi_config.sta.ssid, sizeof(wifi_config.sta.ssid), "%s", ssid.c_str());
snprintf((char*)wifi_config.sta.password, sizeof(wifi_config.sta.password), "%s", password.c_str());
wifi_config.sta.threshold.authmode = authMode;
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
return awaitConnected();
}
bool WiFi::attemptConnect(const std::string ssid, const std::string uname,
const std::string password, const wifi_auth_mode_t authMode) {
esp_wifi_disconnect();
// 1. Auto-generate the Identity
std::string identity = "anonymous";
size_t atPos = uname.find('@');
if (atPos != std::string::npos) identity += uname.substr(atPos);
printf("Real User: %s\n", uname.c_str());
printf("Outer ID : %s (Privacy Safe)\n", identity.c_str());
wifi_config_t wifi_config = {};
snprintf((char*)wifi_config.sta.ssid, sizeof(wifi_config.sta.ssid), "%s", ssid.c_str());
wifi_config.sta.threshold.authmode = authMode;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// 3. Set the calculated identity (using new ESP-IDF v5.x API)
esp_wifi_sta_enterprise_enable();
esp_eap_client_set_identity((uint8_t *)identity.c_str(), identity.length());
esp_eap_client_set_username((uint8_t *)uname.c_str(), uname.length());
esp_eap_client_set_password((uint8_t *)password.c_str(), password.length());
return awaitConnected();
}
bool WiFi::awaitConnected() {
authFailed = false;
if (esp_wifi_connect() != ESP_OK) return false;
uint8_t attempts = 0;
while (!isConnected() && attempts < 20) {
if (authFailed) {
printf("SSID/Password was wrong! Aborting connection attempt.\n");
return false;
}
vTaskDelay(500);
attempts++;
}
if (isConnected()) return true;
return false;
}
// ------------- non-class --------------
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();
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = false
};
esp_err_t err = esp_wifi_scan_start(&scan_config, false);
if (err != ESP_OK) {
printf("Scan failed!\n");
return;
}
}
void WiFi::processScanResults() {
// 2. Get the results
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
// Limit to 10 networks to save RAM/BLE MTU space
if (ap_count > 10) ap_count = 10;
wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count);
if (ap_list == NULL) {
printf("Heap allocation error in processScanResults\n");
return;
}
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_list));
// 3. Build JSON using cJSON
cJSON *root = cJSON_CreateArray();
for (int i = 0; i < ap_count; i++) {
cJSON *item = cJSON_CreateObject();
// ESP-IDF stores SSID as uint8_t, cast to char*
cJSON_AddStringToObject(item, "ssid", (char *)ap_list[i].ssid);
cJSON_AddNumberToObject(item, "rssi", ap_list[i].rssi);
// Add encryption type if you want (optional)
cJSON_AddNumberToObject(item, "auth", ap_list[i].authmode);
cJSON_AddItemToArray(root, item);
}
// 4. Convert to String
char *json_string = cJSON_PrintUnformatted(root); // Compact JSON
printf("JSON: %s\n", json_string);
// 5. Update BLE
if (ssidListChar != nullptr) {
ssidListChar.load()->setValue(std::string(json_string));
NimBLECharacteristic *tmpRefreshChar = ssidRefreshChar.load();
tmpRefreshChar->setValue("Ready");
tmpRefreshChar->notify();
}
// 6. Cleanup Memory
free(ap_list);
cJSON_Delete(root); // This deletes all children (items) too
free(json_string); // cJSON_Print allocates memory, you must free it
}

View File

@@ -1,88 +0,0 @@
#include "bmHTTP.hpp"
#include "esp_http_client.h"
#include "nvs_flash.h"
#include "defines.h"
#include "esp_crt_bundle.h"
std::string webToken;
const std::string urlBase = std::string("http") + (secureSrv ? "s" : "") + "://" + srvAddr + "/";
esp_err_t _http_event_handler(esp_http_client_event_t *evt) {
switch(evt->event_id) {
case HTTP_EVENT_ON_DATA: {
// Append received data to buffer (handles both chunked and non-chunked)
if (evt->data_len > 0 && evt->user_data != NULL) {
std::string* rxBuffer = (std::string*)evt->user_data;
rxBuffer->append((char*)evt->data, evt->data_len);
}
break;
}
default:
break;
}
return ESP_OK;
}
bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse) {
std::string url = urlBase + endpoint;
std::string responseBuffer = "";
esp_http_client_config_t config = {};
config.url = url.c_str();
config.event_handler = _http_event_handler; // Attach the bucket
config.user_data = &responseBuffer; // Pass pointer to our string so the handler can write to it
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);
// Add authorization header
std::string authHeader = "Bearer " + token;
esp_http_client_set_header(client, "Authorization", authHeader.c_str());
// Open connection and fetch headers
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) {
printf("Response: %s\n", responseBuffer.c_str());
JSONresponse = cJSON_Parse(responseBuffer.c_str());
if (JSONresponse) success = true;
}
} else {
printf("HTTP GET failed: %s\n", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
return success;
}
void deleteWiFiAndTokenDetails() {
nvs_handle_t wifiHandle;
if (nvs_open(nvsWiFi, NVS_READWRITE, &wifiHandle) == ESP_OK) {
if (nvs_erase_all(wifiHandle) == ESP_OK) {
printf("Successfully erased WiFi details\n");
nvs_commit(wifiHandle);
}
else printf("ERROR: Erase wifi failed\n");
nvs_close(wifiHandle);
}
else printf("ERROR: Failed to open WiFi section for deletion\n");
nvs_handle_t authHandle;
if (nvs_open(nvsAuth, NVS_READWRITE, &authHandle) == ESP_OK) {
if (nvs_erase_all(authHandle) == ESP_OK) {
printf("Successfully erased Auth details\n");
nvs_commit(authHandle);
}
else printf("ERROR: Erase auth failed\n");
nvs_close(authHandle);
}
else printf("ERROR: Failed to open Auth section for deletion\n");
}

View File

@@ -1,112 +0,0 @@
#include "calibration.hpp"
#include "defines.h"
#include "nvs_flash.h"
void Calibration::init() {
nvs_handle_t calibHandle;
if (nvs_open(nvsCalib, NVS_READONLY, &calibHandle) == ESP_OK) {
int32_t tempUpTicks;
int32_t tempDownTicks;
uint8_t tempCalib;
esp_err_t err = ESP_OK;
err |= nvs_get_i32(calibHandle, UpTicksTag, &tempUpTicks);
err |= nvs_get_i32(calibHandle, DownTicksTag, &tempDownTicks);
err |= nvs_get_u8(calibHandle, statusTag, &tempCalib);
if (err == ESP_OK) {
UpTicks = tempUpTicks;
DownTicks = tempDownTicks;
calibrated = tempCalib;
printf("Range: %d - %d\n", tempUpTicks, tempDownTicks);
}
else {
printf("Data missing from NVS\n");
calibrated = false;
}
nvs_close(calibHandle);
}
else {
printf("CALIBINIT: failed to open NVS - not created?\n");
calibrated = false;
}
}
bool Calibration::clearCalibrated() {
if (!calibrated) return true;
// clear variable and NVS
calibrated = false;
nvs_handle_t calibHandle;
if (nvs_open(nvsCalib, NVS_READWRITE, &calibHandle) == ESP_OK) {
if (nvs_set_u8(calibHandle, statusTag, false) != ESP_OK) {
printf("Error saving calibration status as false.\n");
return false;
}
nvs_commit(calibHandle);
nvs_close(calibHandle);
}
else {
printf("Error opening calibration NVS segment.\n");
return false;
}
return true;
}
bool Calibration::beginDownwardCalib(Encoder& topEnc) {
int32_t tempUpTicks = topEnc.getCount();
nvs_handle_t calibHandle;
if (nvs_open(nvsCalib, NVS_READWRITE, &calibHandle) == ESP_OK) {
if (nvs_set_i32(calibHandle, UpTicksTag, tempUpTicks) == ESP_OK) {
printf("Saved UpTicks to NVS\n");
UpTicks = tempUpTicks;
nvs_commit(calibHandle);
}
else {
printf("Error saving UpTicks.\n");
return false;
}
nvs_close(calibHandle);
}
else {
printf("Error opening NVS to save UpTicks\n");
return false;
}
return true;
}
bool Calibration::completeCalib(Encoder& topEnc) {
int32_t tempDownTicks = topEnc.getCount();
if (tempDownTicks == UpTicks) {
printf("ERROR: NO RANGE\n");
return false;
}
nvs_handle_t calibHandle;
if (nvs_open(nvsCalib, NVS_READWRITE, &calibHandle) == ESP_OK) {
esp_err_t err = ESP_OK;
err |= nvs_set_i32(calibHandle, DownTicksTag, tempDownTicks);
err |= nvs_set_u8(calibHandle, statusTag, true);
if (err != ESP_OK) {
printf("Error saving calibration data.\n");
return false;
}
DownTicks = tempDownTicks;
calibrated = true;
printf("Range: %d - %d\n", UpTicks.load(), tempDownTicks);
nvs_commit(calibHandle);
nvs_close(calibHandle);
}
else {
printf("Error opening calibration NVS segment.\n");
return false;
}
return true;
}
int32_t Calibration::convertToTicks(uint8_t appPos) {
// appPos between 0 and 10, convert to target encoder ticks.
return (((int32_t)appPos * (UpTicks - DownTicks)) / 10) + DownTicks;
}
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);
}

View File

@@ -1,130 +0,0 @@
#include "encoder.hpp"
#include "driver/gpio.h"
#include "esp_log.h"
#include "soc/gpio_struct.h"
#include "servo.hpp"
static const char *TAG = "ENCODER";
// Constructor
Encoder::Encoder(gpio_num_t pinA, gpio_num_t pinB)
: pin_a(pinA), pin_b(pinB), count(0),
last_state_a(0), last_state_b(0), last_count_base(0),
watchdog_handle(nullptr) {}
// Static ISR - receives Encoder instance via arg
void IRAM_ATTR Encoder::isr_handler(void* arg)
{
Encoder* encoder = static_cast<Encoder*>(arg);
// Read GPIO levels directly from hardware
uint32_t gpio_levels = GPIO.in.val;
uint8_t current_a = (gpio_levels >> encoder->pin_a) & 0x1;
uint8_t current_b = (gpio_levels >> encoder->pin_b) & 0x1;
// Quadrature decoding logic
if (current_a != encoder->last_state_a) {
if (!current_a) {
if (current_b) encoder->last_count_base++;
else encoder->last_count_base--;
}
else {
if (current_b) encoder->last_count_base--;
else encoder->last_count_base++;
}
}
else if (current_b != encoder->last_state_b) {
if (!current_b) {
if (current_a) encoder->last_count_base--;
else encoder->last_count_base++;
}
else {
if (current_a) encoder->last_count_base++;
else encoder->last_count_base--;
}
}
// Accumulate to full detent count
if (encoder->last_count_base > 3) {
encoder->count += 1;
encoder->last_count_base -= 4;
if (calibListen) servoCalibListen();
if (encoder->feedWDog) {
esp_timer_stop(encoder->watchdog_handle);
esp_timer_start_once(encoder->watchdog_handle, 500000);
debugLEDTgl();
}
if (encoder->wandListen) servoWandListen();
if (encoder->serverListen) servoServerListen();
}
else if (encoder->last_count_base < 0) {
encoder->count -= 1;
encoder->last_count_base += 4;
if (calibListen) servoCalibListen();
if (encoder->feedWDog) {
esp_timer_stop(encoder->watchdog_handle);
esp_timer_start_once(encoder->watchdog_handle, 500000);
debugLEDTgl();
}
if (encoder->wandListen) servoWandListen();
if (encoder->serverListen) servoServerListen();
}
encoder->last_state_a = current_a;
encoder->last_state_b = current_b;
}
void Encoder::init()
{
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_ANYEDGE;
io_conf.pin_bit_mask = (1ULL << pin_a) | (1ULL << pin_b);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
// Install ISR service if not already installed
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
// Attach ISR with THIS instance as argument
gpio_isr_handler_add(pin_a, Encoder::isr_handler, this);
gpio_isr_handler_add(pin_b, Encoder::isr_handler, this);
ESP_LOGI(TAG, "Encoder initialized on pins %d and %d", pin_a, pin_b);
}
void Encoder::deinit()
{
gpio_isr_handler_remove(pin_a);
gpio_isr_handler_remove(pin_b);
ESP_LOGI(TAG, "Encoder deinitialized");
}
void Encoder::setupWatchdog() {
if (watchdog_handle == NULL) {
const esp_timer_create_args_t enc_watchdog_args = {
.callback = &watchdogCallback,
.dispatch_method = ESP_TIMER_ISR,
.name = "encoder_wdt",
};
ESP_ERROR_CHECK(esp_timer_create(&enc_watchdog_args, &watchdog_handle));
}
ESP_ERROR_CHECK(esp_timer_start_once(watchdog_handle, 500000));
feedWDog = true;
}
void IRAM_ATTR Encoder::pauseWatchdog() {
if (watchdog_handle != nullptr) esp_timer_stop(watchdog_handle);
feedWDog = false;
}
Encoder::~Encoder() {
if (watchdog_handle != NULL) {
esp_timer_stop(watchdog_handle);
esp_timer_delete(watchdog_handle);
watchdog_handle = NULL;
}
}

View File

@@ -1,275 +0,0 @@
#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"
std::atomic<bool> calibListen{false};
std::atomic<int32_t> baseDiff{0};
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() {
// LEDC timer configuration (C++ aggregate initialization)
ledc_timer_config_t ledc_timer = {};
ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;
ledc_timer.timer_num = LEDC_TIMER_0;
ledc_timer.duty_resolution = LEDC_TIMER_16_BIT;
ledc_timer.freq_hz = 50;
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// LEDC channel configuration
ledc_channel_config_t ledc_channel = {};
ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;
ledc_channel.channel = servoLEDCChannel;
ledc_channel.timer_sel = LEDC_TIMER_0;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.gpio_num = servoPin;
ledc_channel.duty = offSpeed; // Start off
ledc_channel.hpoint = 0;
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
// Configure servo power switch pin as output
gpio_set_direction(servoSwitch, GPIO_MODE_OUTPUT);
gpio_set_level(servoSwitch, 0); // Start with servo power off
// Configure debug LED pin as output
gpio_reset_pin(GPIO_NUM_22);
gpio_set_direction(debugLED, GPIO_MODE_OUTPUT);
gpio_set_level(debugLED, 0); // Start with LED off
topEnc->count = servoReadPos();
if (calib.getCalibrated()) initMainLoop();
debugLEDSwitch(1);
}
void servoOn(uint8_t dir, uint8_t manOrServer) {
servoMainSwitch(1);
ledc_set_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel, (dir ? ccwSpeed : cwSpeed));
ledc_update_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel);
runningManual = !manOrServer;
runningServer = manOrServer;
}
void servoOff() {
ledc_set_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel, offSpeed);
ledc_update_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel);
runningManual = false;
runningServer = false;
servoMainSwitch(0);
}
void servoMainSwitch(uint8_t onOff) {
gpio_set_level(servoSwitch, onOff ? 1 : 0);
}
void debugLEDSwitch(uint8_t onOff) {
gpio_set_level(debugLED, onOff ? 1 : 0);
}
void debugLEDTgl() {
static bool onOff = false;
gpio_set_level(debugLED, onOff);
onOff = !onOff;
}
bool servoInitCalib() {
topEnc->pauseWatchdog();
// get ready for calibration by clearing all these listeners
bottomEnc->wandListen.store(false, std::memory_order_release);
topEnc->wandListen.store(false, std::memory_order_release);
topEnc->serverListen.store(false, std::memory_order_release);
if (!calib.clearCalibrated()) return false;
if (topEnc == nullptr || bottomEnc == nullptr) {
printf("ERROR: CALIBRATION STARTED BEFORE SERVO INITIALIZATION\n");
return false;
}
baseDiff = bottomEnc->getCount() - topEnc->getCount();
calibListen = true;
return true;
}
void servoCancelCalib() {
calibListen = false;
servoOff();
}
void servoCalibListen() {
int32_t effDiff = (bottomEnc->getCount() - topEnc->getCount()) - baseDiff;
if (effDiff > 1) servoOn(CCW, manual);
else if (effDiff < -1) {
servoOn(CW, manual);
}
else {
servoOff();
}
}
bool servoBeginDownwardCalib() {
calibListen = false;
servoOff();
vTaskDelay(pdMS_TO_TICKS(1000));
if (!calib.beginDownwardCalib(*topEnc)) return false;
baseDiff = bottomEnc->getCount() - topEnc->getCount();
calibListen = true;
return true;
}
bool servoCompleteCalib() {
calibListen = false;
servoOff();
vTaskDelay(pdMS_TO_TICKS(1000));
if (!calib.completeCalib(*topEnc)) return false;
initMainLoop();
return true;
}
void initMainLoop() {
topEnc->setupWatchdog();
servoSavePos();
bottomEnc->wandListen.store(true, std::memory_order_release);
}
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;
topEnc->pauseWatchdog();
// get ready for recalibration by clearing all these listeners
bottomEnc->wandListen.store(false, std::memory_order_release);
topEnc->wandListen.store(false, std::memory_order_release);
topEnc->serverListen.store(false, std::memory_order_release);
servoOff();
}
else {
// if no movement is running, we're fine
// save current servo-encoder position for reinitialization
savePosFlag = true;
}
// clear running flags
runningManual = false;
runningServer = false;
}
void servoSavePos() {
// save current servo-encoder position for use on reinitialization
nvs_handle_t servoHandle;
if (nvs_open(nvsServo, NVS_READWRITE, &servoHandle) == ESP_OK) {
int32_t topCount = topEnc->getCount();
if (nvs_set_i32(servoHandle, posTag, topCount) != ESP_OK)
printf("Error saving current position\n");
else printf("Success - Current position saved as: %d\n", topCount);
nvs_commit(servoHandle);
nvs_close(servoHandle);
}
else {
printf("Error opening servoPos NVS segment.\n");
}
}
int32_t servoReadPos() {
// save current servo-encoder position for use on reinitialization
int32_t val = 0;
nvs_handle_t servoHandle;
if (nvs_open(nvsServo, NVS_READONLY, &servoHandle) == ESP_OK) {
if (nvs_get_i32(servoHandle, posTag, &val) != ESP_OK)
printf("Error reading current position\n");
else printf("Success - Current position read as: %d\n", val);
nvs_close(servoHandle);
}
else {
printf("Error opening servoPos NVS segment.\n");
}
return val;
}
void stopServerRun() {
// stop listener and stop running if serverRun is still active.
topEnc->serverListen.store(false, std::memory_order_release);
if (runningServer) servoOff();
}
void servoWandListen() {
// stop any remote-initiated movement
stopServerRun();
// freeze atomic values
int32_t upBound = calib.UpTicks;
int32_t downBound = calib.DownTicks;
int32_t bottomCount = bottomEnc->getCount();
int32_t topCount = topEnc->getCount();
// ensure the baseDiff doesn't wait on wand to turn all the way back to original range.
if ((upBound > downBound && bottomCount - baseDiff > upBound)
|| (upBound < downBound && bottomCount - baseDiff < upBound))
baseDiff = bottomCount - upBound;
else if ((upBound > downBound && bottomCount - baseDiff < downBound)
|| (upBound < downBound && bottomCount - baseDiff > downBound))
baseDiff = bottomCount - downBound;
// calculate the difference between wand and top servo
int32_t effDiff = (bottomCount - topCount) - baseDiff;
// if we are at either bound, stop servo and servo-listener
// if effective difference is 0, stop servo and servo-listener
// otherwise, run servo in whichever direction necessary and
// ensure servo-listener is active.
if (topCount >= (MAX(upBound, downBound) - 1)
&& effDiff > 1) { // TODO: see whether these margins need to be removed.
servoOff();
topEnc->wandListen.store(false, std::memory_order_release);
}
else if (topCount <= (MIN(upBound, downBound) + 1)
&& effDiff < -1) {
servoOff();
topEnc->wandListen.store(false, std::memory_order_release);
}
else if (effDiff > 1) {
topEnc->wandListen.store(true, std::memory_order_release);
servoOn(CCW, manual);
}
else if (effDiff < -1) {
topEnc->wandListen.store(true, std::memory_order_release);
servoOn(CW, manual);
}
else {
servoOff();
topEnc->wandListen.store(false, std::memory_order_release);
}
}
void servoServerListen() {
// If we have reached or passed our goal, stop running and stop listener.
if (topEnc->getCount() >= target && startLess) stopServerRun();
else if (topEnc->getCount() <= target && !startLess) stopServerRun();
baseDiff = bottomEnc->getCount() - topEnc->getCount();
}
void runToAppPos(uint8_t appPos) {
// manual control takes precedence over remote control, always.
// also do not begin operation if not calibrated;
if (runningManual || !calib.getCalibrated()) return;
servoOff();
target = calib.convertToTicks(appPos); // calculate target encoder position
printf("runToAppPos Called, running to %d from %d", target.load(), topEnc->getCount());
// allow servo position to settle
vTaskDelay(pdMS_TO_TICKS(500));
int32_t topCount = topEnc->getCount();
if (abs(topCount - target) <= 1) return;
startLess = topCount < target;
if (runningManual) return; // check again before starting remote control
if (startLess) servoOn(CCW, server); // begin servo movement
else servoOn(CW, server);
topEnc->serverListen.store(true, std::memory_order_release); // start listening for shutoff point
}

View File

@@ -1,106 +0,0 @@
#include "setup.hpp"
#include "BLE.hpp"
#include "WiFi.hpp"
#include "nvs_flash.h"
#include "defines.h"
#include "bmHTTP.hpp"
#include "socketIO.hpp"
void initialSetup() {
printf("Entered Setup\n");
NimBLEAdvertising* pAdv = initBLE();
while (!BLEtick(pAdv)) {
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void setupLoop() {
bool initSuccess = false;
while(!initSuccess) {
nvs_handle_t WiFiHandle;
if (nvs_open(nvsWiFi, NVS_READONLY, &WiFiHandle) == ESP_OK) {
size_t ssidSize;
esp_err_t WiFiPrefsError = nvs_get_str(WiFiHandle, ssidTag, NULL, &ssidSize);
size_t pwSize;
WiFiPrefsError |= nvs_get_str(WiFiHandle, passTag, NULL, &pwSize);
uint8_t authMode;
WiFiPrefsError |= nvs_get_u8(WiFiHandle, authTag, &authMode);
if (WiFiPrefsError == ESP_ERR_NVS_NOT_FOUND) {
printf("Didn't find creds\n");
// Make the RGB LED a certain color (Blue?)
nvs_close(WiFiHandle);
initialSetup();
} else if (WiFiPrefsError == ESP_OK) {
char ssid[ssidSize];
nvs_get_str(WiFiHandle, ssidTag, ssid, &ssidSize);
char pw[pwSize];
nvs_get_str(WiFiHandle, passTag, pw, &pwSize);
nvs_close(WiFiHandle);
if (!bmWiFi.attemptConnect(ssid, pw, (wifi_auth_mode_t)authMode)) {
// Make RGB LED certain color (Blue?)
printf("Found credentials, failed to connect.\n");
initialSetup();
}
else {
printf("Connected to WiFi from NVS credentials\n");
nvs_handle_t authHandle;
if (nvs_open(nvsAuth, NVS_READONLY, &authHandle) == ESP_OK) {
size_t tokenSize;
if (nvs_get_str(authHandle, tokenTag, NULL, &tokenSize) == ESP_OK) {
char token[tokenSize];
nvs_get_str(authHandle, tokenTag, token, &tokenSize);
nvs_close(authHandle);
// 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;
initialSetup();
} else {
initSuccess = connected;
if (!initSuccess) {
printf("Device authentication failed - entering setup\n");
initialSetup();
}
}
}
else {
printf("Token read unsuccessful, entering setup.\n");
initialSetup();
}
}
else {
printf("Auth NVS segment doesn't exist, entering setup.\n");
initialSetup();
}
}
} else {
// Make RGB LED certain color (Blue?)
nvs_close(WiFiHandle);
printf("Program error in Wifi Connection\n");
initialSetup();
}
}
else {
printf("WiFi NVS segment doesn't exist, entering setup.\n");
initialSetup();
}
}
}

View File

@@ -1,408 +0,0 @@
#include "socketIO.hpp"
#include "esp_socketio_client.h"
#include "bmHTTP.hpp" // To access webToken
#include "WiFi.hpp"
#include "setup.hpp"
#include "cJSON.h"
#include "calibration.hpp"
#include "servo.hpp"
#include "defines.h"
#include "esp_crt_bundle.h"
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};
// Event handler for Socket.IO events
static void socketio_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_socketio_event_data_t *data = (esp_socketio_event_data_t *)event_data;
esp_socketio_packet_handle_t packet = data->socketio_packet;
switch (event_id) {
case SOCKETIO_EVENT_OPENED:
printf("Socket.IO Received OPEN packet\n");
// Connect to default namespace "/"
esp_socketio_client_connect_nsp(data->client, NULL, NULL);
break;
case SOCKETIO_EVENT_NS_CONNECTED: {
printf("Socket.IO Connected to namespace!\n");
// Check if connected to default namespace
char *nsp = esp_socketio_packet_get_nsp(packet);
if (strcmp(nsp, "/") == 0) {
printf("Connected to default namespace - waiting for device_init...\n");
}
// Don't set connected yet - wait for device_init message from server
break;
}
case SOCKETIO_EVENT_DATA: {
printf("Received Socket.IO data\n");
// Parse the received packet
cJSON *json = esp_socketio_packet_get_json(packet);
if (json) {
char *json_str = cJSON_Print(json);
printf("Data: %s\n", json_str);
// Check if this is an array event
if (cJSON_IsArray(json) && cJSON_GetArraySize(json) >= 2) {
cJSON *eventName = cJSON_GetArrayItem(json, 0);
if (cJSON_IsString(eventName)) {
// Handle error event
if (strcmp(eventName->valuestring, "error") == 0) {
printf("Received error message from server\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *message = cJSON_GetObjectItem(data, "message");
if (message && cJSON_IsString(message)) {
printf("Server error: %s\n", message->valuestring);
}
}
// Mark connection as failed
connected = false;
statusResolved = true;
}
// Handle device_init event
else if (strcmp(eventName->valuestring, "device_init") == 0) {
printf("Received device_init message\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *type = cJSON_GetObjectItem(data, "type");
if (type && strcmp(type->valuestring, "success") == 0) {
printf("Device authenticated successfully\n");
// Parse device state
cJSON *deviceState = cJSON_GetObjectItem(data, "deviceState");
if (cJSON_IsArray(deviceState)) {
int stateCount = cJSON_GetArraySize(deviceState);
printf("Device has %d peripheral(s):\n", stateCount);
for (int i = 0; i < stateCount; i++) {
cJSON *periph = cJSON_GetArrayItem(deviceState, i);
int port = cJSON_GetObjectItem(periph, "port")->valueint;
int lastPos = cJSON_GetObjectItem(periph, "lastPos")->valueint;
// 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 = calib.getCalibrated();
emitCalibStatus(deviceCalibrated);
printf(" Reported calibrated=%d for port %d\n", deviceCalibrated, port);
runToAppPos(lastPos);
}
}
}
// Now mark as connected
connected = true;
statusResolved = true;
} else {
printf("Device authentication failed\n");
calib.clearCalibrated();
deleteWiFiAndTokenDetails();
connected = false;
statusResolved = true;
}
}
}
// Handle device_deleted event
else if (strcmp(eventName->valuestring, "device_deleted") == 0) {
printf("Device has been deleted from account - disconnecting\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *message = cJSON_GetObjectItem(data, "message");
if (message && cJSON_IsString(message)) {
printf("Server message: %s\n", message->valuestring);
}
}
calib.clearCalibrated();
deleteWiFiAndTokenDetails();
connected = false;
statusResolved = true;
}
// Handle calib_start event
else if (strcmp(eventName->valuestring, "calib_start") == 0) {
printf("Device calibration begun, setting up...\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *port = cJSON_GetObjectItem(data, "port");
if (port && cJSON_IsNumber(port)) {
if (port->valueint != 1) {
printf("Error, non-1 port received for calibration\n");
emitCalibError("Non-1 Port");
}
else {
printf("Running initCalib...\n");
if (!servoInitCalib()) {
printf("initCalib returned False\n");
emitCalibError("Initialization failed");
}
else {
printf("Ready to calibrate\n");
emitCalibStage1Ready();
}
}
}
}
}
// Handle user_stage1_complete event
else if (strcmp(eventName->valuestring, "user_stage1_complete") == 0) {
printf("User completed stage 1 (tilt up), switching direction...\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *port = cJSON_GetObjectItem(data, "port");
if (port && cJSON_IsNumber(port)) {
if (port->valueint != 1) {
printf("Error, non-1 port received for calibration\n");
emitCalibError("Non-1 Port");
}
else {
if (!servoBeginDownwardCalib())
emitCalibError("Direction Switch Failed");
else emitCalibStage2Ready();
}
}
}
}
// Handle user_stage2_complete event
else if (strcmp(eventName->valuestring, "user_stage2_complete") == 0) {
printf("User completed stage 2 (tilt down), finalizing calibration...\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *port = cJSON_GetObjectItem(data, "port");
if (port && cJSON_IsNumber(port)) {
if (port->valueint != 1) {
printf("Error, non-1 port received for calibration\n");
emitCalibError("Non-1 port");
}
else {
if (!servoCompleteCalib()) emitCalibError("Completion failed");
else emitCalibDone();
}
}
}
}
// Handle user_stage1_complete event
else if (strcmp(eventName->valuestring, "cancel_calib") == 0) {
printf("Canceling calibration process...\n");
cJSON *data = cJSON_GetArrayItem(json, 1);
if (data) {
cJSON *port = cJSON_GetObjectItem(data, "port");
if (port && cJSON_IsNumber(port)) {
if (port->valueint != 1) {
printf("Error, non-1 port received for calibration\n");
emitCalibError("Non-1 Port");
}
else {
servoCancelCalib();
}
}
}
}
// 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");
}
}
}
}
}
free(json_str);
}
break;
}
case SOCKETIO_EVENT_ERROR: {
printf("Socket.IO Error!\n");
servoCancelCalib();
esp_websocket_event_data_t *ws_event = data->websocket_event;
if (ws_event) {
// 1. Check for TLS/SSL specific errors (Certificate issues)
if (ws_event->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
// This prints the "MbedTLS" error code (The low-level crypto library)
// Common codes: -0x2700 (CRT verify failed), -0x7200 (SSL handshake failed)
if (ws_event->error_handle.esp_tls_stack_err != 0) {
printf("TLS/SSL Stack Error: -0x%x\n", -ws_event->error_handle.esp_tls_stack_err);
}
// 2. Check the Certificate Verification Flags
// If this is non-zero, the certificate was rejected.
if (ws_event->error_handle.esp_tls_cert_verify_flags != 0) {
uint32_t flags = ws_event->error_handle.esp_tls_cert_verify_flags;
printf("Certificate Verification FAILED. Flags: 0x%lx\n", flags);
// Simple decoder for common flags:
if (flags & (1 << 0)) printf(" - CRT_NOT_TRUSTED (Root CA not found in bundle)\n");
if (flags & (1 << 1)) printf(" - CRT_BAD_KEY_USAGE\n");
if (flags & (1 << 2)) printf(" - CRT_EXPIRED (Check your ESP32 system time!)\n");
if (flags & (1 << 3)) printf(" - CRT_CN_MISMATCH (Domain name doesn't match cert)\n");
}
}
// 3. Check for HTTP Handshake errors (401/403/404)
// This happens if SSL worked, but the server rejected your path or token
else if (ws_event->error_handle.error_type == WEBSOCKET_ERROR_TYPE_HANDSHAKE) {
int status = ws_event->error_handle.esp_ws_handshake_status_code;
printf("HTTP Handshake Error: %d\n", status);
if (status == 401 || status == 403) {
printf("Authentication failed - invalid token\n");
} else if (status == 404) {
printf("404 Not Found - Check your URI/Path\n");
}
}
}
connected = false;
statusResolved = true;
break;
}
}
// Handle WebSocket-level disconnections
if (data->websocket_event_id == WEBSOCKET_EVENT_DISCONNECTED) {
printf("WebSocket disconnected\n");
connected = false;
statusResolved = true;
}
}
const std::string uriString = std::string("ws") + (secureSrv ? "s" : "") + "://" + srvAddr + "/socket.io/?EIO=4&transport=websocket";
void initSocketIO() {
// 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();
config.websocket_config.headers = authHeader.c_str();
if (secureSrv) {
config.websocket_config.transport = WEBSOCKET_TRANSPORT_OVER_SSL;
config.websocket_config.crt_bundle_attach = esp_crt_bundle_attach;
}
io_client = esp_socketio_client_init(&config);
tx_packet = esp_socketio_client_get_tx_packet(io_client);
esp_socketio_register_events(io_client, SOCKETIO_EVENT_ANY, socketio_event_handler, NULL);
esp_socketio_client_start(io_client);
}
void stopSocketIO() {
if (io_client != NULL) {
printf("Stopping Socket.IO client...\n");
esp_socketio_client_close(io_client, pdMS_TO_TICKS(1000));
esp_socketio_client_destroy(io_client);
io_client = NULL;
tx_packet = NULL;
connected = false;
statusResolved = false;
}
}
// Helper function to emit Socket.IO event with data
static void emitSocketEvent(const char* eventName, cJSON* data) {
if (esp_socketio_packet_set_header(tx_packet, EIO_PACKET_TYPE_MESSAGE,
SIO_PACKET_TYPE_EVENT, NULL, -1) == ESP_OK) {
cJSON *array = cJSON_CreateArray();
cJSON_AddItemToArray(array, cJSON_CreateString(eventName));
cJSON_AddItemToArray(array, data);
esp_socketio_packet_set_json(tx_packet, array);
esp_socketio_client_send_data(io_client, tx_packet);
esp_socketio_packet_reset(tx_packet);
cJSON_Delete(array);
} else {
// If packet header setup failed, clean up the data object
cJSON_Delete(data);
}
}
// Function to emit 'calib_done' as expected by your server
void emitCalibDone(int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
emitSocketEvent("calib_done", data);
}
// Function to emit 'calib_stage1_ready' to notify server device is ready for tilt up
void emitCalibStage1Ready(int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
emitSocketEvent("calib_stage1_ready", data);
}
// Function to emit 'calib_stage2_ready' to notify server device is ready for tilt down
void emitCalibStage2Ready(int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
emitSocketEvent("calib_stage2_ready", data);
}
// Function to emit 'report_calib_status' to tell server device's actual calibration state
void emitCalibStatus(bool calibrated, int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
cJSON_AddBoolToObject(data, "calibrated", calibrated);
emitSocketEvent("report_calib_status", data);
}
// Function to emit 'device_calib_error' to notify server of calibration failure
void emitCalibError(const char* errorMessage, int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
cJSON_AddStringToObject(data, "message", errorMessage);
emitSocketEvent("device_calib_error", data);
}
// Function to emit 'pos_hit' to notify server of position change
void emitPosHit(int pos, int port) {
cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "port", port);
cJSON_AddNumberToObject(data, "pos", pos);
emitSocketEvent("pos_hit", data);
}