Ready for test
This commit is contained in:
Submodule components/esp-nimble-cpp deleted from 25af28bcad
@@ -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
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
// #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
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
#ifndef BM_EVENTS_H
|
#ifndef BM_EVENTS_H
|
||||||
#define BM_EVENTS_H
|
#define BM_EVENTS_H
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
// Event Types
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Shared with max17048.c (C) — only C-compatible types here
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EVENT_CLEAR_CALIB,
|
EVENT_CLEAR_CALIB,
|
||||||
EVENT_SAVE_POS,
|
EVENT_SAVE_POS,
|
||||||
EVENT_REQUEST_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;
|
} main_event_type_t;
|
||||||
|
|
||||||
void mainEventLoop();
|
|
||||||
|
|
||||||
extern QueueHandle_t main_event_queue;
|
extern QueueHandle_t main_event_queue;
|
||||||
|
|
||||||
void wakeTimer(void* pvParameters);
|
|
||||||
|
|
||||||
extern TaskHandle_t wakeTaskHandle;
|
extern TaskHandle_t wakeTaskHandle;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
void mainEventLoop();
|
||||||
|
void wakeTimer(void* pvParameters);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -2,34 +2,64 @@
|
|||||||
#define MAX_17_H
|
#define MAX_17_H
|
||||||
|
|
||||||
#define MAX17048_ADDR 0x36
|
#define MAX17048_ADDR 0x36
|
||||||
#define MAX17048_REG_VCELL 0x02 // Voltage
|
#define MAX17048_REG_VCELL 0x02
|
||||||
#define MAX17048_REG_SOC 0x04 // State of Charge (%)
|
#define MAX17048_REG_SOC 0x04
|
||||||
#define MAX17048_REG_MODE 0x06
|
#define MAX17048_REG_MODE 0x06
|
||||||
#define MAX17048_REG_VERSION 0x08
|
#define MAX17048_REG_VERSION 0x08
|
||||||
#define MAX17048_REG_CONFIG 0x0C
|
#define MAX17048_REG_CONFIG 0x0C
|
||||||
#define MAX17048_REG_CMD 0xFE // Command (Reset)
|
|
||||||
#define MAX17048_REG_STATUS 0x1A
|
|
||||||
#define MAX17048_REG_VALRT 0x14
|
#define MAX17048_REG_VALRT 0x14
|
||||||
#define MAX17048_REG_VRST_ID 0x18
|
#define MAX17048_REG_VRST_ID 0x18
|
||||||
|
#define MAX17048_REG_STATUS 0x1A
|
||||||
|
#define MAX17048_REG_CMD 0xFE
|
||||||
|
|
||||||
// Commands
|
#define MAX17048_CMD_POR 0x5400
|
||||||
#define MAX17048_CMD_POR 0x5400 // Power On Reset command
|
#define MAX17048_CMD_QSTRT 0x4000
|
||||||
#define MAX17048_CMD_QSTRT 0x4000 // Quick Start command
|
|
||||||
|
|
||||||
|
// 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
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "driver/i2c.h"
|
#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();
|
esp_err_t max17048_init();
|
||||||
uint8_t bms_get_soc();
|
uint8_t bms_get_soc();
|
||||||
esp_err_t bms_set_alert_bound_voltages(float min, float max);
|
esp_err_t bms_set_alert_bound_voltages(float min, float max);
|
||||||
esp_err_t bms_set_reset_voltage(float vreset);
|
esp_err_t bms_set_reset_voltage(float vreset);
|
||||||
esp_err_t bms_clear_status();
|
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_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_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)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2568,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
|
||||||
#
|
#
|
||||||
@@ -2590,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
|
||||||
#
|
#
|
||||||
@@ -2807,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
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ void MyServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInf
|
|||||||
void MyServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
|
void MyServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
|
||||||
isBLEClientConnected = false;
|
isBLEClientConnected = false;
|
||||||
printf("Client disconnected - reason: %d\n", reason);
|
printf("Client disconnected - reason: %d\n", reason);
|
||||||
reset();
|
if (!finalAuth) reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyCharCallbacks::onRead(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) {
|
void MyCharCallbacks::onRead(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
// 3. Post Event to System Loop
|
// // 3. Post Event to System Loop
|
||||||
battery_data_t data = { .soc = soc, .voltage = voltage };
|
// battery_data_t data = { .soc = soc, .voltage = voltage };
|
||||||
|
|
||||||
esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_UPDATE, &data, sizeof(data), 0);
|
// esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_UPDATE, &data, sizeof(data), 0);
|
||||||
|
|
||||||
// Optional: Post warnings
|
// // Optional: Post warnings
|
||||||
if (soc < 20.0) {
|
// if (soc < 20.0) {
|
||||||
esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_LOW, NULL, 0, 0);
|
// esp_event_post(BATTERY_EVENTS, BATTERY_EVENT_LOW, NULL, 0, 0);
|
||||||
}
|
// }
|
||||||
@@ -66,7 +66,7 @@ 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,
|
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 MSBmask, uint8_t LSBmask) {
|
||||||
uint8_t origMSB, origLSB;
|
uint8_t origMSB, origLSB;
|
||||||
esp_err_t err = max17048_read_reg(reg_addr, origMSB, origLSB);
|
esp_err_t err = max17048_read_reg(reg_addr, &origMSB, &origLSB);
|
||||||
MSB &= MSBmask;
|
MSB &= MSBmask;
|
||||||
LSB &= LSBmask;
|
LSB &= LSBmask;
|
||||||
MSB |= origMSB & ~MSBmask;
|
MSB |= origMSB & ~MSBmask;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "calibration.hpp"
|
#include "calibration.hpp"
|
||||||
#include "esp_pm.h"
|
#include "esp_pm.h"
|
||||||
#include "mainEventLoop.hpp"
|
#include "mainEventLoop.hpp"
|
||||||
|
#include "max17048.h"
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -24,6 +25,8 @@ void mainApp() {
|
|||||||
}
|
}
|
||||||
ESP_ERROR_CHECK(ret);
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
|
main_event_queue = xQueueCreate(10, sizeof(main_event_type_t));
|
||||||
|
|
||||||
WiFi::init();
|
WiFi::init();
|
||||||
Calibration::init();
|
Calibration::init();
|
||||||
|
|
||||||
@@ -31,6 +34,7 @@ void mainApp() {
|
|||||||
topEnc->init();
|
topEnc->init();
|
||||||
bottomEnc->init();
|
bottomEnc->init();
|
||||||
servoInit();
|
servoInit();
|
||||||
|
max17048_init();
|
||||||
|
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
|
|
||||||
|
|||||||
@@ -7,39 +7,63 @@
|
|||||||
#include "encoder.hpp"
|
#include "encoder.hpp"
|
||||||
#include "WiFi.hpp"
|
#include "WiFi.hpp"
|
||||||
#include "socketIO.hpp"
|
#include "socketIO.hpp"
|
||||||
|
#include "max17048.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
|
||||||
|
// Give C linkage so max17048.c (a C translation unit) can link against these
|
||||||
|
extern "C" {
|
||||||
TaskHandle_t wakeTaskHandle = NULL;
|
TaskHandle_t wakeTaskHandle = NULL;
|
||||||
|
QueueHandle_t main_event_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void wakeTimer(void* pvParameters) {
|
// ── Battery helpers ───────────────────────────────────────────────────────────
|
||||||
while (1) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(60000));
|
static const char* battAlertTypeStr(batt_alert_type_t type) {
|
||||||
// avoid accumulating events during re-setup or calibration
|
switch (type) {
|
||||||
if (setupTaskHandle != NULL || socketIOactive
|
case BATT_ALERT_OVERVOLTAGE: return "overvoltage";
|
||||||
|| uxQueueMessagesWaiting(main_event_queue) > 2) continue;
|
case BATT_ALERT_CRITICAL_LOW: return "critical_low";
|
||||||
main_event_type_t evt = EVENT_REQUEST_POS;
|
case BATT_ALERT_LOW_VOLTAGE_WARNING: return "low_voltage_warning";
|
||||||
xQueueSend(main_event_queue, &evt, portMAX_DELAY);
|
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) {
|
bool postServoPos(uint8_t currentAppPos) {
|
||||||
// Create POST data
|
|
||||||
cJSON* posData = cJSON_CreateObject();
|
cJSON* posData = cJSON_CreateObject();
|
||||||
cJSON_AddNumberToObject(posData, "port", 1);
|
cJSON_AddNumberToObject(posData, "port", 1);
|
||||||
cJSON_AddNumberToObject(posData, "pos", currentAppPos);
|
cJSON_AddNumberToObject(posData, "pos", currentAppPos);
|
||||||
|
|
||||||
// Send position update
|
|
||||||
cJSON* response = nullptr;
|
cJSON* response = nullptr;
|
||||||
bool success = httpPOST("position", webToken, posData, response);
|
bool success = httpPOST("position", webToken, posData, response);
|
||||||
cJSON_Delete(posData);
|
cJSON_Delete(posData);
|
||||||
|
|
||||||
if (success && response != nullptr) {
|
if (success && response != nullptr) {
|
||||||
// Parse await_calib from response
|
|
||||||
cJSON* awaitCalibItem = cJSON_GetObjectItem(response, "await_calib");
|
cJSON* awaitCalibItem = cJSON_GetObjectItem(response, "await_calib");
|
||||||
bool awaitCalib = false;
|
bool awaitCalib = cJSON_IsBool(awaitCalibItem) && awaitCalibItem->valueint != 0;
|
||||||
if (cJSON_IsBool(awaitCalibItem)) {
|
|
||||||
awaitCalib = awaitCalibItem->valueint != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Position update sent: %d, await_calib=%d\n", currentAppPos, awaitCalib);
|
printf("Position update sent: %d, await_calib=%d\n", currentAppPos, awaitCalib);
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
|
|
||||||
@@ -48,15 +72,13 @@ bool postServoPos(uint8_t currentAppPos) {
|
|||||||
if (!calibrate()) {
|
if (!calibrate()) {
|
||||||
if (!WiFi::attemptDHCPrenewal())
|
if (!WiFi::attemptDHCPrenewal())
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
else {
|
else if (!calibrate()) {
|
||||||
if (!calibrate()) {
|
printf("ERROR: EVEN AFTER DHCP RENEWAL, SOCKET OPENING FAIL\n");
|
||||||
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
|
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +87,14 @@ bool getServoPos() {
|
|||||||
bool success = httpGET("position", webToken, response);
|
bool success = httpGET("position", webToken, response);
|
||||||
|
|
||||||
if (success && response != NULL) {
|
if (success && response != NULL) {
|
||||||
// Check if response is an array
|
|
||||||
if (cJSON_IsArray(response)) {
|
if (cJSON_IsArray(response)) {
|
||||||
int arraySize = cJSON_GetArraySize(response);
|
int arraySize = cJSON_GetArraySize(response);
|
||||||
|
|
||||||
// Condition 1: More than one object in array
|
|
||||||
if (arraySize > 1) {
|
if (arraySize > 1) {
|
||||||
printf("Multiple peripherals detected, entering setup.\n");
|
printf("Multiple peripherals detected, entering setup.\n");
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (arraySize > 0) {
|
||||||
// Condition 2: Check peripheral_number in first object
|
|
||||||
else if (arraySize > 0) {
|
|
||||||
cJSON* firstObject = cJSON_GetArrayItem(response, 0);
|
cJSON* firstObject = cJSON_GetArrayItem(response, 0);
|
||||||
if (firstObject != NULL) {
|
if (firstObject != NULL) {
|
||||||
cJSON* peripheralNum = cJSON_GetObjectItem(firstObject, "peripheral_number");
|
cJSON* peripheralNum = cJSON_GetObjectItem(firstObject, "peripheral_number");
|
||||||
@@ -94,15 +112,12 @@ bool getServoPos() {
|
|||||||
if (!calibrate()) {
|
if (!calibrate()) {
|
||||||
if (!WiFi::attemptDHCPrenewal())
|
if (!WiFi::attemptDHCPrenewal())
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
else {
|
else if (!calibrate()) {
|
||||||
if (!calibrate()) {
|
printf("ERROR: EVEN AFTER DHCP RENEWAL, SOCKET OPENING FAIL\n");
|
||||||
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
|
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
cJSON* pos = cJSON_GetObjectItem(firstObject, "last_pos");
|
cJSON* pos = cJSON_GetObjectItem(firstObject, "last_pos");
|
||||||
runToAppPos(pos->valueint);
|
runToAppPos(pos->valueint);
|
||||||
}
|
}
|
||||||
@@ -115,62 +130,86 @@ bool getServoPos() {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueHandle_t main_event_queue = NULL;
|
// ── 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() {
|
void mainEventLoop() {
|
||||||
main_event_queue = xQueueCreate(10, sizeof(main_event_type_t));
|
|
||||||
main_event_type_t received_event_type;
|
main_event_type_t received_event_type;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (xQueueReceive(main_event_queue, &received_event_type, portMAX_DELAY)) {
|
if (!xQueueReceive(main_event_queue, &received_event_type, portMAX_DELAY)) continue;
|
||||||
|
|
||||||
if (received_event_type == EVENT_CLEAR_CALIB) {
|
if (received_event_type == EVENT_CLEAR_CALIB) {
|
||||||
Calibration::clearCalibrated();
|
Calibration::clearCalibrated();
|
||||||
if (!calibrate()) {
|
if (!calibrate()) {
|
||||||
if (!WiFi::attemptDHCPrenewal())
|
if (!WiFi::attemptDHCPrenewal())
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
else {
|
else if (!calibrate()) {
|
||||||
if (!calibrate()) {
|
printf("ERROR: EVEN AFTER DHCP RENEWAL, SOCKET OPENING FAIL\n");
|
||||||
printf("ERROR OCCURED: EVEN AFTER SETUP, SOCKET OPENING FAIL\n");
|
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
} else if (received_event_type == EVENT_SAVE_POS) {
|
||||||
else if (received_event_type == EVENT_SAVE_POS) {
|
|
||||||
servoSavePos();
|
servoSavePos();
|
||||||
|
|
||||||
uint8_t currentAppPos = Calibration::convertToAppPos(topEnc->getCount());
|
uint8_t currentAppPos = Calibration::convertToAppPos(topEnc->getCount());
|
||||||
|
|
||||||
if (!postServoPos(currentAppPos)) {
|
if (!postServoPos(currentAppPos)) {
|
||||||
printf("Failed to send position update\n");
|
printf("Failed to send position update\n");
|
||||||
if (!WiFi::attemptDHCPrenewal()) {
|
if (!WiFi::attemptDHCPrenewal()) {
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
postServoPos(currentAppPos);
|
postServoPos(currentAppPos);
|
||||||
}
|
} else if (!postServoPos(currentAppPos)) {
|
||||||
else {
|
printf("Renewed DHCP but still failed to post position\n");
|
||||||
if (!postServoPos(currentAppPos)) {
|
|
||||||
printf("renewed dhcp successfully, but still failed to post\n");
|
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
} else if (received_event_type == EVENT_REQUEST_POS) {
|
||||||
else if (received_event_type == EVENT_REQUEST_POS) {
|
|
||||||
if (!getServoPos()) {
|
if (!getServoPos()) {
|
||||||
printf("Failed to send position update\n");
|
printf("Failed to get position\n");
|
||||||
if (!WiFi::attemptDHCPrenewal()) {
|
if (!WiFi::attemptDHCPrenewal()) {
|
||||||
setupAndCalibrate();
|
setupAndCalibrate();
|
||||||
getServoPos();
|
getServoPos();
|
||||||
}
|
} else if (!getServoPos()) {
|
||||||
else {
|
printf("Renewed DHCP but still failed to get position\n");
|
||||||
if (!getServoPos()) {
|
|
||||||
printf("renewed dhcp successfully, but still failed to post\n");
|
|
||||||
setupAndCalibrate();
|
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);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
116
src/max17048.c
116
src/max17048.c
@@ -1,44 +1,120 @@
|
|||||||
#include "max17048.h"
|
#include "max17048.h"
|
||||||
#include "esp_err.h"
|
#include "mainEventLoop.hpp"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "esp_timer.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";
|
static const char *TAG = "BATTERY";
|
||||||
uint8_t established_soc;
|
|
||||||
|
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 max17048_init() {
|
||||||
esp_err_t err = ESP_OK;
|
esp_err_t err = ESP_OK;
|
||||||
uint8_t status, _;
|
|
||||||
err |= i2c_init();
|
err |= i2c_init();
|
||||||
err |= max17048_read_reg(MAX17048_REG_STATUS, &status, &_);
|
err |= bms_set_alert_bound_voltages(3.3f, 4.2f);
|
||||||
err |= bms_set_alert_bound_voltages(3.3, 4.2);
|
err |= bms_set_reset_voltage(3.25f);
|
||||||
err |= bms_set_reset_voltage(3.25);
|
|
||||||
err |= bms_set_alsc();
|
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_status();
|
||||||
|
err |= bms_clear_alrt();
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Function reading MAX17048 SOC register, returning battery percentage
|
|
||||||
uint8_t bms_get_soc() {
|
uint8_t bms_get_soc() {
|
||||||
// uint16_t raw_soc, raw_vcell;
|
|
||||||
uint16_t raw_soc;
|
uint16_t raw_soc;
|
||||||
|
|
||||||
// Read SOC (Register 0x04)
|
|
||||||
if (max17048_read_reg(MAX17048_REG_SOC, ((uint8_t*)&raw_soc) + 1, (uint8_t*)&raw_soc) == ESP_OK) {
|
if (max17048_read_reg(MAX17048_REG_SOC, ((uint8_t*)&raw_soc) + 1, (uint8_t*)&raw_soc) == ESP_OK) {
|
||||||
return (raw_soc >> 8) + raw_soc & 0x80; // round to the nearest percent
|
// upper byte = whole percent; lower byte msb = 0.5%; round to nearest
|
||||||
} else {
|
return (uint8_t)(raw_soc >> 8) + ((raw_soc & 0x80) ? 1 : 0);
|
||||||
ESP_LOGE(TAG, "Failed to read MAX17048");
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
ESP_LOGE(TAG, "Failed to read SOC register");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t bms_set_alert_bound_voltages(float min, float max) {
|
esp_err_t bms_set_alert_bound_voltages(float min, float max) {
|
||||||
uint8_t minVal = (uint16_t)((float)min * 1000.0) / 20;
|
uint8_t minVal = (uint8_t)((uint16_t)(min * 1000.0f) / 20);
|
||||||
uint8_t maxVal = (uint16_t)((float)max * 1000.0) / 20;
|
uint8_t maxVal = (uint8_t)((uint16_t)(max * 1000.0f) / 20);
|
||||||
return max17048_write_reg(MAX17048_REG_VALRT, minVal, maxVal);
|
return max17048_write_reg(MAX17048_REG_VALRT, minVal, maxVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t bms_set_reset_voltage(float vreset) {
|
esp_err_t bms_set_reset_voltage(float vreset) {
|
||||||
uint8_t maxVal = (uint16_t)((float)vreset * 1000.0) / 40;
|
uint8_t val = (uint8_t)((uint16_t)(vreset * 1000.0f) / 40);
|
||||||
max17048_write_reg(MAX17048_REG_VRST_ID, vreset, 0);
|
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 & VHbit) || (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,12 @@ void servoInit() {
|
|||||||
gpio_sleep_sel_dis(servoPin);
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ void setupLoop() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("Token read unsuccessful, entering setup.\n");
|
printf("Token read unsuccessful, entering setup.\n");
|
||||||
|
nvs_close(authHandle);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user