Compare commits

14 Commits

39 changed files with 1906 additions and 683 deletions

Submodule components/esp-nimble-cpp deleted from 25af28bcad

View File

@@ -13,8 +13,28 @@ dependencies:
registry_url: https://components.espressif.com/ registry_url: https://components.espressif.com/
type: service type: service
version: 1.0.0 version: 1.0.0
esp-nimble-cpp:
component_hash: 224980602c685130c426248ad9a0500686d4a5aff0ec3a10b6dfacf00c554a91
dependencies:
- name: espressif/esp_hosted
rules:
- if: target in [esp32p4]
version: '*'
- name: espressif/esp_wifi_remote
rules:
- if: target in [esp32p4]
version: '>=0.5.3'
- name: idf
rules:
- if: target in [esp32p4]
version: '>=5.3.0'
source:
git: https://github.com/h2zero/esp-nimble-cpp.git
path: .
type: git
version: 002abf91e9779ea5646d75278ae52c6b848d3fa0
espressif/esp_websocket_client: espressif/esp_websocket_client:
component_hash: 723aba370113196c66321442426cd6452c351eef31c85c83bd1446831ef9f8f4 component_hash: c5a067a9fddea370c478017e66fac302f4b79c3d4027e9bdd42a019786cceb92
dependencies: dependencies:
- name: idf - name: idf
require: private require: private
@@ -22,13 +42,14 @@ dependencies:
source: source:
registry_url: https://components.espressif.com registry_url: https://components.espressif.com
type: service type: service
version: 1.6.0 version: 1.6.1
idf: idf:
source: source:
type: idf type: idf
version: 5.5.1 version: 5.5.1
direct_dependencies: direct_dependencies:
- bubblesnake/esp_socketio_client - bubblesnake/esp_socketio_client
manifest_hash: d73c96c5d6ddd24707089a2953e50f36a12ebbc66b5458ada3d4f55c0987ccf1 - esp-nimble-cpp
manifest_hash: 5f4bfc48b0eb389591b06fcf0a5c43c05e081427d594466829c6f7c545158221
target: esp32c6 target: esp32c6
version: 2.0.0 version: 2.0.0

View File

@@ -1,347 +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"
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;
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
// 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

@@ -23,6 +23,17 @@ class MyCharCallbacks : public NimBLECharacteristicCallbacks {
}; };
NimBLEAdvertising* initBLE(); NimBLEAdvertising* initBLE();
bool BLEtick(NimBLEAdvertising* pAdvertising);
void BLE_manager_task(void *pvParameters);
// Event Types
typedef enum {
EVENT_SCAN_REQUESTED,
EVENT_TOKEN_GIVEN,
EVENT_CREDS_GIVEN,
EVENT_SHUTDOWN
} BLE_event_type_t;
bool tokenCheck();
#endif #endif

View File

@@ -14,9 +14,10 @@ class WiFi {
const std::string password, const wifi_auth_mode_t authMode); const std::string password, const wifi_auth_mode_t authMode);
static bool isConnected(); static bool isConnected();
static void scanAndUpdateSSIDList(); static void scanAndUpdateSSIDList();
static bool attemptDHCPrenewal();
private: private:
static TaskHandle_t awaitConnectHandle;
static void processScanResults(); static void processScanResults();
static std::atomic<bool> authFailed;
static bool awaitConnected(); static bool awaitConnected();
static esp_event_handler_instance_t instance_any_id; static esp_event_handler_instance_t instance_any_id;
static esp_event_handler_instance_t instance_got_ip; static esp_event_handler_instance_t instance_got_ip;
@@ -27,6 +28,4 @@ class WiFi {
static std::string getIP(); static std::string getIP();
}; };
extern WiFi bmWiFi;
#endif #endif

View File

View File

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

View File

@@ -1,24 +1,27 @@
#ifndef CALIBRATION_H #ifndef CALIBRATION_H
#define CALIBRATION_H #define CALIBRATION_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <atomic> #include <atomic>
#include "encoder.hpp" #include "encoder.hpp"
class Calibration { class Calibration {
public: public:
void init(); static void init();
bool beginDownwardCalib(Encoder& topEnc); static bool beginDownwardCalib(Encoder& topEnc);
bool completeCalib(Encoder& topEnc); static bool completeCalib(Encoder& topEnc);
int32_t convertToTicks(uint8_t appPos); static int32_t convertToTicks(uint8_t appPos);
uint8_t convertToAppPos(int32_t ticks); static uint8_t convertToAppPos(int32_t ticks);
bool getCalibrated() {return calibrated;} static bool getCalibrated() {return calibrated;}
bool clearCalibrated(); static bool clearCalibrated();
std::atomic<int32_t> DownTicks; static std::atomic<int32_t> DownTicks;
std::atomic<int32_t> UpTicks; static std::atomic<int32_t> UpTicks;
private: private:
std::atomic<bool> calibrated; static std::atomic<bool> calibrated;
}; };
extern Calibration calib; extern TaskHandle_t calibTaskHandle;
bool calibrate();
#endif #endif

View File

@@ -31,16 +31,16 @@
// #define srvAddr "192.168.1.190:3000" // #define srvAddr "192.168.1.190:3000"
#define srvAddr "wahwa.com" #define srvAddr "wahwa.com"
#define ENCODER_PIN_A GPIO_NUM_23 // d5 #define ENCODER_PIN_A GPIO_NUM_21 // d3
#define ENCODER_PIN_B GPIO_NUM_16 // d6 #define ENCODER_PIN_B GPIO_NUM_16 // d6
#define InputEnc_PIN_A GPIO_NUM_1 // d1 #define InputEnc_PIN_A GPIO_NUM_0 // d0
#define InputEnc_PIN_B GPIO_NUM_2 // d2 #define InputEnc_PIN_B GPIO_NUM_1 // d1
#define servoPin GPIO_NUM_20 #define servoPin GPIO_NUM_20
#define servoLEDCChannel LEDC_CHANNEL_0 #define servoLEDCChannel LEDC_CHANNEL_0
#define servoSwitch GPIO_NUM_17 #define servoSwitch GPIO_NUM_17
#define debugLED GPIO_NUM_22 // d4 #define debugLED GPIO_NUM_18 // d10
#endif #endif

22
include/i2c.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef I2C_HELPER_H
#define I2C_HELPER_H
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
// I2C Configuration (Match your schematic)
#define I2C_MASTER_SCL_IO GPIO_NUM_23 // Example GPIO for C6
#define I2C_MASTER_SDA_IO GPIO_NUM_22 // Example GPIO for C6
#define I2C_MASTER_NUM 0
#define I2C_MASTER_FREQ_HZ 100000 // use standard freq
#define I2C_MASTER_TIMEOUT_MS 1000
esp_err_t i2c_init();
#ifdef __cplusplus
}
#endif
#endif

34
include/mainEventLoop.hpp Normal file
View File

@@ -0,0 +1,34 @@
#ifndef BM_EVENTS_H
#define BM_EVENTS_H
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#ifdef __cplusplus
extern "C" {
#endif
// Shared with max17048.c (C) — only C-compatible types here
typedef enum {
EVENT_CLEAR_CALIB,
EVENT_SAVE_POS,
EVENT_REQUEST_POS,
EVENT_BATTERY_CRITICAL, // stop servo, save pos, alert server, deep sleep
EVENT_BATTERY_WARNING, // alert server (no shutdown)
EVENT_REPORT_SOC, // periodic SOC sync to server
} main_event_type_t;
extern QueueHandle_t main_event_queue;
extern TaskHandle_t wakeTaskHandle;
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
void mainEventLoop();
void wakeTimer(void* pvParameters);
#endif
#endif

73
include/max17048.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef MAX_17_H
#define MAX_17_H
#define MAX17048_ADDR 0x36
#define MAX17048_REG_VCELL 0x02
#define MAX17048_REG_SOC 0x04
#define MAX17048_REG_MODE 0x06
#define MAX17048_REG_VERSION 0x08
#define MAX17048_REG_CONFIG 0x0C
#define MAX17048_REG_VALRT 0x14
#define MAX17048_REG_VRST_ID 0x18
#define MAX17048_REG_STATUS 0x1A
#define MAX17048_REG_CMD 0xFE
#define MAX17048_CMD_POR 0x5400
#define MAX17048_CMD_QSTRT 0x4000
// NOTE: maxALRT (GPIO_NUM_2) conflicts with InputEnc_PIN_B in defines.h.
// Assign maxALRT to a free GPIO before enabling the interrupt.
#define maxALRT GPIO_NUM_2
// STATUS register MSB (addr 0x1A) bit masks
// [7:X] [6:EnVR] [5:SC] [4:HD] [3:VR] [2:VL] [1:VH] [0:RI]
#define SCbit (1 << 5) // SOC changed by 1%
#define HDbit (1 << 4) // SOC crossed low threshold (CONFIG.ATHD)
#define VRbit (1 << 3) // voltage reset alert
#define VLbit (1 << 2) // VCELL below VALRT.MIN
#define VHbit (1 << 1) // VCELL above VALRT.MAX
#define RIbit (1 << 0) // reset indicator (device just powered on)
// SOC thresholds for user-facing push notifications / shutdown logic
#define SOC_WARN_20 20 // emit warning push at this level
#define SOC_WARN_10 10 // emit critical push at this level
#define SOC_CRITICAL_VL 5 // treat undervoltage as critical only below this SOC
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/i2c.h"
// Alert type carried from bms_checker_task to mainEventLoop via bms_pending_alert
typedef enum {
BATT_ALERT_OVERVOLTAGE, // VH: charging fault / damaged battery
BATT_ALERT_CRITICAL_LOW, // HD, or VL + SOC < SOC_CRITICAL_VL
BATT_ALERT_LOW_VOLTAGE_WARNING, // VL with SOC >= SOC_CRITICAL_VL (transient dip)
BATT_ALERT_SOC_LOW_20, // SOC just crossed 20% downward
BATT_ALERT_SOC_LOW_10, // SOC just crossed 10% downward
} batt_alert_type_t;
extern uint8_t established_soc;
extern volatile batt_alert_type_t bms_pending_alert;
esp_err_t max17048_init();
uint8_t bms_get_soc();
esp_err_t bms_set_alert_bound_voltages(float min, float max);
esp_err_t bms_set_reset_voltage(float vreset);
void bms_checker_task(void *pvParameters);
#define bms_set_alsc() max17048_friendly_write_reg(MAX17048_REG_CONFIG, 0, 1<<6, 0, 1<<6)
#define bms_clear_status() max17048_write_reg(MAX17048_REG_STATUS, 0, 0)
#define bms_clear_alrt() max17048_friendly_write_reg(MAX17048_REG_CONFIG, 0, 0, 0, 1<<5)
esp_err_t max17048_read_reg(uint8_t reg_addr, uint8_t *MSB, uint8_t *LSB);
esp_err_t max17048_write_reg(uint8_t reg_addr, uint8_t MSB, uint8_t LSB);
esp_err_t max17048_friendly_write_reg(uint8_t reg_addr, uint8_t MSB, uint8_t LSB,
uint8_t MSBmask, uint8_t LSBmask);
#ifdef __cplusplus
}
#endif
#endif

