Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 100 additions & 35 deletions tinyGS/src/ConfigManager/ConfigManager.cpp

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions tinyGS/src/ConfigManager/ConfigManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,21 @@ struct board_t
uint8_t L_MISO;
uint8_t L_MOSI;
uint8_t L_SCK;
float L_TCXO_V;
float L_TCXO_V;
uint8_t RX_EN;
uint8_t TX_EN;
String BOARD;
uint8_t ADC_CTL; // GPIO pin to enable ADC reading
uint8_t VBAT_AIN; // GPIO pin for VBAT monitoring
float VBAT_SCALE; // potential divider between battery and GPIO pin
String BOARD;

board_t() = default;
board_t(uint8_t oled_addr, uint8_t oled_sda, uint8_t oled_scl, uint8_t oled_rst, uint8_t prog_btn, uint8_t board_led,
uint8_t l_radio, uint8_t l_nss, uint8_t l_di00, uint8_t l_di01, uint8_t l_bussy, uint8_t l_rst, uint8_t l_miso, uint8_t l_mosi, uint8_t l_sck,
float l_tcxo_v, uint8_t rx_en, uint8_t tx_en, String board_name)
float l_tcxo_v, uint8_t rx_en, uint8_t tx_en, uint8_t adc_ctl, uint8_t vbat_ain, float vbat_scale, String board_name)
: OLED__address(oled_addr), OLED__SDA(oled_sda), OLED__SCL(oled_scl), OLED__RST(oled_rst), PROG__BUTTON(prog_btn), BOARD_LED(board_led),
L_radio(l_radio), L_NSS(l_nss), L_DI00(l_di00), L_DI01(l_di01), L_BUSSY(l_bussy), L_RST(l_rst), L_MISO(l_miso), L_MOSI(l_mosi), L_SCK(l_sck),
L_TCXO_V(l_tcxo_v), RX_EN(rx_en), TX_EN(tx_en), BOARD(board_name) {}
L_TCXO_V(l_tcxo_v), RX_EN(rx_en), TX_EN(tx_en), ADC_CTL(adc_ctl), VBAT_AIN(vbat_ain), VBAT_SCALE(vbat_scale), BOARD(board_name) {}
};

const uint8_t UNUSED = -1;
Expand Down Expand Up @@ -306,6 +309,7 @@ class ConfigManager : public IotWebConf2
void parseAdvancedConf();
void parseModemStartup();
bool parseBoardTemplate(board_t &);
String boardTemplateToJSON(board_t &board);

std::function<boolean(iotwebconf2::WebRequestWrapper *)> formValidatorStd;
DNSServer dnsServer;
Expand Down
6 changes: 3 additions & 3 deletions tinyGS/src/ConfigManager/html.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const char BOARD_VALUES[][BOARD_LENGTH] PROGMEM = {"0", "1" };
const char BOARD_VALUES[][BOARD_LENGTH] PROGMEM = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" , "21", "22"};
#endif