View File

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

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,7 +1,16 @@
#ifndef SETUP_H #ifndef SETUP_H
#define SETUP_H #define SETUP_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <atomic>
extern TaskHandle_t setupTaskHandle;
extern std::atomic<bool> awaitCalibration;
void initialSetup(); void initialSetup();
void setupLoop(); void setupLoop();
void setupAndCalibrate();
#endif #endif

View File

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

View File

@@ -132,7 +132,6 @@ CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_VALID_GPIO_MASK=0
CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=8 CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=8
CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x000000007FFFFF00 CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x000000007FFFFF00
CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y
CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y
CONFIG_SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP=y CONFIG_SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP=y
CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y
CONFIG_SOC_CLOCKOUT_HAS_SOURCE_GATE=y CONFIG_SOC_CLOCKOUT_HAS_SOURCE_GATE=y
@@ -1153,7 +1152,6 @@ CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y
CONFIG_ESP_COEX_ENABLED=y CONFIG_ESP_COEX_ENABLED=y
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set # CONFIG_ESP_COEX_POWER_MANAGEMENT is not set
# CONFIG_ESP_COEX_GPIO_DEBUG is not set
# end of Wireless Coexistence # end of Wireless Coexistence
# #
@@ -1294,6 +1292,7 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
# ESP-Driver:USB Serial/JTAG Configuration # ESP-Driver:USB Serial/JTAG Configuration
# #
CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
# CONFIG_USJ_NO_AUTO_LS_ON_CONNECTION is not set
# end of ESP-Driver:USB Serial/JTAG Configuration # end of ESP-Driver:USB Serial/JTAG Configuration
# #
@@ -1424,6 +1423,7 @@ CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0
# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set # CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set
# CONFIG_ESP_SLEEP_DEBUG is not set # CONFIG_ESP_SLEEP_DEBUG is not set
CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y
# CONFIG_ESP_SLEEP_EVENT_CALLBACKS is not set
# end of Sleep Config # end of Sleep Config
# #
@@ -1528,6 +1528,7 @@ CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y
# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set # CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP_PHY_MAX_TX_POWER=20 CONFIG_ESP_PHY_MAX_TX_POWER=20
CONFIG_ESP_PHY_MAC_BB_PD=y
# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set # CONFIG_ESP_PHY_REDUCE_TX_POWER is not set
# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set # CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set
CONFIG_ESP_PHY_RF_CAL_PARTIAL=y CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
@@ -1545,11 +1546,18 @@ CONFIG_ESP_PHY_IRAM_OPT=y
# Power Management # Power Management
# #
CONFIG_PM_SLEEP_FUNC_IN_IRAM=y CONFIG_PM_SLEEP_FUNC_IN_IRAM=y
# CONFIG_PM_ENABLE is not set CONFIG_PM_ENABLE=y
CONFIG_PM_DFS_INIT_AUTO=y
# CONFIG_PM_PROFILING is not set
CONFIG_PM_TRACE=y
CONFIG_PM_SLP_IRAM_OPT=y CONFIG_PM_SLP_IRAM_OPT=y
CONFIG_PM_RTOS_IDLE_OPT=y
CONFIG_PM_SLP_DISABLE_GPIO=y
CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y
CONFIG_PM_LIGHTSLEEP_RTC_OSC_CAL_INTERVAL=1
CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y
# CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is not set CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
# CONFIG_PM_LIGHT_SLEEP_CALLBACKS is not set
# end of Power Management # end of Power Management
# #
@@ -1612,7 +1620,7 @@ CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=y
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set # CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
@@ -1708,6 +1716,7 @@ CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y
# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set # CONFIG_ESP_WIFI_GCMP_SUPPORT is not set
CONFIG_ESP_WIFI_GMAC_SUPPORT=y CONFIG_ESP_WIFI_GMAC_SUPPORT=y
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
# CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP is not set
# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set # CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7
CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y
@@ -1830,6 +1839,8 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set # CONFIG_FREERTOS_USE_TRACE_FACILITY is not set
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set # CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set # CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
# end of Kernel # end of Kernel
@@ -1909,6 +1920,7 @@ CONFIG_IEEE802154_CCA_THRESHOLD=-60
CONFIG_IEEE802154_PENDING_TABLE_SIZE=20 CONFIG_IEEE802154_PENDING_TABLE_SIZE=20
# CONFIG_IEEE802154_MULTI_PAN_ENABLE is not set # CONFIG_IEEE802154_MULTI_PAN_ENABLE is not set
CONFIG_IEEE802154_TIMING_OPTIMIZATION=y CONFIG_IEEE802154_TIMING_OPTIMIZATION=y
# CONFIG_IEEE802154_SLEEP_ENABLE is not set
# CONFIG_IEEE802154_DEBUG is not set # CONFIG_IEEE802154_DEBUG is not set
# CONFIG_IEEE802154_DEBUG_ASSERT_MONITOR is not set # CONFIG_IEEE802154_DEBUG_ASSERT_MONITOR is not set
# end of IEEE 802.15.4 # end of IEEE 802.15.4
@@ -2344,13 +2356,6 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
# #
# CONFIG_OPENTHREAD_ENABLED is not set # CONFIG_OPENTHREAD_ENABLED is not set
#
# Thread Console
#
CONFIG_OPENTHREAD_CLI=y
CONFIG_OPENTHREAD_CONSOLE_COMMAND_PREFIX="ot"
# end of Thread Console
# #
# OpenThread Spinel # OpenThread Spinel
# #
@@ -2563,6 +2568,10 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
# end of Wi-Fi Provisioning Manager # end of Wi-Fi Provisioning Manager
#
# ESP Socket.IO client
#
# #
# ESP-NimBLE-CPP configuration # ESP-NimBLE-CPP configuration
# #
@@ -2585,10 +2594,6 @@ CONFIG_NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT=31
CONFIG_NIMBLE_CPP_IDF=y CONFIG_NIMBLE_CPP_IDF=y
# end of ESP-NimBLE-CPP configuration # end of ESP-NimBLE-CPP configuration
#
# ESP Socket.IO client
#
# #
# ESP WebSocket client # ESP WebSocket client
# #
@@ -2696,12 +2701,14 @@ CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set # CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set
CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20 CONFIG_ESP32_PHY_MAX_TX_POWER=20
CONFIG_MAC_BB_PD=y
CONFIG_ESP32_PHY_MAC_BB_PD=y
# CONFIG_REDUCE_PHY_TX_POWER is not set # CONFIG_REDUCE_PHY_TX_POWER is not set
# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set # CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_MAIN_TASK_STACK_SIZE=3584 CONFIG_MAIN_TASK_STACK_SIZE=8192
CONFIG_CONSOLE_UART_DEFAULT=y CONFIG_CONSOLE_UART_DEFAULT=y
# CONFIG_CONSOLE_UART_CUSTOM is not set # CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set # CONFIG_CONSOLE_UART_NONE is not set
@@ -2800,5 +2807,4 @@ CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
CONFIG_SUPPORT_TERMIOS=y CONFIG_SUPPORT_TERMIOS=y
CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1
CONFIG_NIMBLE_ENABLED=y
# End of deprecated options # End of deprecated options

View File