const char IOTWEBCONF_DASHBOARD_STYLE_INNER[] PROGMEM = "table{margin:20px auto;}h3{text-align:center;}.card{height:12em;margin:10px;text-align:left;font-family:Arial;border:3px groove;border-radius:0.3rem;display:inline-block;padding:10px;min-width:260px;}td{padding:0 10px;}textarea{resize:vertical;width:100%;margin:0;height:318px;padding:5px;overflow:auto;}#c1{width:98%;padding:5px;}#t1{width:98%}.console{display:inline-block;text-align:center;margin:10px 0;width:98%;max-width:1080px;}.G{color:green;}.R{color:red}";
const char IOTWEBCONF_DASHBOARD_STYLE_INNER[] PROGMEM = "table{margin:20px auto;}h3{text-align:center;}.card{height:13em;margin:10px;text-align:left;font-family:Arial;border:3px groove;border-radius:0.3rem;display:inline-block;padding:10px;min-width:260px;}td{padding:0 10px;}textarea{resize:vertical;width:100%;margin:0;height:318px;padding:5px;overflow:auto;}#c1{width:98%;padding:5px;}#t1{width:98%}.console{display:inline-block;text-align:center;margin:10px 0;width:98%;max-width:1080px;}.G{color:green;}.R{color:red}";
const char IOTWEBCONF_DASHBOARD_BODY_INNER[] PROGMEM = "<div style='text-align:center;min-width:260px;'>\n";
const char IOTWEBCONF_CONSOLE_BODY_INNER[] PROGMEM = "<br /><div class='console'><textarea readonly='' id='t1' wrap='off' name='t1'></textarea><form method='get' onsubmit='return f(1);'><input id='c1' placeholder='Enter command' autofocus='' name='c1'><br></form></div>\n";
const char IOTWEBCONF_CONSOLE_SCRIPT[] PROGMEM = "var x=null,lt,to,tp,pc='';var sn=0,id=0;function f(p){var c,o='',t;clearTimeout(lt);t = document.getElementById('t1');if (p==1) {c =document.getElementById('c1');o='&c1='+encodeURIComponent(c.value);c.value='';t.scrollTop=99999;sn=t.scrollTop;}if (t.scrollTop >= sn){if (x!=null){x.abort();}x=new XMLHttpRequest();x.onreadystatechange=function() {if(x.readyState==4&&x.status==200){var z,d;var a=x.responseText;console.log(a);id=a.substr(0,a.indexOf('\\n'));z=a.substr(a.indexOf('\\n')+1);if(z.length>0){t.value+=z;}t.scrollTop=99999;sn=t.scrollTop;}};x.open('GET','cs?c2='+id+o,true);x.send();}lt=setTimeout(f,2345);return false;}window.addEventListener('load', f);";
Expand All @@ -82,5 +82,5 @@ const char ADVANCED_CONFIG_SCRIPT[] PROGMEM =
"function tableDoneHandler(btn){var tbd=document.getElementById('current-table'); var ds=tableDictString(tbd); current_ctrl.value=ds; document.getElementById('dt-' + current_id).remove(); current_ctrl=null; current_id=null; }"
"function editElementDict(ed){if (current_ctrl===null){var ph=ed.getAttribute('placeholder'); var dstring = ed.value!='' ? ed.value : ph; if(dstring !== ''){ current_id=ed.id; var dict = JSON.parse(dstring); var tblhtml = '<div id=""dt-' + current_id + '"">' + dictTable(dict) + '<input type=""button"" value=""Done ' + current_id + '"" onclick=""tableDoneHandler()"" style=""height:35px;width:100px;background-color:lightblue;""></div>'; ed.insertAdjacentHTML('afterend', tblhtml); current_ctrl=ed; } } }"
"var current_id, current_ctrl=null; window.addEventListener('load', function() {setup_click('board_template'); setup_click('modem_startup');});";
const char IOTWEBCONF_WORLDMAP_SCRIPT[] PROGMEM ="var wmx=null,wmt;function wmf(p){var sp,mc,gs,lp;clearTimeout(wmt);wmx=new XMLHttpRequest();wmx.onreadystatechange=function() {if(wmx.readyState==4&&x.status==200){var wma=wmx.responseText;var wmp = wma.split(',');sp=document.getElementById('wmsatpos');sp.setAttribute('cx', wmp[0]);sp.setAttribute('cy', wmp[1]);mc=document.getElementById('modemconfig');for(let r=0;r<6;r++){mc.rows[r].cells[1].innerHTML=wmp[r+2]};if(wmp[2]=='LoRa'){mc.rows[3].cells[0].innerHTML='Spreading Factor ';mc.rows[4].cells[0].innerHTML='Coding Rate ';}else{mc.rows[3].cells[0].innerHTML='Bitrate ';mc.rows[4].cells[0].innerHTML='Frequency dev ';};gs=document.getElementById('gsstatus');for(let r=0;r<6;r++){gs.rows[r].cells[1].innerHTML=wmp[r+8];};sd=document.getElementById('satdata');for(let r=0;r<6;r++){sd.rows[r].cells[1].innerHTML=wmp[r+14];};lp=document.getElementById('lastpacket');for(let r=0;r<4;r++){lp.rows[r].cells[1].innerHTML=wmp[r+20];};lp.rows[4].cells[0].innerHTML=wmp[24];}};wmx.open('GET','wm',true);wmx.send();wmt=setTimeout(wmf,5000);return false;}window.addEventListener('load', wmf);";
const char IOTWEBCONF_CONFIG_STYLE_INNER[] PROGMEM = " fieldset[id='Board config'] div:nth-of-type(3) ~ div { display:none}";
const char IOTWEBCONF_WORLDMAP_SCRIPT[] PROGMEM ="var wmx=null,wmt;function wmf(p){var sp,mc,gs,lp;clearTimeout(wmt);wmx=new XMLHttpRequest();wmx.onreadystatechange=function() {if(wmx.readyState==4&&x.status==200){var wma=wmx.responseText;var wmp = wma.split(',');sp=document.getElementById('wmsatpos');sp.setAttribute('cx', wmp[0]);sp.setAttribute('cy', wmp[1]);mc=document.getElementById('modemconfig');for(let r=0;r<6;r++){mc.rows[r].cells[1].innerHTML=wmp[r+2]};if(wmp[2]=='LoRa'){mc.rows[3].cells[0].innerHTML='Spreading Factor ';mc.rows[4].cells[0].innerHTML='Coding Rate ';}else{mc.rows[3].cells[0].innerHTML='Bitrate ';mc.rows[4].cells[0].innerHTML='Frequency dev ';};gs=document.getElementById('gsstatus');for(let r=0;r<7;r++){gs.rows[r].cells[1].innerHTML=wmp[r+8];};sd=document.getElementById('satdata');for(let r=0;r<6;r++){sd.rows[r].cells[1].innerHTML=wmp[r+15];};lp=document.getElementById('lastpacket');for(let r=0;r<4;r++){lp.rows[r].cells[1].innerHTML=wmp[r+21];};lp.rows[4].cells[0].innerHTML=wmp[25];}};wmx.open('GET','wm',true);wmx.send();wmt=setTimeout(wmf,5000);return false;}window.addEventListener('load', wmf);";
const char IOTWEBCONF_CONFIG_STYLE_INNER[] PROGMEM = " fieldset[id='Board config'] div:nth-of-type(3) ~ div { display:none}";
10 changes: 10 additions & 0 deletions tinyGS/src/Display/Display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "../ConfigManager/ConfigManager.h"
#include "../Mqtt/MQTT_credentials.h"
#include "../Logger/Logger.h"
#include "../Power/Battery.h"

SSD1306* display;
OLEDDisplayUi* ui = NULL;
Expand Down Expand Up @@ -232,6 +233,15 @@ void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int1
snprintf(displayBuffer, sizeof(displayBuffer), "%.1fkbps", status.modeminfo.bitrate);
display->drawString(128 + x, 34 + y, displayBuffer);
}
if (status.vbat != 0.0)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
if(getBatteryPercentage() > 100.0f) {
display->drawString(x, 45 + y, "Pow: USB " + String(status.vbat) + "V");
} else {
display->drawString(x, 45 + y, "Pow: Bat " + String(getBatteryPercentage(), 0) + "% " + String(status.vbat) + "V");
}
}
}

void drawFrame4(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y)
Expand Down
38 changes: 2 additions & 36 deletions tinyGS/src/Mqtt/MQTT_Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void MQTT_Client::loop()
else
{
StaticJsonDocument<192> doc;
doc["Vbat"] = voltage();
doc["Vbat"] = status.vbat;
doc["Mem"] = ESP.getFreeHeap();
doc["MinMem"] = ESP.getMinFreeHeap(); // Mínimo histórico
doc["MaxBlk"] = ESP.getMaxAllocHeap(); // Bloque más grande disponible
Expand Down Expand Up @@ -249,7 +249,7 @@ void MQTT_Client::sendWelcome()
doc["board"] = configManager.getBoard();
doc["mac"] = clientId;
doc["seconds"] = millis()/1000;
doc["Vbat"] = voltage();
doc["Vbat"] = status.vbat;
doc["chip"] = ESP.getChipModel();
doc["slot"] = esp_ota_get_running_partition ()->label;
doc["pSize"] = esp_ota_get_running_partition ()->size;
Expand Down Expand Up @@ -1170,37 +1170,3 @@ void MQTT_Client::begin()
setServer(configManager.getMqttServer(), configManager.getMqttPort());
setCallback(manageMQTTDataCallback);
}