@@ -132,7 +132,6 @@ CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_VALID_GPIO_MASK=0
CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=8 CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=8
CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x000000007FFFFF00 CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x000000007FFFFF00
CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y
CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y
CONFIG_SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP=y CONFIG_SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP=y
CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y
CONFIG_SOC_CLOCKOUT_HAS_SOURCE_GATE=y CONFIG_SOC_CLOCKOUT_HAS_SOURCE_GATE=y
@@ -1153,7 +1152,6 @@ CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y
CONFIG_ESP_COEX_ENABLED=y CONFIG_ESP_COEX_ENABLED=y
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set # CONFIG_ESP_COEX_POWER_MANAGEMENT is not set
# CONFIG_ESP_COEX_GPIO_DEBUG is not set
# end of Wireless Coexistence # end of Wireless Coexistence
# #
@@ -1294,6 +1292,7 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
# ESP-Driver:USB Serial/JTAG Configuration # ESP-Driver:USB Serial/JTAG Configuration
# #
CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
# CONFIG_USJ_NO_AUTO_LS_ON_CONNECTION is not set
# end of ESP-Driver:USB Serial/JTAG Configuration # end of ESP-Driver:USB Serial/JTAG Configuration
# #
@@ -1424,6 +1423,7 @@ CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0
# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set # CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set
# CONFIG_ESP_SLEEP_DEBUG is not set # CONFIG_ESP_SLEEP_DEBUG is not set
CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y
# CONFIG_ESP_SLEEP_EVENT_CALLBACKS is not set
# end of Sleep Config # end of Sleep Config
# #
@@ -1528,6 +1528,7 @@ CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y
# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set # CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP_PHY_MAX_TX_POWER=20 CONFIG_ESP_PHY_MAX_TX_POWER=20
CONFIG_ESP_PHY_MAC_BB_PD=y
# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set # CONFIG_ESP_PHY_REDUCE_TX_POWER is not set
# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set # CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set
CONFIG_ESP_PHY_RF_CAL_PARTIAL=y CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
@@ -1545,11 +1546,18 @@ CONFIG_ESP_PHY_IRAM_OPT=y
# Power Management # Power Management
# #
CONFIG_PM_SLEEP_FUNC_IN_IRAM=y CONFIG_PM_SLEEP_FUNC_IN_IRAM=y
# CONFIG_PM_ENABLE is not set CONFIG_PM_ENABLE=y
CONFIG_PM_DFS_INIT_AUTO=y
# CONFIG_PM_PROFILING is not set
CONFIG_PM_TRACE=y
CONFIG_PM_SLP_IRAM_OPT=y CONFIG_PM_SLP_IRAM_OPT=y
CONFIG_PM_RTOS_IDLE_OPT=y
CONFIG_PM_SLP_DISABLE_GPIO=y
CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y
CONFIG_PM_LIGHTSLEEP_RTC_OSC_CAL_INTERVAL=1
CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y
# CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is not set CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
# CONFIG_PM_LIGHT_SLEEP_CALLBACKS is not set
# end of Power Management # end of Power Management
# #
@@ -1664,7 +1672,7 @@ CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1
CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 CONFIG_ESP_TIMER_TASK_AFFINITY=0x0
CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y
CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y
# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_ESP_TIMER_IMPL_SYSTIMER=y CONFIG_ESP_TIMER_IMPL_SYSTIMER=y
# end of ESP Timer (High Resolution Timer) # end of ESP Timer (High Resolution Timer)
@@ -1708,6 +1716,7 @@ CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y
# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set # CONFIG_ESP_WIFI_GCMP_SUPPORT is not set
CONFIG_ESP_WIFI_GMAC_SUPPORT=y CONFIG_ESP_WIFI_GMAC_SUPPORT=y
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
# CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP is not set
# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set # CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7
CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y
@@ -1830,6 +1839,8 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set # CONFIG_FREERTOS_USE_TRACE_FACILITY is not set
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set # CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set # CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
# end of Kernel # end of Kernel
@@ -1909,6 +1920,7 @@ CONFIG_IEEE802154_CCA_THRESHOLD=-60
CONFIG_IEEE802154_PENDING_TABLE_SIZE=20 CONFIG_IEEE802154_PENDING_TABLE_SIZE=20
# CONFIG_IEEE802154_MULTI_PAN_ENABLE is not set # CONFIG_IEEE802154_MULTI_PAN_ENABLE is not set
CONFIG_IEEE802154_TIMING_OPTIMIZATION=y CONFIG_IEEE802154_TIMING_OPTIMIZATION=y
# CONFIG_IEEE802154_SLEEP_ENABLE is not set
# CONFIG_IEEE802154_DEBUG is not set # CONFIG_IEEE802154_DEBUG is not set
# CONFIG_IEEE802154_DEBUG_ASSERT_MONITOR is not set # CONFIG_IEEE802154_DEBUG_ASSERT_MONITOR is not set
# end of IEEE 802.15.4 # end of IEEE 802.15.4
@@ -2344,13 +2356,6 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
# #
# CONFIG_OPENTHREAD_ENABLED is not set # CONFIG_OPENTHREAD_ENABLED is not set
#
# Thread Console
#
CONFIG_OPENTHREAD_CLI=y
CONFIG_OPENTHREAD_CONSOLE_COMMAND_PREFIX="ot"
# end of Thread Console
# #
# OpenThread Spinel # OpenThread Spinel
# #
@@ -2563,6 +2568,10 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
# end of Wi-Fi Provisioning Manager # end of Wi-Fi Provisioning Manager
#
# ESP Socket.IO client
#
# #
# ESP-NimBLE-CPP configuration # ESP-NimBLE-CPP configuration
# #
@@ -2585,10 +2594,6 @@ CONFIG_NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT=31
CONFIG_NIMBLE_CPP_IDF=y CONFIG_NIMBLE_CPP_IDF=y
# end of ESP-NimBLE-CPP configuration # end of ESP-NimBLE-CPP configuration
#
# ESP Socket.IO client
#
# #
# ESP WebSocket client # ESP WebSocket client
# #

445
src/BLE.cpp Normal file
View File

@@ -0,0 +1,445 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "BLE.hpp"
#include "WiFi.hpp"
#include "nvs_flash.h"
#include "socketIO.hpp"
#include "defines.h"
#include <mutex>
#include "bmHTTP.hpp"
#include "setup.hpp"
#include "esp_mac.h"
#include "esp_netif.h"
std::atomic<bool> flag_scan_requested{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;
// Forward declarations
bool attemptUseWiFiCreds();
bool tokenCheck();
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;
static QueueHandle_t BLE_event_queue = NULL;
static TaskHandle_t BLE_manager_task_handle = NULL;
SemaphoreHandle_t BLE_Queue_Shutdown_Semaphore = NULL;
NimBLEAdvertising* initBLE() {
BLE_event_queue = xQueueCreate(10, sizeof(BLE_event_type_t));
xTaskCreate(BLE_manager_task, "BLE", 8192, NULL, 5, &BLE_manager_task_handle);
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
}
void reset() {
BLE_Queue_Shutdown_Semaphore = xSemaphoreCreateBinary();
BLE_event_type_t event_type = EVENT_SHUTDOWN;
xQueueSend(BLE_event_queue, &event_type, portMAX_DELAY);
xSemaphoreTake(BLE_Queue_Shutdown_Semaphore, portMAX_DELAY);
vQueueDelete(BLE_event_queue);
esp_wifi_scan_stop();
if (!finalAuth) esp_wifi_disconnect();
scanBlock = false;
vSemaphoreDelete(BLE_Queue_Shutdown_Semaphore);
BLE_event_queue = xQueueCreate(10, sizeof(BLE_event_type_t));
xTaskCreate(BLE_manager_task, "BLE", 8192, NULL, 5, &BLE_manager_task_handle);
}
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);
if (!finalAuth) 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");
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 : "";
}
BLE_event_type_t event_type = EVENT_CREDS_GIVEN;
if (xQueueSend(BLE_event_queue, &event_type, pdMS_TO_TICKS(10)) == pdPASS)
printf("Successfully added credsGiven to event queue\n");
else printf("CredsGiven event queue addition failed\n");
}
else printf("ERROR: Did not receive necessary credentials.\n");
cJSON_Delete(root);
} else {
printf("Failed to parse JSON\n");
}
}
}
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;
}
BLE_event_type_t event_type = EVENT_TOKEN_GIVEN;
if (xQueueSend(BLE_event_queue, &event_type, pdMS_TO_TICKS(10)) == pdPASS)
printf("Successfully added tokenGiven to event queue\n");
else printf("TokenGiven event queue addition failed\n");
}
}
else if (pChar == currentRefreshChar) {
if (val == "Start") {
// Refresh characteristic
BLE_event_type_t event_type = EVENT_SCAN_REQUESTED;
if (xQueueSend(BLE_event_queue, &event_type, pdMS_TO_TICKS(10)) == pdPASS) {
printf("Event queue addition success for scan start\n");
}
else printf("Scan start event queue addition failed\n");
}
else if (val == "Done") {
printf("Data read complete\n");
scanBlock = false;
}
}
else printf("Unknown UUID: %s\n", uuidStr.c_str());
}
void BLE_manager_task(void *pvParameters) {
BLE_event_type_t received_event_type;
while (true) {
if (xQueueReceive(BLE_event_queue, &received_event_type, portMAX_DELAY)) {
if (received_event_type == EVENT_SCAN_REQUESTED) {
printf("Refresh Requested\n");
if (!scanBlock) {
scanBlock = true;
printf("Scanning WiFi...\n");
WiFi::scanAndUpdateSSIDList();
}
else printf("Duplicate scan request\n");
}
else if (received_event_type == EVENT_TOKEN_GIVEN) {
if (tokenCheck()) {
vQueueDelete(BLE_event_queue);
if (setupTaskHandle != NULL) {
xTaskNotifyGive(setupTaskHandle);
printf("Setup complete.\n");
}
break;
}
}
else if (received_event_type == EVENT_CREDS_GIVEN)
attemptUseWiFiCreds();
else if (received_event_type == EVENT_SHUTDOWN) break;
}
}
xSemaphoreGive(BLE_Queue_Shutdown_Semaphore); // this is null-safe
vTaskDelete(NULL);
}
bool tokenCheck() {
if (!WiFi::isConnected()) {
printf("ERROR: token given without WiFi connection\n");
notifyAuthStatus(false);
return false;
}
vTaskDelay(pdMS_TO_TICKS(500));
// --- DIAGNOSTIC: snapshot network state before HTTP ---
{
extern esp_netif_t* _diag_netif __attribute__((weak));
// Use esp_netif_get_handle_from_ifkey to get the STA netif without changing headers
esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif) {
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(sta_netif, &ip_info);
printf("[DIAG tokenCheck] IP: " IPSTR "\n", IP2STR(&ip_info.ip));
esp_netif_dns_info_t dns;
if (esp_netif_get_dns_info(sta_netif, ESP_NETIF_DNS_MAIN, &dns) == ESP_OK)
printf("[DIAG tokenCheck] DNS: " IPSTR "\n", IP2STR(&dns.ip.u_addr.ip4));
else
printf("[DIAG tokenCheck] DNS: NOT SET\n");
} else {
printf("[DIAG tokenCheck] Could not get STA netif handle\n");
}
printf("[DIAG tokenCheck] WiFi::isConnected() = %d\n", (int)WiFi::isConnected());
}
// 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) {
notifyAuthStatus(false);
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;
}
bool attemptUseWiFiCreds() {
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;
}
bool wifiConnect;
if (tmpAUTH == WIFI_AUTH_WPA2_ENTERPRISE || tmpAUTH == WIFI_AUTH_WPA3_ENTERPRISE)
wifiConnect = WiFi::attemptConnect(tmpSSID.c_str(), tmpUNAME.c_str(), tmpPASS.c_str(), tmpAUTH);
else wifiConnect = WiFi::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);
return true;
}
else {
// notify errored
notifyConnectionStatus(false);
return false;
}
}

View File