int MQTT_Client::voltage() {
int medianVoltage;
int length = 21;
int voltages[22];

for (int i = 0; i < 21; i++)
{
voltages[i] = analogRead(36);
}

// BubbleSortAsc from https://www.luisllamas.es/arduino-bubble-sort/
int i, j, flag = 1;
int temp;
for (i = 1; (i <= length) && flag; i++)
{
flag = 0;
for (j = 0; j < (length - 1); j++)
{
if (voltages[j + 1] < voltages[j])
{
temp = voltages[j];
voltages[j] = voltages[j + 1];
voltages[j + 1] = temp;
flag = 1;
}
}
}
medianVoltage = voltages[10];
return medianVoltage;
}

126 changes: 126 additions & 0 deletions tinyGS/src/Power/Battery.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Battery.cpp
// Battery monitoring helpers for tinyGS

#include <stdlib.h>
#include "Battery.h"
#include "../ConfigManager/ConfigManager.h"
#include "../Logger/Logger.h"
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>

adc_cali_handle_t cali_handle = NULL;
#define DEFAULT_VREF 1100


// Arduino Framework Battery Writeup
// https://digitalconcepts.net.au/arduino/index.php?op=Battery

// Initialize ADC pins and ADC configuration for the board's VBAT input.
// If the board provides an `ADC_CTL` pin (to enable/disable the voltage
// divider), that pin is configured as an output. If the `VBAT_AIN` analog
// input is available, attach it to the ADC and set resolution/attenuation
// appropriate for battery voltage measurement.
void initBatteryMonitoring(void)
{
board_t board;
if (ConfigManager::getInstance().getBoardConfig(board)) {
// If ADC_CTL is defined, set it up as the enable pin for ADC reading
if (board.ADC_CTL != UNUSED) {
pinMode(board.ADC_CTL, OUTPUT); // control pin for ADC/voltage divider
}
if (board.VBAT_AIN != UNUSED) {
// Configure the analog input used for VBAT monitoring
pinMode(board.VBAT_AIN, INPUT);
// Use 12-bit resolution and 11dB attenuation for ~0-3.6V range
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
// Characterize ADC for voltage conversion
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};

adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle);
}
checkBattery(); // Initial read to seed status.vbat
}
}

// Drive the ADC control pin to the enabled state (if present) and wait
// briefly for voltages to stabilize before taking a reading.
inline void enableADCReading(board_t board)
{
if (board.ADC_CTL != UNUSED) {
digitalWrite(board.ADC_CTL, ADC_READ_ENABLE);
delay(50); // Allow voltage to stabilize before sampling
}
}

// Disable the ADC control pin to remove standby current from the
// voltage divider (if the board uses one). No delay is necessary here.
inline void disableADCReading(board_t board)
{
if (board.ADC_CTL != UNUSED) {
digitalWrite(board.ADC_CTL, ADC_READ_DISABLE);
}
}

// Periodically read the VBAT analog input and update the global
// `status.vbat` value. Reads are throttled by `BATTERY_CHECK_INTERVAL`.
// The raw ADC value is scaled by `board.VBAT_SCALE` (provided by the
// board configuration) to convert it into volts.
void checkBattery(void)
{
static unsigned long lastReadTime = 0;
board_t board;

// Throttle the battery check interval
if (millis() - lastReadTime > BATTERY_CHECK_INTERVAL) {
lastReadTime = millis();
if (ConfigManager::getInstance().getBoardConfig(board)) {
if (board.VBAT_AIN != UNUSED && board.VBAT_SCALE != UNUSED) {
int voltage = 0;

enableADCReading(board);

// Read raw ADC
int raw = analogRead(board.VBAT_AIN);

disableADCReading(board);

//Convert adc_reading to voltage in mV
adc_cali_raw_to_voltage(cali_handle, raw, &voltage);

// Convert and smooth: seed or simple average with previous value
float measured = (board.VBAT_SCALE * voltage) / 1000.0f; // convert mV to V
Log::debug(PSTR("adc raw: %d, voltage: %f V"), raw, measured);
if (status.vbat == 0.0) {
status.vbat = measured; // initial seed
} else {
status.vbat = (status.vbat + measured) / 2.0f; // simple smoothing
}
}
}
}
}

// Convert the current battery voltage status.vbat to a percentage.
// Assumes a linear discharge curve between 3.0V (0%) and 4.15V (100%).
// Returns 999.0f if the voltage is above 4.2V, indicating charging state.
float getBatteryPercentage(void)
{
const float poweredVoltage = 4.23f; // Voltage above 100%
const float maxVoltage = 4.15f; // Voltage at 100%
const float minVoltage = 3.5f; // Voltage at 0%
float voltage = status.vbat;
if (voltage >= poweredVoltage) {
return 999.0f; // Plugged in / charging
} else if (voltage >= maxVoltage) {
return 100.0f;
} else if (voltage <= minVoltage) {
return 0.0f;
} else {
return ((voltage - minVoltage) / (maxVoltage - minVoltage)) * 100.0f;
}
}
31 changes: 31 additions & 0 deletions tinyGS/src/Power/Battery.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef BATTERY_H
#define BATTERY_H
#include "Arduino.h"
#include <Wire.h>
#include "../Status.h"
#include "../ConfigManager/ConfigManager.h"

// Throttle reads of battery voltage (in ms)
#define BATTERY_CHECK_INTERVAL 10000

// ADC control pin polarity differs between some boards (ESP32-S3 vs
// others). `ADC_READ_ENABLE` drives the board-specific pin value that
// enables the voltage divider or ADC front-end; `ADC_READ_DISABLE`
// disables it. These are used by enableADCReading()/disableADCReading().
#if CONFIG_IDF_TARGET_ESP32S3
#define ADC_READ_ENABLE HIGH
#define ADC_READ_DISABLE LOW
#else
#define ADC_READ_ENABLE LOW
#define ADC_READ_DISABLE HIGH
#endif

// Global device status structure defined elsewhere; `status.vbat` is
// used/updated by the battery monitoring code in Battery.cpp.
extern Status status;

void initBatteryMonitoring(void);
void checkBattery(void);
float getBatteryPercentage(void);

#endif
1 change: 1 addition & 0 deletions tinyGS/src/Status.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ struct Status {
bool mqtt_connected = false;
bool radio_ready = false;
int16_t radio_error = 0;
float vbat = 0.0;
PacketInfo lastPacketInfo;
ModemInfo modeminfo;
ModemInfo modeminfolastpckt;
Expand Down
7 changes: 6 additions & 1 deletion tinyGS/tinyGS.ino
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#include "time.h"
#include "src/Mqtt/MQTT_credentials.h"
#include "src/Improv/tinygs_improv.h"
#include "src/Power/Battery.h"


#if RADIOLIB_VERSION_MAJOR != (0x07) || RADIOLIB_VERSION_MINOR != (0x05) || RADIOLIB_VERSION_PATCH != (0x00) || RADIOLIB_VERSION_EXTRA != (0x00)
Expand Down Expand Up @@ -166,8 +167,10 @@ void setup()
// make sure to call doLoop at least once before starting to use the configManager
configManager.doLoop();
board_t board;
if(configManager.getBoardConfig(board))
if(configManager.getBoardConfig(board)) {
pinMode (board.PROG__BUTTON, INPUT_PULLUP);
initBatteryMonitoring();
}
displayInit();
displayShowInitialCredits();
configManager.delay(1000);
Expand Down Expand Up @@ -321,6 +324,8 @@ void loop() {
return;
}

checkBattery();

// connected

mqtt.loop();
Expand Down