@@ -1,8 +1,8 @@
# This file was automatically generated for projects # This file was automatically generated for projects
# without default 'CMakeLists.txt' file. # without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.* ${CMAKE_SOURCE_DIR}/include/*.cpp) FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.* ${CMAKE_SOURCE_DIR}/test/*.*)
idf_component_register(SRCS ${app_sources} idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "." INCLUDE_DIRS "." "${CMAKE_SOURCE_DIR}/test"
REQUIRES nvs_flash esp-nimble-cpp esp_socketio_client) REQUIRES nvs_flash esp-nimble-cpp esp_socketio_client)

View File

@@ -2,8 +2,11 @@
#include "esp_eap_client.h" #include "esp_eap_client.h"
#include "cJSON.h" // Native replacement for ArduinoJson #include "cJSON.h" // Native replacement for ArduinoJson
#include "BLE.hpp" #include "BLE.hpp"
#include "esp_wifi_he.h"
#include "esp_netif.h"
#include "lwip/dns.h"
std::atomic<bool> WiFi::authFailed{false}; TaskHandle_t WiFi::awaitConnectHandle = NULL;
EventGroupHandle_t WiFi::s_wifi_event_group = NULL; EventGroupHandle_t WiFi::s_wifi_event_group = NULL;
esp_netif_t* WiFi::netif = NULL; esp_netif_t* WiFi::netif = NULL;
esp_event_handler_instance_t WiFi::instance_any_id = NULL; esp_event_handler_instance_t WiFi::instance_any_id = NULL;
@@ -12,8 +15,6 @@ esp_event_handler_instance_t WiFi::instance_got_ip = NULL;
#define WIFI_CONNECTED_BIT BIT0 #define WIFI_CONNECTED_BIT BIT0
#define WIFI_STARTED_BIT BIT1 #define WIFI_STARTED_BIT BIT1
WiFi bmWiFi;
// The Event Handler (The engine room) // The Event Handler (The engine room)
void WiFi::event_handler(void* arg, esp_event_base_t event_base, void WiFi::event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) { int32_t event_id, void* event_data) {
@@ -38,26 +39,33 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base,
case WIFI_REASON_AUTH_FAIL: // Reason 202 case WIFI_REASON_AUTH_FAIL: // Reason 202
case WIFI_REASON_HANDSHAKE_TIMEOUT: // Reason 204 case WIFI_REASON_HANDSHAKE_TIMEOUT: // Reason 204
printf("ERROR: Likely Wrong Password!\n"); printf("ERROR: Likely Wrong Password!\n");
authFailed = true; if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, false, eSetValueWithOverwrite);
}
break; break;
case WIFI_REASON_NO_AP_FOUND: // Reason 201 case WIFI_REASON_NO_AP_FOUND: // Reason 201
printf("ERROR: SSID Not Found\n"); printf("ERROR: SSID Not Found\n");
authFailed = true; if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, false, eSetValueWithOverwrite);
}
break; break;
case WIFI_REASON_ASSOC_LEAVE: // Reason 8 - Manual disconnect case WIFI_REASON_ASSOC_LEAVE: // Reason 8 - Manual disconnect
printf("Manual disconnect, not retrying\n"); printf("Manual disconnect, not retrying\n");
esp_netif_dhcpc_start(netif);
break; break;
case WIFI_REASON_ASSOC_FAIL: // Reason 203 (Can be AP busy/rate limiting) case WIFI_REASON_ASSOC_FAIL: // Reason 203 (Can be AP busy/rate limiting)
printf("Association failed, will retry...\n"); printf("Association failed, will retry...\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait 1 second before retry to avoid rate limiting vTaskDelay(pdMS_TO_TICKS(1000)); // Wait 1 second before retry to avoid rate limiting
esp_netif_dhcpc_start(netif);
esp_wifi_connect(); esp_wifi_connect();
break; break;
default: default:
printf("Retrying...\n"); printf("Retrying...\n");
esp_netif_dhcpc_start(netif);
esp_wifi_connect(); esp_wifi_connect();
break; break;
} }
@@ -75,6 +83,9 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base,
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
printf("Got IP: " IPSTR "\n", IP2STR(&event->ip_info.ip)); printf("Got IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
if (awaitConnectHandle != NULL) {
xTaskNotify(awaitConnectHandle, true, eSetValueWithOverwrite);
}
} }
} }
@@ -110,6 +121,7 @@ void WiFi::init() {
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY); xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
awaitConnectHandle = NULL;
} }
// --- CHECK STATUS --- // --- CHECK STATUS ---
@@ -172,19 +184,59 @@ bool WiFi::attemptConnect(const std::string ssid, const std::string uname,
} }
bool WiFi::awaitConnected() { bool WiFi::awaitConnected() {
authFailed = false; awaitConnectHandle = xTaskGetCurrentTaskHandle();
if (esp_wifi_connect() != ESP_OK) return false; if (esp_wifi_connect() != ESP_OK) {
awaitConnectHandle = NULL;
return false;
}
uint8_t attempts = 0; uint32_t status;
while (!isConnected() && attempts < 20) { uint8_t MAX_TIMEOUT = 10; //seconds
if (authFailed) { 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"); printf("SSID/Password was wrong! Aborting connection attempt.\n");
return false; return false;
} }
vTaskDelay(500); } else {
attempts++; // Timeout - check if connected anyway
awaitConnectHandle = NULL;
}
if (isConnected()) {
// --- DIAGNOSTIC: snapshot network state before DHCP stop ---
// Save IP and DNS before stopping DHCP — dhcpc_stop clears both
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(netif, &ip_info);
esp_netif_dns_info_t dns_main, dns_backup;
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_main);
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_backup);
esp_netif_dhcpc_stop(netif);
// Re-apply IP and DNS as static configuration
esp_netif_set_ip_info(netif, &ip_info);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_main);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_backup);
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; return false;
} }
@@ -192,8 +244,6 @@ bool WiFi::awaitConnected() {
void WiFi::scanAndUpdateSSIDList() { void WiFi::scanAndUpdateSSIDList() {
printf("Starting WiFi Scan...\n"); 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_sta_enterprise_disable();
esp_wifi_disconnect(); esp_wifi_disconnect();
@@ -224,7 +274,13 @@ void WiFi::processScanResults() {
printf("Heap allocation error in processScanResults\n"); printf("Heap allocation error in processScanResults\n");
return; 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 // 3. Build JSON using cJSON
cJSON *root = cJSON_CreateArray(); cJSON *root = cJSON_CreateArray();
@@ -257,3 +313,32 @@ void WiFi::processScanResults() {
cJSON_Delete(root); // This deletes all children (items) too cJSON_Delete(root); // This deletes all children (items) too
free(json_string); // cJSON_Print allocates memory, you must free it 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\n");
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(netif, &ip_info);
esp_netif_dns_info_t dns_main, dns_backup;
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_main);
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_backup);
esp_netif_dhcpc_stop(netif);
esp_netif_set_ip_info(netif, &ip_info);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_main);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_backup);
return true;
}
printf("DHCP Renewal failed. Reconnecting Wi-Fi...\n");
esp_wifi_disconnect();
return awaitConnected();
}

10
src/batteryManagement.cpp Normal file
View File

@@ -0,0 +1,10 @@
// // 3. Post Event to System Loop
// battery_data_t data = { .soc = soc, .voltage = voltage };
// esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_UPDATE, &data, sizeof(data), 0);
// // Optional: Post warnings
// if (soc < 20.0) {
// esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_LOW, NULL, 0, 0);
// }

View File

@@ -63,6 +63,59 @@ bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse) {
return success; 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() { void deleteWiFiAndTokenDetails() {
nvs_handle_t wifiHandle; nvs_handle_t wifiHandle;
if (nvs_open(nvsWiFi, NVS_READWRITE, &wifiHandle) == ESP_OK) { if (nvs_open(nvsWiFi, NVS_READWRITE, &wifiHandle) == ESP_OK) {

View File

@@ -1,6 +1,16 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "calibration.hpp" #include "calibration.hpp"
#include "defines.h" #include "defines.h"
#include "nvs_flash.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() { void Calibration::init() {
nvs_handle_t calibHandle; nvs_handle_t calibHandle;
@@ -102,7 +112,7 @@ bool Calibration::completeCalib(Encoder& topEnc) {
int32_t Calibration::convertToTicks(uint8_t appPos) { int32_t Calibration::convertToTicks(uint8_t appPos) {
// appPos between 0 and 10, convert to target encoder ticks. // appPos between 0 and 10, convert to target encoder ticks.
return (((int32_t)appPos * (UpTicks - DownTicks)) / 10) + DownTicks; return (((int32_t)appPos * (UpTicks - DownTicks)) / 10) + DownTicks + ((UpTicks - DownTicks) / 20);
} }
uint8_t Calibration::convertToAppPos(int32_t ticks) { uint8_t Calibration::convertToAppPos(int32_t ticks) {
@@ -110,3 +120,50 @@ uint8_t Calibration::convertToAppPos(int32_t ticks) {
int8_t retVal = (ticks - DownTicks) * 10 / (UpTicks - DownTicks); int8_t retVal = (ticks - DownTicks) * 10 / (UpTicks - DownTicks);
return (retVal < 0) ? 0 : ((retVal > 10) ? 10 : retVal); 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");
// Socket is now authenticated. Tell the server this device is not
// calibrated so it resets DB state and notifies the app to show the
// pre-calibration screen. The device stays connected, so when the user
// taps Calibrate the subsequent calib_start event will arrive here.
emitCalibStatus(false, 1);
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

@@ -51,8 +51,8 @@ void IRAM_ATTR Encoder::isr_handler(void* arg)
if (calibListen) servoCalibListen(); if (calibListen) servoCalibListen();
if (encoder->feedWDog) { if (encoder->feedWDog) {
esp_timer_stop(encoder->watchdog_handle); esp_timer_stop(encoder->watchdog_handle);
esp_timer_start_once(encoder->watchdog_handle, 500000); esp_timer_start_once(encoder->watchdog_handle, 2000000);
debugLEDTgl(); // debugLEDTgl();
} }
if (encoder->wandListen) servoWandListen(); if (encoder->wandListen) servoWandListen();
if (encoder->serverListen) servoServerListen(); if (encoder->serverListen) servoServerListen();
@@ -63,8 +63,8 @@ void IRAM_ATTR Encoder::isr_handler(void* arg)
if (calibListen) servoCalibListen(); if (calibListen) servoCalibListen();
if (encoder->feedWDog) { if (encoder->feedWDog) {
esp_timer_stop(encoder->watchdog_handle); esp_timer_stop(encoder->watchdog_handle);
esp_timer_start_once(encoder->watchdog_handle, 500000); esp_timer_start_once(encoder->watchdog_handle, 2000000);
debugLEDTgl(); // debugLEDTgl();
} }
if (encoder->wandListen) servoWandListen(); if (encoder->wandListen) servoWandListen();
if (encoder->serverListen) servoServerListen(); if (encoder->serverListen) servoServerListen();
@@ -112,7 +112,7 @@ void Encoder::setupWatchdog() {
ESP_ERROR_CHECK(esp_timer_create(&enc_watchdog_args, &watchdog_handle)); ESP_ERROR_CHECK(esp_timer_create(&enc_watchdog_args, &watchdog_handle));
} }
ESP_ERROR_CHECK(esp_timer_start_once(watchdog_handle, 500000)); ESP_ERROR_CHECK(esp_timer_start_once(watchdog_handle, 2000000));
feedWDog = true; feedWDog = true;
} }

20
src/i2c.c Normal file
View File

@@ -0,0 +1,20 @@
#include "i2c.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include "max17048.h"
// Helper: Initializes I2C controller at 100kHz, returns 0=success
esp_err_t i2c_init() {
// 1. Initialize I2C
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
return (i2c_param_config(I2C_MASTER_NUM, &conf) ||
i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0));
}

View File

@@ -1,2 +1,4 @@
dependencies: dependencies:
bubblesnake/esp_socketio_client: "^1.0.0" bubblesnake/esp_socketio_client: "^1.0.0"
esp-nimble-cpp:
git: https://github.com/h2zero/esp-nimble-cpp.git

View File

@@ -8,16 +8,17 @@
#include "socketIO.hpp" #include "socketIO.hpp"
#include "encoder.hpp" #include "encoder.hpp"
#include "calibration.hpp" #include "calibration.hpp"
#include "esp_pm.h"
#include "mainEventLoop.hpp"
#include "max17048.h"
#include "bms_test.hpp"
#include "servo_test.hpp"
#include "encoder_test.hpp"
// Global encoder instances // Global encoder instances
Encoder* topEnc = new Encoder(ENCODER_PIN_A, ENCODER_PIN_B); Encoder* topEnc = new Encoder(ENCODER_PIN_A, ENCODER_PIN_B);
Encoder* bottomEnc = new Encoder(InputEnc_PIN_A, InputEnc_PIN_B); Encoder* bottomEnc = new Encoder(InputEnc_PIN_A, InputEnc_PIN_B);
// Global encoder pointers (used by servo.cpp)
// Global calibration instance
Calibration calib;
void mainApp() { void mainApp() {
esp_err_t ret = nvs_flash_init(); // change to secure init logic soon!! 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 // 2. If NVS is full or corrupt (common after flashing new code), erase and retry
@@ -27,70 +28,36 @@ void mainApp() {
} }
ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(ret);
bmWiFi.init(); main_event_queue = xQueueCreate(10, sizeof(main_event_type_t));
calib.init();
WiFi::init();
Calibration::init();
// Initialize encoders // Initialize encoders
topEnc->init(); topEnc->init();
bottomEnc->init(); bottomEnc->init();
servoInit(); servoInit();
max17048_init();
printf("beforeSetup\n");
setupAndCalibrate();
printf("afterSetup\n");
setupLoop(); xTaskCreate(wakeTimer, "wakeTimer", 2048, NULL, 5, &wakeTaskHandle);
statusResolved = false; mainEventLoop();
int32_t prevCount = topEnc->getCount();
// Main loop
while (1) {
// websocket disconnect/reconnect handling
if (statusResolved) {
if (!connected) {
printf("Disconnected! Beginning setup loop.\n");
stopSocketIO();
setupLoop();
}
else printf("Reconnected!\n");
statusResolved = false;
}
if (clearCalibFlag) {
calib.clearCalibrated();
emitCalibStatus(false);
clearCalibFlag = false;
}
if (savePosFlag) {
servoSavePos();
savePosFlag = false;
// Send position update to server
uint8_t currentAppPos = calib.convertToAppPos(topEnc->getCount());
emitPosHit(currentAppPos);
printf("Sent pos_hit: position %d\n", currentAppPos);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
} }
void encoderTest() { void pm_init() {
// Create encoder instance esp_pm_config_t pm_config = {
Encoder encoder(ENCODER_PIN_A, ENCODER_PIN_B); .max_freq_mhz = 160, // Max CPU frequency
encoder.init(); .min_freq_mhz = 80, // Min CPU frequency (DFS)
.light_sleep_enable = true // ALLOW CPU to power down during idle
int32_t prevCount = encoder.getCount(); };
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
while (1) {
int32_t currentCount = encoder.getCount();
if (currentCount != prevCount) {
prevCount = currentCount;
printf("Encoder Pos: %d\n", prevCount);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
} }
extern "C" void app_main() { extern "C" void app_main() {
pm_init();
mainApp(); mainApp();
// encoderTest(); // servo_test();
} }

220
src/mainEventLoop.cpp Normal file
View File

@@ -0,0 +1,220 @@
#include "mainEventLoop.hpp"
#include "calibration.hpp"
#include "setup.hpp"
#include "servo.hpp"
#include "bmHTTP.hpp"
#include "cJSON.h"
#include "encoder.hpp"
#include "WiFi.hpp"
#include "socketIO.hpp"
#include "max17048.h"
#include "esp_sleep.h"
#include "defines.h"
// Give C linkage so max17048.c (a C translation unit) can link against these
extern "C" {
TaskHandle_t wakeTaskHandle = NULL;
QueueHandle_t main_event_queue = NULL;
}
// ── Battery helpers ───────────────────────────────────────────────────────────
static const char* battAlertTypeStr(batt_alert_type_t type) {
switch (type) {
case BATT_ALERT_OVERVOLTAGE: return "overvoltage";
case BATT_ALERT_CRITICAL_LOW: return "critical_low";
case BATT_ALERT_LOW_VOLTAGE_WARNING: return "low_voltage_warning";
case BATT_ALERT_SOC_LOW_20: return "low_20";
case BATT_ALERT_SOC_LOW_10: return "low_10";
default: return "unknown";
}
}
static bool postBatteryAlert(batt_alert_type_t type, uint8_t soc) {
cJSON* payload = cJSON_CreateObject();
cJSON_AddStringToObject(payload, "type", battAlertTypeStr(type));
cJSON_AddNumberToObject(payload, "soc", soc);
cJSON* response = nullptr;
bool ok = httpPOST("battery_alert", webToken, payload, response);
cJSON_Delete(payload);
if (response) cJSON_Delete(response);
return ok;
}
static bool postBatterySoc(uint8_t soc) {
cJSON* payload = cJSON_CreateObject();
cJSON_AddNumberToObject(payload, "soc", soc);
cJSON* response = nullptr;
bool ok = httpPOST("battery_update", webToken, payload, response);
cJSON_Delete(payload);
if (response) cJSON_Delete(response);
return ok;
}
// ── Position helpers ──────────────────────────────────────────────────────────
bool postServoPos(uint8_t currentAppPos) {
cJSON* posData = cJSON_CreateObject();
cJSON_AddNumberToObject(posData, "port", 1);
cJSON_AddNumberToObject(posData, "pos", currentAppPos);
cJSON* response = nullptr;
bool success = httpPOST("position", webToken, posData, response);
cJSON_Delete(posData);
if (success && response != nullptr) {
cJSON* awaitCalibItem = cJSON_GetObjectItem(response, "await_calib");
bool awaitCalib = cJSON_IsBool(awaitCalibItem) && awaitCalibItem->valueint != 0;
printf("Position update sent: %d, await_calib=%d\n", currentAppPos, awaitCalib);
cJSON_Delete(response);
if (awaitCalib) {
gpio_set_level(debugLED, 1); // Start with LED off
gpio_hold_en(debugLED);
Calibration::clearCalibrated();
if (!calibrate()) {
if (!WiFi::attemptDHCPrenewal())
setupAndCalibrate();
else if (!calibrate()) {
printf("ERROR: EVEN AFTER DHCP RENEWAL, SOCKET OPENING FAIL\n");
setupAndCalibrate();
}
}
gpio_hold_dis(debugLED);
gpio_set_level(debugLED, 0);
}
}
return success;
}
bool getServoPos() {
cJSON* response = nullptr;
bool success = httpGET("position", webToken, response);
if (success && response != NULL) {
if (cJSON_IsArray(response)) {
int arraySize = cJSON_GetArraySize(response);
if (arraySize > 1) {
printf("Multiple peripherals detected, entering setup.\n");
cJSON_Delete(response);
return false;
} 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: EVEN AFTER DHCP RENEWAL, SOCKET OPENING FAIL\n");
setupAndCalibrate();
}
}
} else {
cJSON* pos = cJSON_GetObjectItem(firstObject, "last_pos");
runToAppPos(pos->valueint);
}
}
cJSON_Delete(response);
}
}
}
}
return success;
}
// ── Wake timer (fires every 60 s) ────────────────────────────────────────────
void wakeTimer(void* pvParameters) {
while (1) {
vTaskDelay(pdMS_TO_TICKS(60000));
if (setupTaskHandle != NULL || socketIOactive
|| uxQueueMessagesWaiting(main_event_queue) > 2) continue;
main_event_type_t evt = EVENT_REQUEST_POS;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
evt = EVENT_REPORT_SOC;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
}
}
// ── Main event loop ───────────────────────────────────────────────────────────
void mainEventLoop() {
main_event_type_t received_event_type;
while (true) {
if (!xQueueReceive(main_event_queue, &received_event_type, portMAX_DELAY)) continue;
if (received_event_type == EVENT_CLEAR_CALIB) {
Calibration::clearCalibrated();
if (!calibrate()) {
if (!WiFi::attemptDHCPrenewal())
setupAndCalibrate();
else if (!calibrate()) {
printf("ERROR: EVEN AFTER DHCP RENEWAL, 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 but still failed to post position\n");
setupAndCalibrate();
}
}
} else if (received_event_type == EVENT_REQUEST_POS) {
if (!getServoPos()) {
printf("Failed to get position\n");
if (!WiFi::attemptDHCPrenewal()) {
setupAndCalibrate();
getServoPos();
} else if (!getServoPos()) {
printf("Renewed DHCP but still failed to get position\n");
setupAndCalibrate();
}
}
} else if (received_event_type == EVENT_BATTERY_CRITICAL) {
// Stop the motor immediately, persist position, notify server, then hibernate.
// esp_deep_sleep_start() with no wakeup source = indefinite sleep until reset.
// The MAX17048 VRESET comparator handles detection of battery recovery.
servoOff();
servoSavePos();
batt_alert_type_t alertType = bms_pending_alert; // snapshot volatile
postBatteryAlert(alertType, established_soc);
printf("CRITICAL BATTERY EVENT (%s, SOC=%d%%). Entering deep sleep.\n",
battAlertTypeStr(alertType), established_soc);
esp_deep_sleep_start();
} else if (received_event_type == EVENT_BATTERY_WARNING) {
postBatteryAlert((batt_alert_type_t)bms_pending_alert, established_soc);
} else if (received_event_type == EVENT_REPORT_SOC) {
postBatterySoc(established_soc);
}
}
vTaskDelete(NULL);
}

175
src/max17048.c Normal file
View File

@@ -0,0 +1,175 @@
#include "max17048.h"
#include "mainEventLoop.hpp"
#include "i2c.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
static const char *TAG = "BATTERY";
uint8_t established_soc = 100;
volatile batt_alert_type_t bms_pending_alert = BATT_ALERT_CRITICAL_LOW;
static TaskHandle_t bms_event_handler = NULL;
void IRAM_ATTR alrt_ISR(void* arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(bms_event_handler, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
esp_err_t max17048_init() {
esp_err_t err = ESP_OK;
err |= i2c_init();
err |= bms_set_alert_bound_voltages(3.3f, 4.2f);
err |= bms_set_reset_voltage(3.25f);
err |= bms_set_alsc();
xTaskCreate(bms_checker_task, "BMS", 4096, NULL, 20, &bms_event_handler);
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << maxALRT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE,
};
gpio_config(&io_conf);
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
gpio_isr_handler_add(maxALRT, alrt_ISR, NULL);
ESP_LOGI(TAG, "MAX17048 initialized, ALRT interrupt on GPIO %d", maxALRT);
err |= bms_clear_status();
err |= bms_clear_alrt();
return err;
}
uint8_t bms_get_soc() {
uint16_t raw_soc;
if (max17048_read_reg(MAX17048_REG_SOC, ((uint8_t*)&raw_soc) + 1, (uint8_t*)&raw_soc) == ESP_OK) {
// upper byte = whole percent; lower byte msb = 0.5%; round to nearest
return (uint8_t)(raw_soc >> 8) + ((raw_soc & 0x80) ? 1 : 0);
}
ESP_LOGE(TAG, "Failed to read SOC register");
return 0;
}
esp_err_t bms_set_alert_bound_voltages(float min, float max) {
uint8_t minVal = (uint8_t)((uint16_t)(min * 1000.0f) / 20);
uint8_t maxVal = (uint8_t)((uint16_t)(max * 1000.0f) / 20);
return max17048_write_reg(MAX17048_REG_VALRT, minVal, maxVal);
}
esp_err_t bms_set_reset_voltage(float vreset) {
uint8_t val = (uint8_t)((uint16_t)(vreset * 1000.0f) / 40);
return max17048_write_reg(MAX17048_REG_VRST_ID, val, 0);
}
void bms_checker_task(void *pvParameters) {
uint8_t prev_soc = 100;
while (true) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
uint8_t status, _;
if (max17048_read_reg(MAX17048_REG_STATUS, &status, &_) != ESP_OK) {
ESP_LOGE(TAG, "STATUS read failed");
bms_clear_alrt();
continue;
}
uint8_t soc = bms_get_soc();
established_soc = soc;
// Clear before posting to queue so ALRT pin is deasserted promptly
bms_clear_status();
bms_clear_alrt();
main_event_type_t evt;
if ((status & HDbit) || ((status & VLbit) && soc < SOC_CRITICAL_VL)) {
// Critical: overvoltage (hardware fault) or battery truly empty
bms_pending_alert = (status & VHbit) ? BATT_ALERT_OVERVOLTAGE : BATT_ALERT_CRITICAL_LOW;
evt = EVENT_BATTERY_CRITICAL;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
} else if (status & VLbit) {
// Undervoltage but SOC still healthy — likely a transient load spike
bms_pending_alert = BATT_ALERT_LOW_VOLTAGE_WARNING;
evt = EVENT_BATTERY_WARNING;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
} else if (status & SCbit) {
// 1% SOC change: check downward threshold crossings for user notifications
if (soc <= SOC_WARN_10 && prev_soc > SOC_WARN_10) {
bms_pending_alert = BATT_ALERT_SOC_LOW_10;
evt = EVENT_BATTERY_WARNING;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
} else if (soc <= SOC_WARN_20 && prev_soc > SOC_WARN_20) {
bms_pending_alert = BATT_ALERT_SOC_LOW_20;
evt = EVENT_BATTERY_WARNING;
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
}
prev_soc = soc;
}
}
}
// Helper: Read 16-bit register to 2-byte array (MSB first big endian)
esp_err_t max17048_read_reg(uint8_t reg_addr, uint8_t *MSB, uint8_t *LSB) {
// this is better than converting to little endian for my application
// since I usually need to handle bytes individually.
uint8_t data[2];
// Write register address
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (MAX17048_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
// Restart and Read 2 bytes
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (MAX17048_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, data, 2, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
*MSB = data[0];
*LSB = data[1];
}
return ret;
}
// Write big endian 2-byte array to a 16-bit register
esp_err_t max17048_write_reg(uint8_t reg_addr, uint8_t MSB, uint8_t LSB) {
// Write register address
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (MAX17048_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write_byte(cmd, MSB, true);
i2c_master_write_byte(cmd, LSB, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
i2c_cmd_link_delete(cmd);
return ret;
}
esp_err_t max17048_friendly_write_reg(uint8_t reg_addr, uint8_t MSB, uint8_t LSB,
uint8_t MSBmask, uint8_t LSBmask) {
uint8_t origMSB, origLSB;
esp_err_t err = max17048_read_reg(reg_addr, &origMSB, &origLSB);
MSB &= MSBmask;
LSB &= LSBmask;
MSB |= origMSB & ~MSBmask;
LSB |= origLSB & ~LSBmask;
return err | max17048_write_reg(reg_addr, MSB, LSB);
}

View File

@@ -1,10 +1,12 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "servo.hpp" #include "servo.hpp"
#include "driver/ledc.h" #include "driver/ledc.h"
#include "defines.h" #include "defines.h"
#include <freertos/FreeRTOS.h>
#include "esp_log.h" #include "esp_log.h"
#include "socketIO.hpp" #include "socketIO.hpp"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "mainEventLoop.hpp"
std::atomic<bool> calibListen{false}; std::atomic<bool> calibListen{false};
std::atomic<int32_t> baseDiff{0}; std::atomic<int32_t> baseDiff{0};
@@ -12,8 +14,6 @@ std::atomic<int32_t> target{0};
std::atomic<bool> runningManual{false}; std::atomic<bool> runningManual{false};
std::atomic<bool> runningServer{false}; std::atomic<bool> runningServer{false};
std::atomic<bool> clearCalibFlag{false};
std::atomic<bool> savePosFlag{false};
std::atomic<bool> startLess{false}; std::atomic<bool> startLess{false};
void servoInit() { void servoInit() {
@@ -23,7 +23,7 @@ void servoInit() {
ledc_timer.timer_num = LEDC_TIMER_0; ledc_timer.timer_num = LEDC_TIMER_0;
ledc_timer.duty_resolution = LEDC_TIMER_16_BIT; ledc_timer.duty_resolution = LEDC_TIMER_16_BIT;
ledc_timer.freq_hz = 50; ledc_timer.freq_hz = 50;
ledc_timer.clk_cfg = LEDC_AUTO_CLK; ledc_timer.clk_cfg = LEDC_USE_RC_FAST_CLK;
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// LEDC channel configuration // LEDC channel configuration
@@ -35,20 +35,24 @@ void servoInit() {
ledc_channel.gpio_num = servoPin; ledc_channel.gpio_num = servoPin;
ledc_channel.duty = offSpeed; // Start off ledc_channel.duty = offSpeed; // Start off
ledc_channel.hpoint = 0; ledc_channel.hpoint = 0;
ledc_channel.sleep_mode = LEDC_SLEEP_MODE_KEEP_ALIVE;
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
gpio_sleep_sel_dis(servoPin);
// Configure servo power switch pin as output // Configure servo power switch pin as output
gpio_reset_pin(servoSwitch);
gpio_set_direction(servoSwitch, GPIO_MODE_OUTPUT); gpio_set_direction(servoSwitch, GPIO_MODE_OUTPUT);
gpio_set_level(servoSwitch, 0); // Start with servo power off gpio_set_level(servoSwitch, 0); // Start with servo power off
// Configure debug LED pin as output // Configure debug LED pin as output
gpio_reset_pin(GPIO_NUM_22); gpio_reset_pin(debugLED);
gpio_set_direction(debugLED, GPIO_MODE_OUTPUT); gpio_set_direction(debugLED, GPIO_MODE_OUTPUT);
gpio_set_level(debugLED, 0); // Start with LED off gpio_set_level(debugLED, 0); // Start with LED off
topEnc->count = servoReadPos(); topEnc->count = servoReadPos();
if (calib.getCalibrated()) initMainLoop(); bottomEnc->count = servoReadPos();
debugLEDSwitch(1); if (Calibration::getCalibrated()) initMainLoop();
// debugLEDSwitch(1);
} }
void servoOn(uint8_t dir, uint8_t manOrServer) { void servoOn(uint8_t dir, uint8_t manOrServer) {
@@ -87,7 +91,7 @@ bool servoInitCalib() {
bottomEnc->wandListen.store(false, std::memory_order_release); bottomEnc->wandListen.store(false, std::memory_order_release);
topEnc->wandListen.store(false, std::memory_order_release); topEnc->wandListen.store(false, std::memory_order_release);
topEnc->serverListen.store(false, std::memory_order_release); topEnc->serverListen.store(false, std::memory_order_release);
if (!calib.clearCalibrated()) return false; if (!Calibration::clearCalibrated()) return false;
if (topEnc == nullptr || bottomEnc == nullptr) { if (topEnc == nullptr || bottomEnc == nullptr) {
printf("ERROR: CALIBRATION STARTED BEFORE SERVO INITIALIZATION\n"); printf("ERROR: CALIBRATION STARTED BEFORE SERVO INITIALIZATION\n");
return false; return false;
@@ -117,7 +121,7 @@ bool servoBeginDownwardCalib() {
calibListen = false; calibListen = false;
servoOff(); servoOff();
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
if (!calib.beginDownwardCalib(*topEnc)) return false; if (!Calibration::beginDownwardCalib(*topEnc)) return false;
baseDiff = bottomEnc->getCount() - topEnc->getCount(); baseDiff = bottomEnc->getCount() - topEnc->getCount();
calibListen = true; calibListen = true;
return true; return true;
@@ -127,7 +131,7 @@ bool servoCompleteCalib() {
calibListen = false; calibListen = false;
servoOff(); servoOff();
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
if (!calib.completeCalib(*topEnc)) return false; if (!Calibration::completeCalib(*topEnc)) return false;
initMainLoop(); initMainLoop();
return true; return true;
} }
@@ -140,8 +144,6 @@ void initMainLoop() {
void IRAM_ATTR watchdogCallback(void* arg) { void IRAM_ATTR watchdogCallback(void* arg) {
if (runningManual || runningServer) { if (runningManual || runningServer) {
// if we're trying to move and our timer ran out, we need to recalibrate
clearCalibFlag = true;
topEnc->pauseWatchdog(); topEnc->pauseWatchdog();
// get ready for recalibration by clearing all these listeners // get ready for recalibration by clearing all these listeners
@@ -149,11 +151,18 @@ void IRAM_ATTR watchdogCallback(void* arg) {
topEnc->wandListen.store(false, std::memory_order_release); topEnc->wandListen.store(false, std::memory_order_release);
topEnc->serverListen.store(false, std::memory_order_release); topEnc->serverListen.store(false, std::memory_order_release);
servoOff(); servoOff();
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);
} }
else { else {
// if no movement is running, we're fine // if no movement is running, we're fine
// save current servo-encoder position for reinitialization // 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 // clear running flags
runningManual = false; runningManual = false;
@@ -203,8 +212,8 @@ void servoWandListen() {
stopServerRun(); stopServerRun();
// freeze atomic values // freeze atomic values
int32_t upBound = calib.UpTicks; int32_t upBound = Calibration::UpTicks;
int32_t downBound = calib.DownTicks; int32_t downBound = Calibration::DownTicks;
int32_t bottomCount = bottomEnc->getCount(); int32_t bottomCount = bottomEnc->getCount();
int32_t topCount = topEnc->getCount(); int32_t topCount = topEnc->getCount();
@@ -224,20 +233,20 @@ void servoWandListen() {
// otherwise, run servo in whichever direction necessary and // otherwise, run servo in whichever direction necessary and
// ensure servo-listener is active. // ensure servo-listener is active.
if (topCount >= (MAX(upBound, downBound) - 1) if (topCount >= (MAX(upBound, downBound) - 1)
&& effDiff > 1) { // TODO: see whether these margins need to be removed. && effDiff > 2) { // TODO: see whether these margins need to be removed.
servoOff(); servoOff();
topEnc->wandListen.store(false, std::memory_order_release); topEnc->wandListen.store(false, std::memory_order_release);
} }
else if (topCount <= (MIN(upBound, downBound) + 1) else if (topCount <= (MIN(upBound, downBound) + 1)
&& effDiff < -1) { && effDiff < -2) {
servoOff(); servoOff();
topEnc->wandListen.store(false, std::memory_order_release); topEnc->wandListen.store(false, std::memory_order_release);
} }
else if (effDiff > 1) { else if (effDiff > 2) {
topEnc->wandListen.store(true, std::memory_order_release); topEnc->wandListen.store(true, std::memory_order_release);
servoOn(CCW, manual); servoOn(CCW, manual);
} }
else if (effDiff < -1) { else if (effDiff < -2) {
topEnc->wandListen.store(true, std::memory_order_release); topEnc->wandListen.store(true, std::memory_order_release);
servoOn(CW, manual); servoOn(CW, manual);
} }
@@ -257,11 +266,15 @@ void servoServerListen() {
void runToAppPos(uint8_t appPos) { void runToAppPos(uint8_t appPos) {
// manual control takes precedence over remote control, always. // manual control takes precedence over remote control, always.
// also do not begin operation if not calibrated; // also do not begin operation if not calibrated;
if (runningManual || !calib.getCalibrated()) return; if (runningManual || !Calibration::getCalibrated()) return;
servoOff(); servoOff();
target = calib.convertToTicks(appPos); // calculate target encoder position if (Calibration::convertToAppPos(topEnc->getCount()) == appPos) {
printf("runToAppPos Called, running to %d from %d", target.load(), topEnc->getCount()); printf("Already at pos: %d, not running\n", appPos);
return;
}
target = Calibration::convertToTicks(appPos); // calculate target encoder position
printf("runToAppPos Called, running to %d from %d\n", target.load(), topEnc->getCount());
// allow servo position to settle // allow servo position to settle
vTaskDelay(pdMS_TO_TICKS(500)); vTaskDelay(pdMS_TO_TICKS(500));

187
src/setup.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include "freertos/FreeRTOS.h"
#include "setup.hpp"
#include "BLE.hpp"
#include "WiFi.hpp"
#include "nvs_flash.h"
#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");
initBLE();
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
setupTaskHandle = NULL;
}
void setupAndCalibrate() {
gpio_reset_pin(debugLED);
gpio_set_direction(debugLED, GPIO_MODE_OUTPUT);
gpio_set_level(debugLED, 1); // Start with LED off
gpio_hold_en(debugLED);
while (1) {
setupLoop();
if (awaitCalibration) {
if (calibrate()) break;
}
else break;
}
gpio_hold_dis(debugLED);
gpio_set_level(debugLED, 0); // Start with LED off
}
void setupLoop() {
setupTaskHandle = xTaskGetCurrentTaskHandle();
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();
continue;
}
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 (!WiFi::attemptConnect(ssid, pw, (wifi_auth_mode_t)authMode)) {
// Make RGB LED certain color (Blue?)
printf("Found credentials, failed to connect.\n");
initialSetup();
continue;
}
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);
cJSON* response = nullptr;
initSuccess = httpGET("position", webToken, response);
if (!initSuccess) {
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");
nvs_close(authHandle);
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();
}
}
setupTaskHandle = NULL; // Clear handle on function exit (safety)
}

View File

@@ -11,9 +11,8 @@
static esp_socketio_client_handle_t io_client; static esp_socketio_client_handle_t io_client;
static esp_socketio_packet_handle_t tx_packet = NULL; static esp_socketio_packet_handle_t tx_packet = NULL;
static bool stopSocketFlag = false;
std::atomic<bool> statusResolved{true}; std::atomic<bool> socketIOactive{false};
std::atomic<bool> connected{false};
// Event handler for Socket.IO events // Event handler for Socket.IO events
static void socketio_event_handler(void *handler_args, esp_event_base_t base, static void socketio_event_handler(void *handler_args, esp_event_base_t base,
@@ -65,8 +64,8 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
} }
// Mark connection as failed // Mark connection as failed
connected = false; if (calibTaskHandle != NULL)
statusResolved = true; xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
} }
// Handle device_init event // Handle device_init event
else if (strcmp(eventName->valuestring, "device_init") == 0) { else if (strcmp(eventName->valuestring, "device_init") == 0) {
@@ -91,25 +90,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. // TODO: UPDATE MOTOR/ENCODER STATES BASED ON THIS, as well as the successive websocket updates.
printf(" Port %d: pos=%d\n", port, lastPos); printf(" Port %d: pos=%d\n", port, lastPos);
if (port != 1) printf("ERROR: NON-1 PORT RECEIVED\n"); 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 // Now mark as connected
connected = true; if (calibTaskHandle != NULL)
statusResolved = true; xTaskNotify(calibTaskHandle, true, eSetValueWithOverwrite);
} else { } else {
printf("Device authentication failed\n"); printf("Device authentication failed\n");
calib.clearCalibrated(); Calibration::clearCalibrated();
deleteWiFiAndTokenDetails(); deleteWiFiAndTokenDetails();
connected = false; if (calibTaskHandle != NULL)
statusResolved = true; xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
} }
} }
} }
@@ -123,10 +115,10 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
printf("Server message: %s\n", message->valuestring); printf("Server message: %s\n", message->valuestring);
} }
} }
calib.clearCalibrated(); Calibration::clearCalibrated();
deleteWiFiAndTokenDetails(); deleteWiFiAndTokenDetails();
connected = false; if (calibTaskHandle != NULL)
statusResolved = true; xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
} }
// Handle calib_start event // Handle calib_start event
@@ -188,13 +180,21 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
} }
else { else {
if (!servoCompleteCalib()) emitCalibError("Completion failed"); if (!servoCompleteCalib()) emitCalibError("Completion failed");
else emitCalibDone(); else {
emitCalibDone();
}
} }
} }
} }
} }
// Handle user_stage1_complete event // 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 cancel_calib event
else if (strcmp(eventName->valuestring, "cancel_calib") == 0) { else if (strcmp(eventName->valuestring, "cancel_calib") == 0) {
printf("Canceling calibration process...\n"); printf("Canceling calibration process...\n");
cJSON *data = cJSON_GetArrayItem(json, 1); cJSON *data = cJSON_GetArrayItem(json, 1);
@@ -211,37 +211,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 +262,8 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
} }
} }
connected = false; if (calibTaskHandle != NULL)
statusResolved = true; xTaskNotify(calibTaskHandle, false, eSetValueWithOverwrite);
break; break;
} }
} }
@@ -302,19 +271,22 @@ static void socketio_event_handler(void *handler_args, esp_event_base_t base,
// Handle WebSocket-level disconnections // Handle WebSocket-level disconnections
if (data->websocket_event_id == WEBSOCKET_EVENT_DISCONNECTED) { if (data->websocket_event_id == WEBSOCKET_EVENT_DISCONNECTED) {
printf("WebSocket disconnected\n"); printf("WebSocket disconnected\n");
connected = false; if (calibTaskHandle != NULL)
statusResolved = true; 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"; const std::string uriString = std::string("ws") + (secureSrv ? "s" : "") + "://" + srvAddr + "/socket.io/?EIO=4&transport=websocket";
void initSocketIO() { void initSocketIO() {
stopSocketFlag = false; // Reset flag for new connection
// Prepare the Authorization Header (Bearer format) // Prepare the Authorization Header (Bearer format)
std::string authHeader = "Authorization: Bearer " + webToken + "\r\n"; std::string authHeader = "Authorization: Bearer " + webToken + "\r\n";
statusResolved = false;
connected = false;
esp_socketio_client_config_t config = {}; esp_socketio_client_config_t config = {};
config.websocket_config.uri = uriString.c_str(); config.websocket_config.uri = uriString.c_str();
config.websocket_config.headers = authHeader.c_str(); config.websocket_config.headers = authHeader.c_str();
@@ -329,6 +301,8 @@ void initSocketIO() {
esp_socketio_register_events(io_client, SOCKETIO_EVENT_ANY, socketio_event_handler, NULL); esp_socketio_register_events(io_client, SOCKETIO_EVENT_ANY, socketio_event_handler, NULL);
esp_socketio_client_start(io_client); esp_socketio_client_start(io_client);
socketIOactive = true;
} }
void stopSocketIO() { void stopSocketIO() {
@@ -338,9 +312,8 @@ void stopSocketIO() {
esp_socketio_client_destroy(io_client); esp_socketio_client_destroy(io_client);
io_client = NULL; io_client = NULL;
tx_packet = NULL; tx_packet = NULL;
connected = false;
statusResolved = false;
} }
socketIOactive = false;
} }
// Helper function to emit Socket.IO event with data // Helper function to emit Socket.IO event with data

112
test/bms_test.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "bms_test.hpp"
#include "i2c.h"
#include "max17048.h"
#include "defines.h"
void bms_test_LED() {
vTaskDelay(pdMS_TO_TICKS(5000));
i2c_init();
gpio_config_t led_conf = {
.pin_bit_mask = (1ULL << debugLED),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&led_conf);
bms_set_alert_bound_voltages(3.8f, 3.9f);
bms_clear_status();
bms_clear_alrt();
while (true) {
uint8_t msb, lsb;
if (max17048_read_reg(MAX17048_REG_STATUS, &msb, &lsb) == ESP_OK) {
bool vh = !!(msb & VHbit);
bool vl = !!(msb & VLbit);
bms_clear_status();
bms_clear_alrt();
if (vl) {
// Under-voltage: LED off
gpio_set_level(debugLED, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
} else if (vh) {
// Over-voltage: rapid blink
gpio_set_level(debugLED, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(debugLED, 0);
vTaskDelay(pdMS_TO_TICKS(100));
} else {
// Normal: LED on
gpio_set_level(debugLED, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
}
} else {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
}
void bms_test() {
vTaskDelay(pdMS_TO_TICKS(5000));
esp_err_t err = i2c_init();
printf("Error after init: %d\n", err);
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << maxALRT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
printf("BMS test running. Polling ALRT pin every 1s...\n");
uint8_t msb, lsb;
if (max17048_read_reg(MAX17048_REG_STATUS, &msb, &lsb) == ESP_OK) {
printf(" STATUS before clear: RI=%d\n", !!(msb & RIbit));
printf(" STATUS reg before clear: RI=%x, %x\n", msb, lsb);
} else {
printf(" STATUS read FAILED\n");
}
err = bms_set_alert_bound_voltages(3.3f, 4.2f);
printf("Error after boundV: %d\n", err);
err |= bms_clear_status();
printf("Error after clearStatus: %d\n", err);
err |= bms_clear_alrt();
printf("Error after clearALRT: %d\n", err);
if (max17048_read_reg(MAX17048_REG_STATUS, &msb, &lsb) == ESP_OK) {
printf(" STATUS after clear: RI=%d\n", !!(msb & RIbit));
printf(" STATUS reg after clear: RI=%x, %x\n", msb, lsb);
} else {
printf(" STATUS read FAILED\n");
}
while (true) {
int level = gpio_get_level(maxALRT);
printf("ALRT pin: %s\n", level ? "HIGH (no alert)" : "LOW (alert asserted)");
if (level == 0) {
if (max17048_read_reg(MAX17048_REG_STATUS, &msb, &lsb) == ESP_OK) {
printf(" STATUS: VH=%d VL=%d HD=%d SC=%d\n",
!!(msb & VHbit),
!!(msb & VLbit),
!!(msb & HDbit),
!!(msb & SCbit));
bms_clear_status();
bms_clear_alrt();
} else {
printf(" STATUS read FAILED\n");
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

7
test/bms_test.hpp Normal file
View File

@@ -0,0 +1,7 @@
#ifndef BMS_TEST_H
#define BMS_TEST_H
void bms_test();
void bms_test_LED();
#endif

104
test/encoder_test.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "encoder_test.hpp"
#include "defines.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "soc/gpio_struct.h"
typedef struct {
uint8_t encoder_id; // 0 = top, 1 = bottom
int8_t direction; // +1 or -1
} enc_event_t;
typedef struct {
gpio_num_t pin_a;
gpio_num_t pin_b;
uint8_t id;
uint8_t last_a;
uint8_t last_b;
int8_t base;
} enc_state_t;
static QueueHandle_t enc_queue;
static enc_state_t enc_states[2];
static bool led_state = false;
static void IRAM_ATTR enc_isr(void* arg) {
enc_state_t* s = (enc_state_t*)arg;
uint32_t levels = GPIO.in.val;
uint8_t a = (levels >> s->pin_a) & 1;
uint8_t b = (levels >> s->pin_b) & 1;
if (a != s->last_a) {
if (!a) s->base += b ? 1 : -1;
else s->base += b ? -1 : 1;
} else if (b != s->last_b) {
if (!b) s->base += a ? -1 : 1;
else s->base += a ? 1 : -1;
}
s->last_a = a;
s->last_b = b;
enc_event_t evt;
evt.encoder_id = s->id;
BaseType_t woken = pdFALSE;
if (s->base >= 4) {
s->base -= 4;
evt.direction = 1;
led_state = !led_state;
gpio_set_level(debugLED, led_state);
xQueueSendFromISR(enc_queue, &evt, &woken);
portYIELD_FROM_ISR(woken);
} else if (s->base <= -4) {
s->base += 4;
evt.direction = -1;
led_state = !led_state;
gpio_set_level(debugLED, led_state);
xQueueSendFromISR(enc_queue, &evt, &woken);
portYIELD_FROM_ISR(woken);
}
}
void encoder_test() {
// LED
gpio_reset_pin(debugLED);
gpio_set_direction(debugLED, GPIO_MODE_OUTPUT);
gpio_set_level(debugLED, 0);
enc_queue = xQueueCreate(32, sizeof(enc_event_t));
// Init encoder states
enc_states[0] = { ENCODER_PIN_A, ENCODER_PIN_B, 0, 0, 0, 0 };
enc_states[1] = { InputEnc_PIN_A, InputEnc_PIN_B, 1, 0, 0, 0 };
// Configure encoder pins
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_ANYEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pin_bit_mask = (1ULL << ENCODER_PIN_A) | (1ULL << ENCODER_PIN_B)
| (1ULL << InputEnc_PIN_A) | (1ULL << InputEnc_PIN_B);
gpio_config(&io_conf);
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
gpio_isr_handler_add(ENCODER_PIN_A, enc_isr, &enc_states[0]);
gpio_isr_handler_add(ENCODER_PIN_B, enc_isr, &enc_states[0]);
gpio_isr_handler_add(InputEnc_PIN_A, enc_isr, &enc_states[1]);
gpio_isr_handler_add(InputEnc_PIN_B, enc_isr, &enc_states[1]);
printf("Encoder test running...\n");
enc_event_t evt;
while (true) {
if (xQueueReceive(enc_queue, &evt, portMAX_DELAY) == pdTRUE) {
printf("[%s] %s\n",
evt.encoder_id == 0 ? "TOP " : "BOTTOM",
evt.direction == 1 ? "CW (+1)" : "CCW (-1)");
}
}
}

6
test/encoder_test.hpp Normal file
View File

@@ -0,0 +1,6 @@
#ifndef ENCODER_TEST_H
#define ENCODER_TEST_H
void encoder_test();
#endif

54
test/servo_test.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include "servo_test.hpp"
#include "defines.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void servo_test() {
// LEDC timer
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_USE_RC_FAST_CLK;
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// LEDC channel
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;
ledc_channel.hpoint = 0;
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
// Servo switch
gpio_reset_pin(servoSwitch);
gpio_set_direction(servoSwitch, GPIO_MODE_OUTPUT);
gpio_set_level(servoSwitch, 0);
// Debug LED
gpio_reset_pin(debugLED);
gpio_set_direction(debugLED, GPIO_MODE_OUTPUT);
gpio_set_level(debugLED, 0);
// Cycle: CW on -> off -> CCW on -> off -> ...
static const uint32_t duties[4] = { cwSpeed, offSpeed, ccwSpeed, offSpeed };
static const bool switches[4] = { true, false, true, false };
int step = 0;
while (true) {
vTaskDelay(pdMS_TO_TICKS(5000));
ledc_set_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel, duties[step]);
ledc_update_duty(LEDC_LOW_SPEED_MODE, servoLEDCChannel);
gpio_set_level(servoSwitch, switches[step] ? 1 : 0);
gpio_set_level(debugLED, switches[step] ? 1 : 0);
step = (step + 1) % 4;
}
}

6
test/servo_test.hpp Normal file
View File

@@ -0,0 +1,6 @@
#ifndef SERVO_TEST_H
#define SERVO_TEST_H
void servo_test();
#endif