Ironed out tons of stuff on the webserver
Logging, time sync, collapsible menus, oh my!
This commit is contained in:
@@ -120,9 +120,9 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
||||
}
|
||||
|
||||
|
||||
#define JACK_TIME get_param_value_t(PARAM_JACK_MSPI ).u32 * 1000 * get_param_value_t(PARAM_JACK_DIST ).u8
|
||||
#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_MSPF).u32 * 1000 * get_param_value_t(PARAM_DRIVE_DIST).u8
|
||||
#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_TPDF).u32 / 10 * get_param_value_t(PARAM_DRIVE_DIST).u8
|
||||
#define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST ).f32
|
||||
#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * get_param_value_t(PARAM_DRIVE_DIST).f32
|
||||
#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * get_param_value_t(PARAM_DRIVE_DIST).f32
|
||||
|
||||
void control_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
@@ -239,8 +239,8 @@ void control_task(void *param) {
|
||||
|
||||
|
||||
int64_t elapsed_t = (current_time-timer_start);
|
||||
int64_t total_t = (timer_end-timer_start);
|
||||
int32_t ticks = get_sensor_counter(SENSOR_DRIVE);
|
||||
int64_t total_t = (timer_end-timer_start);
|
||||
int32_t ticks = get_sensor_counter(SENSOR_DRIVE);
|
||||
//ESP_LOGI("FSM", "[%d] %lld / %lld ms, %ld ticks", current_state, (long long) elapsed_t, (long long) total_t, (long) ticks);
|
||||
|
||||
// Output control
|
||||
|
||||
@@ -4,55 +4,129 @@
|
||||
<title>Control Panel</title>
|
||||
<style>
|
||||
* { background-color: #111; color: #eee; font-family: sans-serif; }
|
||||
input { border: 1px solid #666; background-color: #333; font-family: monospace; text-align: right; width: 100%; box-sizing: border-box; }
|
||||
input, button { width: 100%; }
|
||||
input, button { border: 1px solid #666; background-color: #333; font-family: monospace; text-align: right; box-sizing: border-box; }
|
||||
|
||||
button { text-align: center; }
|
||||
.changed { background-color: #3d3 !important; color: #111 !important; }
|
||||
#commit_btn { width: 100%; background-color: #3d3; color: #111; margin-top: 10px; padding: 10px; cursor: pointer; border: none; font-weight: bold; }
|
||||
#commit_btn[disabled] { background-color: #444; color: #888; cursor: not-allowed; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
td { padding: 8px; border-bottom: 1px solid #222; }
|
||||
tr:hover { background-color: #1a1a1a; }
|
||||
summary { font-weight: bold; text-align: left; color: #ccc; background-color: #723; padding: 0.3rem;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="commit_btn" onclick="commit_params()" disabled>Save Changes</button>
|
||||
|
||||
|
||||
<table id="table">
|
||||
<table>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>System Time</td>
|
||||
<td>Schedule Start</td>
|
||||
<td><input type="time" id="move_start" onchange="changeSchedule(this)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Schedule End</td>
|
||||
<td><input type="time" id="move_end" onchange="changeSchedule(this)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td># Moves/Day</td>
|
||||
<td><input type="number" min="0" id="num_moves" onchange="changeSchedule(this)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Move Distance</td>
|
||||
<td><input type="number" min="0" id="drive_dist" onchange="changeSchedule(this)"/></td>
|
||||
<td>ft</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jack Height</td>
|
||||
<td><input type="number" min="0" id="jack_dist" onchange="changeSchedule(this)"/></td>
|
||||
<td>in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td><input type="datetime-local" id="in_time" step="1" onchange="markChanged(this)"/></td>
|
||||
<td><button id="now_btn" onclick="setTimeToNow()">< NOW</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Battery</td>
|
||||
<td><input readonly="" id="voltage"/></td>
|
||||
<td>V</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button onclick="cmd('start')">START MOVE</button></td>
|
||||
</tr>
|
||||
<tr><td colspan="4">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button onclick="cmd('undo')">UNDO MOVE</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button onclick="cmd('stop')" style="background-color:#800">STOP MOVE</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<br/>
|
||||
<details>
|
||||
<summary>DANGER ZONE</summary>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>Battery Voltage</td>
|
||||
<td> <input readonly="" id="voltage"/> </td>
|
||||
<td>Program RF Remote</td>
|
||||
<td>
|
||||
<button onclick="programRF(0)" style="width:40%">Fwd</button>
|
||||
<button onclick="programRF(1)" style="width:40%">Rev</button>
|
||||
<button onclick="programRF(2)" style="width:40%">Up</button>
|
||||
<button onclick="programRF(3)" style="width:40%">Down</button>
|
||||
<button onclick="programRF(-1)">Cancel Learning</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Calibration</td>
|
||||
<td><button id="cal_jack_btn">Jack Calibration</button>
|
||||
<button id="cal_drive_btn">Drive Calibration</button></td>
|
||||
</tr>
|
||||
<button id="commit_btn" onclick="commit_params()" disabled>Save Changes</button>
|
||||
</td></tr>
|
||||
</table>
|
||||
<table id="table2">
|
||||
<tr>
|
||||
<td>Firmware</td>
|
||||
<td><input type="file" id="firmware_file" accept=".bin"></td>
|
||||
<td><button id="upload_btn" onclick="uploadFirmware()">Upload Firmware</button></td>
|
||||
<td></td>
|
||||
<td><input type="file" id="firmware_file" accept=".bin">
|
||||
<button id="upload_btn" onclick="uploadFirmware()">Upload Firmware</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Log File</td>
|
||||
<td><button id="log_btn" onclick="downloadLogFile()">Download Log</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table id="table"></table>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
let param_values = [];
|
||||
let param_names = [];
|
||||
const param_units = ["ms", "Per Day", "time", "time", "feet", "inches", "", "", "", "", "","","","","","","","","Amps","Amps","Amps","Amps","Amps","Seconds","","","","Volts","Seconds","Volts","Seconds","Seconds","uSeconds","Volts"];
|
||||
|
||||
let param_units = [];
|
||||
|
||||
function cmd(x) {
|
||||
if (x === 'start') {
|
||||
if(!confirm("Will begin moving - please confirm."))
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "./cmd", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({"cmd":x}));
|
||||
xhr.onload = function() {
|
||||
console.log(xhr);
|
||||
}
|
||||
}
|
||||
|
||||
function ge(x) { return document.getElementById(x); }
|
||||
|
||||
// Highlight changed inputs and enable the save button
|
||||
@@ -60,11 +134,19 @@
|
||||
el.classList.add("changed");
|
||||
ge('commit_btn').disabled = false;
|
||||
}
|
||||
|
||||
function toFixed(input, n) {
|
||||
const num = parseFloat(input);
|
||||
if (isNaN(num)) {
|
||||
return null; // or throw error, or return 0
|
||||
}
|
||||
return Number(num.toFixed(n));
|
||||
}
|
||||
|
||||
// --- 1. GET DATA ---
|
||||
function fetchStatus() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "http://192.168.4.1/status", true);
|
||||
xhr.open("GET", "./status", true);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
@@ -77,12 +159,21 @@
|
||||
ge('in_time').value = date;
|
||||
}
|
||||
|
||||
ge('voltage').value = data.battery;
|
||||
ge('voltage').value = toFixed(data.battery, 2);
|
||||
|
||||
|
||||
// Store values (default to empty array if missing)
|
||||
param_values = data.values || [];
|
||||
param_names = data.names || [];
|
||||
param_names = data.names || [];
|
||||
param_units = data.units || [];
|
||||
|
||||
if (!data.rtc_set) {
|
||||
ge('in_time').classList.add('error');
|
||||
if (confirm("Clock not set. Sync with this device's clock?")){
|
||||
setTimeToNow();
|
||||
commit_time();
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Error parsing JSON", e);
|
||||
}
|
||||
@@ -96,65 +187,149 @@
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function s_to_hhmm(s) {
|
||||
const hh = String(Math.floor(s / 3600)).padStart(2, '0');
|
||||
const mm = String(Math.floor((s % 3600) / 60)).padStart(2, '0');
|
||||
return `${hh}:${mm}`;
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
const table = ge("table");
|
||||
// Clear existing parameter rows (rows between index 0 and the last row)
|
||||
while(table.rows.length > 2) { table.deleteRow(1); }
|
||||
while(table.rows.length > 0) { table.deleteRow(0); }
|
||||
|
||||
// Loop through the NAMES array to ensure every input is shown
|
||||
param_names.forEach((name, i) => {
|
||||
let row = table.insertRow(table.rows.length - 1);
|
||||
let row = table.insertRow(table.rows.length);
|
||||
|
||||
let name =(param_names[i] !== undefined && param_names[i] !==null)
|
||||
let pname =(param_names[i] !== undefined && param_names[i] !==null)
|
||||
? param_names[i]
|
||||
: "null";
|
||||
|
||||
// If the server didn't send a value for this index, show "null"
|
||||
let val = (param_values[i] !== undefined && param_values[i] !== null)
|
||||
? param_values[i]
|
||||
let pval = (param_values[i] !== undefined && param_values[i] !== null)
|
||||
? param_values[i]
|
||||
: "null";
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${i}</td>
|
||||
<td>${name}</td>
|
||||
<td><input type="text" id="in_${i}" value="${val}" oninput="markChanged(this)"></td>
|
||||
<td>${pname}</td>
|
||||
<td><input type="number" id="in_${i}" value="${pval}" oninput="markChanged(this)"></td>
|
||||
<td>${param_units[i] || ""}</td>
|
||||
`;
|
||||
});
|
||||
|
||||
ge('num_moves').value = param_values[1];
|
||||
ge('move_start').value = s_to_hhmm(param_values[2]);
|
||||
ge('move_end').value = s_to_hhmm(param_values[3]);
|
||||
|
||||
ge('drive_dist').value = param_values[4];
|
||||
ge('jack_dist').value = param_values[5];
|
||||
}
|
||||
|
||||
function changeSchedule(e) {
|
||||
markChanged(e);
|
||||
if (e.id == "num_moves") {
|
||||
ge('in_1').value = e.value;
|
||||
markChanged(ge('in_1'));
|
||||
}
|
||||
if (e.id == "move_start") {
|
||||
const [hours, minutes] = e.value.split(':').map(Number);
|
||||
ge('in_2').value = hours*3600 + minutes*60;
|
||||
markChanged(ge('in_2'));
|
||||
}
|
||||
if (e.id == "move_end") {
|
||||
const [hours, minutes] = e.value.split(':').map(Number);
|
||||
ge('in_3').value = hours*3600 + minutes*60;
|
||||
markChanged(ge('in_3'));
|
||||
}
|
||||
if (e.id == "drive_dist") {
|
||||
ge('in_4').value = e.value;
|
||||
markChanged(ge('in_4'));
|
||||
}
|
||||
if (e.id == "jack_dist") {
|
||||
ge('in_5').value = e.value;
|
||||
markChanged(ge('in_5'));
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeToNow() {
|
||||
e = ge('in_time');
|
||||
markChanged(e);
|
||||
e.value = new Date().toLocaleString('sv-SE');
|
||||
}
|
||||
|
||||
function commit_time() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
// Time handling
|
||||
const datetimeStr = ge("in_time").value; // e.g., "2024-03-15T14:30:00"
|
||||
|
||||
// Parse the components
|
||||
const [datePart, timePart] = datetimeStr.split('T');
|
||||
const [year, month, day] = datePart.split('-').map(Number);
|
||||
const [hour, minute, second = 0] = timePart.split(':').map(Number);
|
||||
|
||||
// Create UTC timestamp (month is 0-indexed in Date.UTC)
|
||||
const epoch = Math.floor(Date.UTC(year, month - 1, day, hour, minute, second) / 1000);
|
||||
|
||||
xhr.open("POST", "./st", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
ge("in_time").classList.remove("changed");
|
||||
}
|
||||
|
||||
if (document.querySelectorAll('input.changed').length === 0)
|
||||
ge('commit_btn').disabled = true;
|
||||
else
|
||||
ge('commit_btn').disabled = false;
|
||||
}
|
||||
xhr.send(epoch.toString());
|
||||
}
|
||||
|
||||
// --- 2. POST DATA ---
|
||||
function commit_params() {
|
||||
|
||||
ge('commit_btn').disabled = true;
|
||||
const changedInputs = document.querySelectorAll('input.changed');
|
||||
|
||||
sp = {}
|
||||
|
||||
changedInputs.forEach(input => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
if (input.id === "in_time") {
|
||||
// Time handling
|
||||
const epoch = Math.floor(new Date(input.value).getTime() / 1000);
|
||||
xhr.open("POST", "http://192.168.4.1/st", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({ time: epoch }));
|
||||
input.classList.remove("changed");
|
||||
} else {
|
||||
commit_time();
|
||||
// we only really use the parameter table for inputs
|
||||
// the pretty inputs should just modify the param table
|
||||
} else if (input.id.startsWith('in_')) {
|
||||
// Parameter handling
|
||||
const id = input.id.split('_')[1];
|
||||
// If the user typed "null", we send null; otherwise parse as float
|
||||
const val = (input.value.toLowerCase() === "null") ? null : parseFloat(input.value);
|
||||
|
||||
xhr.open("POST", "http://192.168.4.1/sp", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
input.classList.remove("changed");
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify({ id: parseInt(id), value: val }));
|
||||
const id = input.id.split('_')[1];
|
||||
// If the user typed "null", we send null; otherwise parse as float
|
||||
const val = (input.value.toLowerCase() === "null") ? null : parseFloat(input.value);
|
||||
|
||||
sp[id] = val;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(sp).length !== 0) {
|
||||
const xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("POST", "./sp", true);
|
||||
xhr2.setRequestHeader("Content-Type", "application/json");
|
||||
xhr2.onload = function() {
|
||||
if (xhr2.status === 200) {
|
||||
changedInputs.forEach(input => {
|
||||
if (input.id !== "in_time")
|
||||
input.classList.remove("changed");
|
||||
});
|
||||
} else {
|
||||
ge('commit_btn').disabled = false;
|
||||
}
|
||||
};
|
||||
xhr2.send(JSON.stringify(sp));
|
||||
}
|
||||
|
||||
fetchStatus();
|
||||
}
|
||||
|
||||
function uploadFirmware() {
|
||||
@@ -165,7 +340,7 @@
|
||||
}
|
||||
const file = fileInput.files[0];
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "http://192.168.4.1/ota", true);
|
||||
xhr.open("POST", "./ota", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
@@ -180,9 +355,19 @@
|
||||
xhr.send(file);
|
||||
}
|
||||
|
||||
function programRF(i) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "./prf", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(i.toString());
|
||||
if (xhr.status === 200) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadLogFile() {
|
||||
try {
|
||||
const response = await fetch('/log');
|
||||
const response = await fetch('./log');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
23
main/main.c
23
main/main.c
@@ -54,6 +54,7 @@ typedef enum {
|
||||
LED_STATE_ERROR,
|
||||
LED_STATE_AWAKE,
|
||||
LED_STATE_CANCELLING,
|
||||
LED_STATE_ERRORED,
|
||||
LED_STATE_START1,
|
||||
LED_STATE_START2,
|
||||
LED_STATE_START3,
|
||||
@@ -62,17 +63,19 @@ typedef enum {
|
||||
} led_state_t;
|
||||
|
||||
void driveLEDs(led_state_t state) {
|
||||
uint8_t patterns[4][12] = {
|
||||
uint8_t patterns[5][12] = {
|
||||
{1,3,7,6,4,0},
|
||||
{7,0},
|
||||
{0b101,0b001},
|
||||
{1,1,1,1,1,1, 1,1,1,3},
|
||||
{4,2}
|
||||
{4,2},
|
||||
{0b001, 0b101},
|
||||
};
|
||||
switch(state) {
|
||||
case LED_STATE_DRIVING:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/100000) % 6]);
|
||||
break;
|
||||
case LED_STATE_ERROR:
|
||||
ESP_LOGE(TAG, "SOME SORT OF ERROR");
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/1000000) % 2]);
|
||||
break;
|
||||
case LED_STATE_AWAKE:
|
||||
@@ -82,6 +85,9 @@ void driveLEDs(led_state_t state) {
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 2]);
|
||||
break;
|
||||
|
||||
case LED_STATE_ERRORED:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 2]);
|
||||
|
||||
case LED_STATE_BOOTING:
|
||||
i2c_set_led1(0b001);
|
||||
break;
|
||||
@@ -152,11 +158,11 @@ void app_main(void) {
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(100);
|
||||
|
||||
while(true) {
|
||||
/*while(true) {
|
||||
ESP_LOGI(TAG, "TICK");
|
||||
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}*/
|
||||
|
||||
while(true) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
@@ -178,7 +184,12 @@ void app_main(void) {
|
||||
} else if (i2c_get_button_ms(0) > 100){
|
||||
driveLEDs(LED_STATE_START1);
|
||||
} else{
|
||||
driveLEDs(LED_STATE_AWAKE);
|
||||
|
||||
|
||||
if (rtc_is_set())
|
||||
driveLEDs(LED_STATE_AWAKE);
|
||||
else
|
||||
driveLEDs(LED_STATE_ERROR);
|
||||
}
|
||||
|
||||
// when not actively moving we log at a low frequency
|
||||
|
||||
@@ -112,7 +112,7 @@ float get_raw_battery_voltage(void) {
|
||||
!= ESP_OK) { return NAN; }
|
||||
|
||||
// Voltage divider: 150kohm to 1Mohm -> gain = 1.15 -> scale = 1150/150
|
||||
return voltage_mv * 0.00766666666; // same as / 1000.0 * 1150.0 / 150.0;
|
||||
return voltage_mv * 0.00766666666 + get_param_value_t(PARAM_V_SENS_OFFSET).f32; // same as / 1000.0 * 1150.0 / 150.0;
|
||||
}
|
||||
|
||||
esp_err_t process_battery_voltage(void)
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
/*
|
||||
* power_mgmt.c
|
||||
*
|
||||
* 1 kHz power-management task:
|
||||
* • Samples all three H-bridge current sensors (DRIVE, AUX, JACK)
|
||||
* • Samples battery voltage (BAT)
|
||||
* • Applies EMA filtering on every channel
|
||||
* • Updates shared volatile globals for the control FSM
|
||||
* • Handles over-current spike protection
|
||||
*
|
||||
* Updated to modern ESP-IDF ADC API (line fitting)
|
||||
* All variables now defined locally
|
||||
*
|
||||
* Created on: Nov 10, 2025
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "control_fsm.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "power_mgmt.h"
|
||||
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
|
||||
// === GPIO Pin Definitions ===
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||
|
||||
#define PIN_CHG_BULK GPIO_NUM_26
|
||||
|
||||
#define AUTOZERO_THRESH 2000.0f // mA
|
||||
|
||||
|
||||
typedef enum {
|
||||
CHG_T_LOWBAT = 0,
|
||||
CHG_T_BULK = 1,
|
||||
CHG_T_STEADY = 2,
|
||||
} charge_timer_t;
|
||||
|
||||
#define N_CHG_TIMERS 3
|
||||
RTC_DATA_ATTR charge_state_t current_charge_state = CHG_STATE_BULK;
|
||||
RTC_DATA_ATTR int64_t charge_timers[N_CHG_TIMERS] = {-1};
|
||||
|
||||
int64_t now;
|
||||
|
||||
charge_state_t get_charging_state() { return current_charge_state; }
|
||||
|
||||
void setTimerN(charge_timer_t i, int64_t sec) {
|
||||
// set the timer for <sec> in the future if it's currently less than now
|
||||
if (charge_timers[i] < now) {
|
||||
charge_timers[i] = now + sec;
|
||||
ESP_LOGI("BAT", "Set timer[%d] +%lld", i, (long long)sec);
|
||||
}
|
||||
}
|
||||
|
||||
void resetTimerN(charge_timer_t i) {
|
||||
charge_timers[i] = -1;
|
||||
}
|
||||
void resetBatTimers() {
|
||||
for (uint8_t i=0; i<N_CHG_TIMERS; i++)
|
||||
resetTimerN(i);
|
||||
}
|
||||
|
||||
bool getTimerN(charge_timer_t i) {
|
||||
if (charge_timers[i] < 0) return false;
|
||||
return system_rtc_get_raw_time() > charge_timers[i];
|
||||
}
|
||||
|
||||
#define BULK_CHARGE_S 20 //2*60*60
|
||||
#define FLOAT_STEADY_S 10 //30*60
|
||||
#define LOW_DETECT_S 10 //5*60
|
||||
|
||||
#define STEADY_MV 13000
|
||||
#define LOW_MV 12800
|
||||
|
||||
void run_charge_fsm() {
|
||||
now = system_rtc_get_raw_time();
|
||||
|
||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
||||
|
||||
if (rtc_is_set()) {
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
// turn off bulk charging and go to float when time is up
|
||||
if (getTimerN(CHG_T_BULK)) {
|
||||
ESP_LOGI("BAT", "BULK -> FLOAT");
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHG_STATE_FLOAT:
|
||||
if (getTimerN(CHG_T_STEADY)) {
|
||||
ESP_LOGI("BAT", "FLOAT -> OFF");
|
||||
current_charge_state = CHG_STATE_OFF;
|
||||
}
|
||||
if (get_battery_mV() > STEADY_MV) {
|
||||
setTimerN(CHG_T_STEADY, FLOAT_STEADY_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_STEADY);
|
||||
}
|
||||
// NO break; !! float should also kick into bulk with same triggers
|
||||
case CHG_STATE_OFF:
|
||||
|
||||
// after 5 minutes of low-ish battery go into bulk charge
|
||||
if (getTimerN(CHG_T_LOWBAT)) {
|
||||
ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
setTimerN(CHG_T_BULK, BULK_CHARGE_S);
|
||||
}
|
||||
if (get_battery_mV() < LOW_MV) {
|
||||
setTimerN(CHG_T_LOWBAT, LOW_DETECT_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_LOWBAT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
}
|
||||
|
||||
//rtc_gpio_hold_dis(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_dis(PIN_CHG_DISABLE);
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
gpio_set_level(PIN_CHG_BULK, 1);
|
||||
//ESP_LOGI("BAT", "BULK");
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
//ESP_LOGI("BAT", "FLOAT");
|
||||
break;
|
||||
case CHG_STATE_OFF:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
//ESP_LOGI("BAT", "OFF");
|
||||
break;
|
||||
}
|
||||
//rtc_gpio_hold_en(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool enabled; // Auto-zero active for this channel
|
||||
float threshold_ma; // Max current to consider "zero" (mA)
|
||||
float learned_offset_mv; // Accumulated zero offset (mV)
|
||||
bool initialized; // First valid zero established
|
||||
} autozero_t;
|
||||
static autozero_t autozero[N_BRIDGES] = {0};
|
||||
|
||||
// === E-Fuse (Software Breaker) Configuration ===
|
||||
static const char* currentLimits_A[N_BRIDGES] = {
|
||||
[BRIDGE_DRIVE] = "efuse_drive_A", //40000,
|
||||
[BRIDGE_AUX] = "efuse_aux_A", // 5000,
|
||||
[BRIDGE_JACK] = "efuse_jack_A" // 10000
|
||||
};
|
||||
static const float i2t_thresholds[N_BRIDGES] = { // A^2*s (tunable per bridge if needed)
|
||||
[BRIDGE_DRIVE] = 6.0f,
|
||||
[BRIDGE_AUX] = 6.0f,
|
||||
[BRIDGE_JACK] = 6.0f
|
||||
};
|
||||
static const float i_instant[N_BRIDGES] = { // Instant trip multiplier of I_rated
|
||||
[BRIDGE_DRIVE] = 15.0f,
|
||||
[BRIDGE_AUX] = 15.0f,
|
||||
[BRIDGE_JACK] = 15.0f
|
||||
};
|
||||
static const float cool_rate[N_BRIDGES] = { // Cooling constant (1/s)
|
||||
[BRIDGE_DRIVE] = 0.008f,
|
||||
[BRIDGE_AUX] = 0.008f,
|
||||
[BRIDGE_JACK] = 0.008f
|
||||
};
|
||||
static const int32_t cooldown_ms[N_BRIDGES] = { // Auto-reset delay after trip
|
||||
[BRIDGE_DRIVE] = 5000,
|
||||
[BRIDGE_AUX] = 5000,
|
||||
[BRIDGE_JACK] = 5000
|
||||
};
|
||||
|
||||
static float efuse_heat[N_BRIDGES] = {0};
|
||||
static uint64_t efuse_trip_time[N_BRIDGES] = {0}; // Timestamp when tripped
|
||||
static bool efuse_tripped[N_BRIDGES] = {false};
|
||||
|
||||
// === ADC Handles ===
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc_cali_handle = NULL;
|
||||
|
||||
// === EMA Filter State ===
|
||||
#define EMA_ALPHA_CURRENT 0.5f
|
||||
#define EMA_ALPHA_BATTERY 0.05f
|
||||
|
||||
static float ema_current[N_BRIDGES] = {0};
|
||||
static bool ema_init[N_BRIDGES] = {false};
|
||||
|
||||
static float ema_battery = 0.0f;
|
||||
static bool ema_battery_init = false;
|
||||
|
||||
// === Shared Volatile Outputs ===
|
||||
volatile int32_t bridgeCurrents_mA[N_BRIDGES] = {0};
|
||||
volatile int32_t batteryVoltage_mV = 0;
|
||||
|
||||
// === ADC Initialization ===
|
||||
static esp_err_t adc_init(void) {
|
||||
if (adc1_handle != NULL) {
|
||||
return ESP_OK; // Already initialized
|
||||
}
|
||||
|
||||
// ADC1 oneshot mode
|
||||
adc_oneshot_unit_init_cfg_t init_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_cfg, &adc1_handle));
|
||||
|
||||
// Configure all channels
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_ATTEN_DB_11,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS1, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS2, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS3, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_SENS_BAT, &chan_cfg));
|
||||
|
||||
// Line fitting calibration (modern scheme)
|
||||
adc_cali_line_fitting_config_t cali_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.atten = ADC_ATTEN_DB_11,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void autozero_enable(bridge_t bridge, bool enable) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].enabled = enable;
|
||||
if (!enable) {
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void autozero_set_threshold(bridge_t bridge, float threshold_ma) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].threshold_ma = fmaxf(0.0f, threshold_ma);
|
||||
}
|
||||
|
||||
float autozero_get_offset_mv(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return 0.0f;
|
||||
return autozero[bridge].learned_offset_mv;
|
||||
}
|
||||
|
||||
void autozero_reset(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
|
||||
void autozero_reset_all(void) {
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
autozero_reset((bridge_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
// === Raw Current Reading (mA) ===
|
||||
static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
adc_channel_t pin;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
||||
default: return -42069; // lol
|
||||
}
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float current_sense_mv = (float)voltage_mv;
|
||||
autozero_t *az = &autozero[bridge];
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (az->enabled && get_bridge_state(bridge)==0) {
|
||||
float raw_current_ma = 0.0f;
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 13.20f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fabsf(raw_current_ma) <= az->threshold_ma) {
|
||||
// Valid zero sample
|
||||
if (!az->initialized) {
|
||||
az->learned_offset_mv = current_sense_mv - 1650.0f;
|
||||
az->initialized = true;
|
||||
} else {
|
||||
// EMA on offset (slow adaptation)
|
||||
float alpha = 0.1f;
|
||||
az->learned_offset_mv = alpha * (current_sense_mv - 1650.0f) +
|
||||
(1.0f - alpha) * az->learned_offset_mv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === APPLY AUTO-ZERO OFFSET ===
|
||||
float corrected_mv = current_sense_mv - az->learned_offset_mv;
|
||||
|
||||
int32_t offset_mv = (int32_t)(corrected_mv - 1650.0f);
|
||||
int32_t current_ma = 0;
|
||||
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
current_ma = offset_mv * 1000 / 44; // 44 mV/A
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
current_ma = offset_mv * 10000 / 132; // 13.2 mV/A
|
||||
break;
|
||||
}
|
||||
|
||||
return current_ma;
|
||||
}
|
||||
|
||||
// === Raw Battery Voltage Reading (mV) ===
|
||||
static int32_t read_battery_voltage_raw(void)
|
||||
{
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Voltage divider: 150kΩ to 1MΩ → gain = 1.15 → scale = 1150/150
|
||||
return (int32_t)voltage_mv * 1150 / 150;
|
||||
}
|
||||
|
||||
// === EMA Filter Update ===
|
||||
static void apply_ema(float *state, bool *init, float alpha, int32_t raw, volatile int32_t *out)
|
||||
{
|
||||
if (!*init) {
|
||||
*state = (float)raw;
|
||||
*init = true;
|
||||
} else {
|
||||
*state = alpha * (float)raw + (1.0f - alpha) * *state;
|
||||
}
|
||||
*out = (int32_t)(*state + 0.5f);
|
||||
}
|
||||
|
||||
// === Public Accessors ===
|
||||
int32_t get_bridge_mA(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return -1;
|
||||
return (int32_t)bridgeCurrents_mA[bridge];
|
||||
}
|
||||
|
||||
int32_t get_battery_mV(void)
|
||||
{
|
||||
return (int32_t)batteryVoltage_mV;
|
||||
}
|
||||
|
||||
// === E-Fuse: Trip Logic (called every cycle) ===
|
||||
static void efuse_update(uint8_t bridge, float I, float dt, uint64_t now)
|
||||
{
|
||||
float I_rated = (float)get_param_i8(currentLimits_A[bridge]);
|
||||
float I_norm = I / I_rated;
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= i_instant[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooling when below threshold
|
||||
if (I_norm < 1.1f) {
|
||||
efuse_heat[bridge] -= efuse_heat[bridge] * cool_rate[bridge] * dt;
|
||||
efuse_heat[bridge] = fmaxf(0.0f, efuse_heat[bridge]);
|
||||
efuse_tripped[bridge] = false; // Auto-clear if cooled
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate heat (I²t)
|
||||
efuse_heat[bridge] += (I_norm * I_norm) * dt;
|
||||
|
||||
if (efuse_heat[bridge] >= i2t_thresholds[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
}
|
||||
}
|
||||
|
||||
// === E-Fuse: Auto-Reset After Cooldown ===
|
||||
static void efuse_cooldown_check(uint64_t now)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
if (efuse_tripped[i] &&
|
||||
(now - efuse_trip_time[i]) >= (cooldown_ms[i] * 1000ULL)) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Public E-Fuse Controls ===
|
||||
void efuse_reset_all(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool efuse_is_tripped(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
return efuse_tripped[bridge];
|
||||
}
|
||||
|
||||
|
||||
// === Power Management Task ===
|
||||
void power_mgmt_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
/*gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << PIN_CHG_DISABLE) | (1ULL << PIN_CHG_BULK),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);*/
|
||||
|
||||
/*// Enable RTC GPIO domain (required for hold)
|
||||
rtc_gpio_init(PIN_CHG_DISABLE);
|
||||
rtc_gpio_init(PIN_CHG_BULK);
|
||||
|
||||
// Set as output
|
||||
rtc_gpio_set_direction(PIN_CHG_DISABLE, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_direction(PIN_CHG_BULK, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
|
||||
// Optional: set initial level (will be held)
|
||||
//rtc_gpio_set_level(PIN_CHG_DISABLE, 1); // e.g., start disabled
|
||||
//rtc_gpio_set_level(PIN_CHG_BULK, 0);
|
||||
|
||||
// **Critical: Enable hold function**
|
||||
rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
rtc_gpio_hold_en(PIN_CHG_BULK);*/
|
||||
|
||||
ESP_ERROR_CHECK(adc_init());
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||
|
||||
// Optional: Enable auto-zero with default threshold
|
||||
autozero_enable(BRIDGE_DRIVE, true);
|
||||
autozero_enable(BRIDGE_AUX, true);
|
||||
autozero_enable(BRIDGE_JACK, true);
|
||||
|
||||
autozero_set_threshold(BRIDGE_DRIVE, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_AUX, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_JACK, AUTOZERO_THRESH);
|
||||
|
||||
//uint64_t last_wake_time = esp_timer_get_time();
|
||||
//const uint64_t period = 5000; // 100 us => 10kHz
|
||||
while (1) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
uint64_t now_us = esp_timer_get_time();
|
||||
|
||||
/*if (now - last_wake_time < period) {
|
||||
uint32_t delay_us = (period - (now - last_wake_time)) / 1000;
|
||||
if (delay_us > 0) vTaskDelay(pdMS_TO_TICKS(delay_us));
|
||||
continue;
|
||||
}
|
||||
last_wake_time = now;*/
|
||||
|
||||
// Sample currents
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
int32_t raw_ma = read_bridge_current_raw((bridge_t)i);
|
||||
apply_ema(&ema_current[i], &ema_init[i], EMA_ALPHA_CURRENT,
|
||||
raw_ma, &bridgeCurrents_mA[i]);
|
||||
|
||||
// Reset spike timer if under limit
|
||||
/*if (bridgeCurrents_mA[i] < currentLimits_mA[i]) {
|
||||
currentSpikeSafeTimes[i] = now + CURRENT_SPIKE_TIME_US;
|
||||
}*/
|
||||
|
||||
// === E-FUSE UPDATE ===
|
||||
float I = (float)bridgeCurrents_mA[i] / 1000.0f;
|
||||
float dt = 0.020f; // 20 ms task period
|
||||
efuse_update(i, I, dt, now_us);
|
||||
}
|
||||
|
||||
/*ESP_LOGI("PWR", "[ %6ld | %6ld | %6ld mA ] { %6ld mV }",
|
||||
(long)bridgeCurrents_mA[BRIDGE_DRIVE],
|
||||
(long)bridgeCurrents_mA[BRIDGE_JACK],
|
||||
(long)bridgeCurrents_mA[BRIDGE_AUX],
|
||||
(long)batteryVoltage_mV);*/
|
||||
|
||||
// Sample battery
|
||||
int32_t raw_bat = read_battery_voltage_raw();
|
||||
apply_ema(&ema_battery, &ema_battery_init, EMA_ALPHA_BATTERY,
|
||||
raw_bat, &batteryVoltage_mV);
|
||||
|
||||
|
||||
//run_charge_fsm();
|
||||
efuse_cooldown_check(now_us);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void start_power() {
|
||||
xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
void shutdown_power() {
|
||||
|
||||
}
|
||||
@@ -103,6 +103,13 @@ int64_t system_rtc_get_raw_time(void)
|
||||
return (int64_t)tv.tv_sec;
|
||||
}
|
||||
|
||||
|
||||
void system_rtc_set_raw_time(int64_t tv_sec)
|
||||
{
|
||||
rtc_set = true;
|
||||
settimeofday(&(struct timeval){.tv_sec = tv_sec, .tv_usec=0}, NULL);
|
||||
}
|
||||
|
||||
uint64_t rtc_time_ms(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
@@ -42,6 +42,7 @@ void adjust_rtc_min(char *key, int8_t dir);
|
||||
void rtc_get_time(struct tm * timeinfo);
|
||||
|
||||
int64_t system_rtc_get_raw_time(void);
|
||||
void system_rtc_set_raw_time(int64_t);
|
||||
|
||||
bool alarm_tripped();
|
||||
|
||||
|
||||
@@ -17,41 +17,45 @@
|
||||
#define PARAM_NAME_STR(name) #name
|
||||
|
||||
// Generate parameter table with live values (initialized to defaults)
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_VALUE_INIT(type, default_val),
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_VALUE_INIT(type, default_val),
|
||||
param_value_t parameter_table[NUM_PARAMS] = {
|
||||
PARAM_LIST
|
||||
};
|
||||
#undef PARAM_DEF
|
||||
|
||||
// Generate default values array
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_VALUE_INIT(type, default_val),
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_VALUE_INIT(type, default_val),
|
||||
const param_value_t parameter_defaults[NUM_PARAMS] = {
|
||||
PARAM_LIST
|
||||
};
|
||||
#undef PARAM_DEF
|
||||
|
||||
// Generate parameter types array
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_TYPE_ENUM(type),
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_TYPE_ENUM(type),
|
||||
const param_type_e parameter_types[NUM_PARAMS] = {
|
||||
PARAM_LIST
|
||||
};
|
||||
#undef PARAM_DEF
|
||||
|
||||
// Generate parameter names array
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_NAME_STR(name),
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_NAME_STR(name),
|
||||
const char* parameter_names[NUM_PARAMS] = {
|
||||
PARAM_LIST
|
||||
};
|
||||
#undef PARAM_DEF
|
||||
|
||||
// Generate parameter units array (8 chars max per unit)
|
||||
#define PARAM_DEF(name, type, default_val, unit) unit,
|
||||
const char parameter_units[NUM_PARAMS][8] = {
|
||||
PARAM_LIST
|
||||
};
|
||||
#undef PARAM_DEF
|
||||
|
||||
|
||||
// Partition pointer
|
||||
static const esp_partition_t *storage_partition = NULL;
|
||||
|
||||
|
||||
// Calculate offset for log area (after parameters sector)
|
||||
#define LOG_START_OFFSET FLASH_SECTOR_SIZE
|
||||
|
||||
// Log head tracking
|
||||
static uint32_t log_head_index = 0;
|
||||
static uint32_t log_tail_index = 0;
|
||||
@@ -106,6 +110,13 @@ param_value_t get_param_default(param_idx_t id) {
|
||||
return parameter_defaults[id];
|
||||
}
|
||||
|
||||
const char* get_param_unit(param_idx_t id) {
|
||||
if (id >= NUM_PARAMS) {
|
||||
return "";
|
||||
}
|
||||
return parameter_units[id];
|
||||
}
|
||||
|
||||
esp_err_t commit_params() {
|
||||
if (storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Storage partition not initialized");
|
||||
@@ -121,8 +132,8 @@ esp_err_t commit_params() {
|
||||
params_to_store[i].crc = esp_crc32_le(0, (uint8_t*)¶meter_table[i], sizeof(param_value_t));
|
||||
}
|
||||
|
||||
// Erase the first sector (4096 bytes)
|
||||
esp_err_t err = esp_partition_erase_range(storage_partition, PARAMS_OFFSET, FLASH_SECTOR_SIZE);
|
||||
// Erase the sectors for parameter storage
|
||||
esp_err_t err = esp_partition_erase_range(storage_partition, PARAMS_OFFSET, LOG_START_OFFSET);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase parameter sector: %s", esp_err_to_name(err));
|
||||
return ESP_FAIL;
|
||||
|
||||
@@ -51,59 +51,60 @@ typedef enum {
|
||||
// ============================================================================
|
||||
// PARAMETER DEFINITION MACRO
|
||||
// ============================================================================
|
||||
// Usage: PARAM_DEF(NAME, TYPE, DEFAULT_VALUE)
|
||||
// Usage: PARAM_DEF(NAME, TYPE, DEFAULT_VALUE, UNIT)
|
||||
//
|
||||
// Examples:
|
||||
// PARAM_DEF(NUM_MOVES, u32, 0)
|
||||
// PARAM_DEF(EFUSE_1_AS, u16, 2400)
|
||||
// PARAM_DEF(JACK_DIST, u8, 5)
|
||||
// PARAM_DEF(KEYCODE_0, i64, -1)
|
||||
// PARAM_DEF(TEMPERATURE, f32, 25.5)
|
||||
// PARAM_DEF(NUM_MOVES, u32, 0, "")
|
||||
// PARAM_DEF(EFUSE_1_AS, u16, 2400, "mA")
|
||||
// PARAM_DEF(JACK_DIST, u8, 5, "mm")
|
||||
// PARAM_DEF(KEYCODE_0, i64, -1, "")
|
||||
// PARAM_DEF(TEMPERATURE, f32, 25.5, "C")
|
||||
// ============================================================================
|
||||
// REMEMBER: ORDER IS IMPERATIVE! PARAMETERS ARE ENTERED IN THE TABLE BY INDEX!
|
||||
// ============================================================================
|
||||
|
||||
#define PARAM_LIST \
|
||||
PARAM_DEF(BOOT_TIME, i64, 0) \
|
||||
PARAM_DEF(NUM_MOVES, u32, 0) \
|
||||
PARAM_DEF(MOVE_START, u32, 0) \
|
||||
PARAM_DEF(MOVE_END, u32, 0) \
|
||||
PARAM_DEF(DRIVE_DIST, u16, 10) /*4*/\
|
||||
PARAM_DEF(JACK_DIST, u8, 5) \
|
||||
PARAM_DEF(DRIVE_TPDF, u16, 4000) \
|
||||
PARAM_DEF(DRIVE_MSPF, u16, 600) \
|
||||
PARAM_DEF(JACK_MSPI, u16, 600) /*8*/\
|
||||
PARAM_DEF(KEYCODE_0, i64, 0x19000000005D0C61) \
|
||||
PARAM_DEF(KEYCODE_1, i64, 0x19000000005D0C62) \
|
||||
PARAM_DEF(KEYCODE_2, i64, 0x19000000005D0C64) \
|
||||
PARAM_DEF(KEYCODE_3, i64, 0x19000000005D0C68) /*12*/\
|
||||
PARAM_DEF(KEYCODE_4, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_5, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_6, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_7, i64, -1) /*16*/\
|
||||
PARAM_DEF(ADC_ALPHA_BATTERY, f32, 0.02) \
|
||||
PARAM_DEF(ADC_ALPHA_ISENS, f32, 0.02) \
|
||||
PARAM_DEF(ADC_ALPHA_IAZ, f32, 0.005) \
|
||||
PARAM_DEF(ADC_DB_IAZ, f32, 5.0) /*20*/\
|
||||
PARAM_DEF(EFUSE_INOM_1, f32, 40.0) \
|
||||
PARAM_DEF(EFUSE_INOM_2, f32, 6.0) \
|
||||
PARAM_DEF(EFUSE_INOM_3, f32, 2.0) \
|
||||
PARAM_DEF(EFUSE_HEAT_THRESH, f32, 60.0) /*24*/\
|
||||
PARAM_DEF(EFUSE_KINST, f32, 4.0) \
|
||||
PARAM_DEF(EFUSE_TAUCOOL, f32, 0.2) \
|
||||
PARAM_DEF(EFUSE_TCOOL, i64, 5000000) \
|
||||
PARAM_DEF(LOW_PROTECTION_V, f32, 10.0) /*28*/\
|
||||
PARAM_DEF(LOW_PROTECTION_S, i64, 10) \
|
||||
PARAM_DEF(CHG_LOW_V, f32, 5.0) \
|
||||
PARAM_DEF(CHG_LOW_S, i64, 5.0) \
|
||||
PARAM_DEF(CHG_BULK_S, i64, 20) /*32*/\
|
||||
PARAM_DEF(RF_PULSE_LENGTH, u64, 350000) \
|
||||
PARAM_DEF(BOOT_TIME, i64, 0, "us") \
|
||||
PARAM_DEF(NUM_MOVES, u32, 0, "") \
|
||||
PARAM_DEF(MOVE_START, u32, 0, "s") \
|
||||
PARAM_DEF(MOVE_END, u32, 0, "s") \
|
||||
PARAM_DEF(DRIVE_DIST, u16, 10, "ft") /*4*/\
|
||||
PARAM_DEF(JACK_DIST, u8, 5, "in") \
|
||||
PARAM_DEF(DRIVE_KE, f32, 4000, "n/ft") \
|
||||
PARAM_DEF(DRIVE_KT, f32, 600, "ms/ft") \
|
||||
PARAM_DEF(JACK_KT, f32, 600, "ms/in") /*8*/\
|
||||
PARAM_DEF(KEYCODE_0, i64, 0x19000000005D0C61, "") \
|
||||
PARAM_DEF(KEYCODE_1, i64, 0x19000000005D0C62, "") \
|
||||
PARAM_DEF(KEYCODE_2, i64, 0x19000000005D0C64, "") \
|
||||
PARAM_DEF(KEYCODE_3, i64, 0x19000000005D0C68, "") /*12*/\
|
||||
PARAM_DEF(KEYCODE_4, i64, -1, "") \
|
||||
PARAM_DEF(KEYCODE_5, i64, -1, "") \
|
||||
PARAM_DEF(KEYCODE_6, i64, -1, "") \
|
||||
PARAM_DEF(KEYCODE_7, i64, -1, "") /*16*/\
|
||||
PARAM_DEF(ADC_ALPHA_BATTERY, f32, 0.02, "-") \
|
||||
PARAM_DEF(ADC_ALPHA_ISENS, f32, 0.02, "-") \
|
||||
PARAM_DEF(ADC_ALPHA_IAZ, f32, 0.005, "-") \
|
||||
PARAM_DEF(ADC_DB_IAZ, f32, 5.0, "A") /*20*/\
|
||||
PARAM_DEF(EFUSE_INOM_1, f32, 40.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_2, f32, 6.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_3, f32, 2.0, "A") \
|
||||
PARAM_DEF(EFUSE_HEAT_THRESH, f32, 60.0, "i/i^2-s") /*24*/\
|
||||
PARAM_DEF(EFUSE_KINST, f32, 4.0, "i/i") \
|
||||
PARAM_DEF(EFUSE_TAUCOOL, f32, 0.2, "i") \
|
||||
PARAM_DEF(EFUSE_TCOOL, i64, 5000000, "us") \
|
||||
PARAM_DEF(LOW_PROTECTION_V, f32, 10.0, "V") /*28*/\
|
||||
PARAM_DEF(LOW_PROTECTION_S, i64, 10, "s") \
|
||||
PARAM_DEF(CHG_LOW_V, f32, 5.0, "V") \
|
||||
PARAM_DEF(CHG_LOW_S, i64, 5.0, "s") \
|
||||
PARAM_DEF(CHG_BULK_S, i64, 20, "s") /*32*/\
|
||||
PARAM_DEF(RF_PULSE_LENGTH, u64, 350000, "us") \
|
||||
PARAM_DEF(V_SENS_OFFSET, f32, 0.4, "V") \
|
||||
|
||||
|
||||
|
||||
|
||||
// Generate enum for parameter indices
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_##name,
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_##name,
|
||||
typedef enum {
|
||||
PARAM_LIST
|
||||
NUM_PARAMS
|
||||
@@ -119,6 +120,7 @@ extern param_value_t parameter_table[NUM_PARAMS];
|
||||
extern const param_value_t parameter_defaults[NUM_PARAMS];
|
||||
extern const param_type_e parameter_types[NUM_PARAMS];
|
||||
extern const char* parameter_names[NUM_PARAMS];
|
||||
extern const char parameter_units[NUM_PARAMS][8];
|
||||
|
||||
esp_err_t storage_init();
|
||||
esp_err_t log_init();
|
||||
@@ -128,6 +130,7 @@ esp_err_t set_param_value_t(param_idx_t id, param_value_t val);
|
||||
param_type_e get_param_type(param_idx_t id);
|
||||
const char* get_param_name(param_idx_t id);
|
||||
param_value_t get_param_default(param_idx_t id);
|
||||
const char* get_param_unit(param_idx_t id);
|
||||
|
||||
esp_err_t commit_params();
|
||||
|
||||
@@ -141,9 +144,12 @@ esp_err_t write_dummy_log_3();
|
||||
|
||||
|
||||
#define LOG_ENTRY_SIZE 32
|
||||
#define LOG_NUM_ENTRIES 512
|
||||
#define FLASH_SECTOR_SIZE 4096
|
||||
|
||||
// Calculate offset for log area (after parameters sector)
|
||||
#define PARARMETER_NUM_SECTORS 4
|
||||
#define LOG_START_OFFSET FLASH_SECTOR_SIZE*PARARMETER_NUM_SECTORS
|
||||
|
||||
esp_err_t write_log(char* entry);
|
||||
|
||||
void storage_deinit();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,28 +1,22 @@
|
||||
<!doctype html><title>Control Panel</title><style>*{color:#eee;background-color:#111;font-family:sans-serif}input{text-align:right;box-sizing:border-box;background-color:#333;border:1px solid #666;width:100%;font-family:monospace}.changed{color:#111!important;background-color:#3d3!important}#commit_btn{color:#111;cursor:pointer;background-color:#3d3;border:none;width:100%;margin-top:10px;padding:10px;font-weight:700}#commit_btn[disabled]{color:#888;cursor:not-allowed;background-color:#444}table{border-collapse:collapse;width:100%}td{border-bottom:1px solid #222;padding:8px}tr:hover{background-color:#1a1a1a}</style></head><body><table id=table><tr><td>-</td><td>System Time</td><td><input id=in_time onchange=markChanged(this) step=1 type=datetime-local></td><td></td></tr><tr><td colspan=4><button disabled id=commit_btn onclick=commit_params()>Save Changes</button></td></tr></table><table id=table2><tr><td>Firmware</td><td><input accept=.bin id=firmware_file type=file></td><td><button id=upload_btn onclick=uploadFirmware()>Upload Firmware</button></td><td></td></tr><tr><td>Log File</td><td><button id=log_btn onclick=downloadLogFile()>Download Log</button></td><td></td></tr></table><script>let param_values=[];const param_names=[`Drive Distance`,`TPDF`,`Efuse Amt`,`Gain`,`Offset`],param_units=[`in`,`ft`,`in`,`V`,`ms`];function ge(x){return document.getElementById(x)}
|
||||
<!doctype html><title>Control Panel</title><style>*{color:#eee;background-color:#111;font-family:sans-serif}input,button{text-align:right;box-sizing:border-box;background-color:#333;border:1px solid #666;width:100%;font-family:monospace}button{text-align:center}.changed{color:#111!important;background-color:#3d3!important}#commit_btn{color:#111;cursor:pointer;background-color:#3d3;border:none;width:100%;margin-top:10px;padding:10px;font-weight:700}#commit_btn[disabled]{color:#888;cursor:not-allowed;background-color:#444}table{border-collapse:collapse;width:100%}td{border-bottom:1px solid #222;padding:8px}tr:hover{background-color:#1a1a1a}summary{text-align:left;color:#ccc;background-color:#723;padding:.3rem;font-weight:700}</style></head><body><button disabled id=commit_btn onclick=commit_params()>Save Changes</button><table><tr><td>Schedule Start</td><td><input id=move_start onchange=changeSchedule(this) type=time></td></tr><tr><td>Schedule End</td><td><input id=move_end onchange=changeSchedule(this) type=time></td></tr><tr><td># Moves/Day</td><td><input id=num_moves min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Move Distance</td><td><input id=drive_dist min=0 onchange=changeSchedule(this) type=number></td><td>ft</td></tr><tr><td>Jack Height</td><td><input id=jack_dist min=0 onchange=changeSchedule(this) type=number></td><td>in</td></tr><tr><td>Time</td><td><input id=in_time onchange=markChanged(this) step=1 type=datetime-local></td><td><button id=now_btn onclick=setTimeToNow()>< NOW</button></td></tr><tr><td>Battery</td><td><input id=voltage readonly></td><td>V</td></tr><tr><td></td><td><button onclick="cmd('start')">START MOVE</button></td></tr><tr><td></td><td><button onclick="cmd('undo')">UNDO MOVE</button></td></tr><tr><td></td><td><button onclick="cmd('stop')" style=background-color:#800>STOP MOVE</button></td></tr></table><br><details><summary>DANGER ZONE</summary> <table><tr><td>Program RF Remote</td><td><button onclick=programRF(0) style=width:40%>Fwd</button> <button onclick=programRF(1) style=width:40%>Rev</button> <button onclick=programRF(2) style=width:40%>Up</button> <button onclick=programRF(3) style=width:40%>Down</button> <button onclick=programRF(-1)>Cancel Learning</button></td></tr><tr><td>Calibration</td><td><button id=cal_jack_btn>Jack Calibration</button> <button id=cal_drive_btn>Drive Calibration</button></td></tr><tr><td>Firmware</td><td><input accept=.bin id=firmware_file type=file> <button id=upload_btn onclick=uploadFirmware()>Upload Firmware</button></td></tr><tr><td>Log File</td><td><button id=log_btn onclick=downloadLogFile()>Download Log</button></td></tr></table> <table id=table></table></details><script>let param_values=[],param_names=[],param_units=[];function cmd(x){if(x===`start`&&!confirm(`Will begin moving - please confirm.`))return;let xhr=new XMLHttpRequest;xhr.open(`POST`,`./cmd`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.send(JSON.stringify({cmd:x})),xhr.onload=function(){console.log(xhr)}}function ge(x){return document.getElementById(x)}
|
||||
// Highlight changed inputs and enable the save button
|
||||
function markChanged(el){el.classList.add(`changed`),ge(`commit_btn`).disabled=!1}
|
||||
function markChanged(el){el.classList.add(`changed`),ge(`commit_btn`).disabled=!1}function toFixed(input,n){let num=parseFloat(input);return isNaN(num)?null:Number(num.toFixed(n))}
|
||||
// --- 1. GET DATA ---
|
||||
function fetchStatus(){let xhr=new XMLHttpRequest;xhr.open(`GET`,`http://192.168.4.1/status`,!0),xhr.onload=function(){if(xhr.status===200)try{console.log(xhr.responseText);let data=JSON.parse(xhr.responseText);
|
||||
function fetchStatus(){let xhr=new XMLHttpRequest;xhr.open(`GET`,`./status`,!0),xhr.onload=function(){if(xhr.status===200)try{console.log(xhr.responseText);let data=JSON.parse(xhr.responseText);
|
||||
// Update time field if available
|
||||
if(data.time){let date=(/* @__PURE__ */ new Date(data.time*1e3)).toISOString().slice(0,19);ge(`in_time`).value=date}
|
||||
// Store values (default to empty array if missing)
|
||||
param_values=data.params||[]}catch(e){console.error(`Error parsing JSON`,e)}
|
||||
if(data.time){let date=(/* @__PURE__ */ new Date(data.time*1e3)).toISOString().slice(0,19);ge(`in_time`).value=date}ge(`voltage`).value=toFixed(data.battery,2),param_values=data.values||[],param_names=data.names||[],param_units=data.units||[],data.rtc_set||(ge(`in_time`).classList.add(`error`),confirm(`Clock not set. Sync with this device's clock?`)&&(setTimeToNow(),commit_time()))}catch(e){console.error(`Error parsing JSON`,e)}
|
||||
// Always render table even if request fails or data is empty
|
||||
renderTable()},xhr.onerror=function(e){console.error(`Network error`,e),renderTable()},xhr.send()}function renderTable(){let table=ge(`table`);
|
||||
renderTable()},xhr.onerror=function(e){console.error(`Network error`,e),renderTable()},xhr.send()}function s_to_hhmm(s){return`${String(Math.floor(s/3600)).padStart(2,`0`)}:${String(Math.floor(s%3600/60)).padStart(2,`0`)}`}function renderTable(){let table=ge(`table`);
|
||||
// Clear existing parameter rows (rows between index 0 and the last row)
|
||||
for(;table.rows.length>2;)table.deleteRow(1);
|
||||
// Loop through the NAMES array to ensure every input is shown
|
||||
param_names.forEach((name,i)=>{let row=table.insertRow(table.rows.length-1);row.innerHTML=`
|
||||
for(;table.rows.length>0;)table.deleteRow(0);param_names.forEach((name,i)=>{let row=table.insertRow(table.rows.length);row.innerHTML=`
|
||||
<td>${i}</td>
|
||||
<td>${name}</td>
|
||||
<td><input type="text" id="in_${i}" value="${param_values[i]!==void 0&¶m_values[i]!==null?param_values[i]:`null`}" oninput="markChanged(this)"></td>
|
||||
<td>${param_names[i]!==void 0&¶m_names[i]!==null?param_names[i]:`null`}</td>
|
||||
<td><input type="number" id="in_${i}" value="${param_values[i]!==void 0&¶m_values[i]!==null?param_values[i]:`null`}" oninput="markChanged(this)"></td>
|
||||
<td>${param_units[i]||``}</td>
|
||||
`})}
|
||||
`}),ge(`num_moves`).value=param_values[1],ge(`move_start`).value=s_to_hhmm(param_values[2]),ge(`move_end`).value=s_to_hhmm(param_values[3]),ge(`drive_dist`).value=param_values[4],ge(`jack_dist`).value=param_values[5]}function changeSchedule(e){if(markChanged(e),e.id==`num_moves`&&(ge(`in_1`).value=e.value,markChanged(ge(`in_1`))),e.id==`move_start`){let[hours,minutes]=e.value.split(`:`).map(Number);ge(`in_2`).value=hours*3600+minutes*60,markChanged(ge(`in_2`))}if(e.id==`move_end`){let[hours,minutes]=e.value.split(`:`).map(Number);ge(`in_3`).value=hours*3600+minutes*60,markChanged(ge(`in_3`))}e.id==`drive_dist`&&(ge(`in_4`).value=e.value,markChanged(ge(`in_4`))),e.id==`jack_dist`&&(ge(`in_5`).value=e.value,markChanged(ge(`in_5`)))}function setTimeToNow(){e=ge(`in_time`),markChanged(e),e.value=(/* @__PURE__ */ new Date()).toLocaleString(`sv-SE`)}function commit_time(){let xhr=new XMLHttpRequest,[datePart,timePart]=ge(`in_time`).value.split(`T`),[year,month,day]=datePart.split(`-`).map(Number),[hour,minute,second=0]=timePart.split(`:`).map(Number),epoch=Math.floor(Date.UTC(year,month-1,day,hour,minute,second)/1e3);xhr.open(`POST`,`./st`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.onload=function(){xhr.status===200&&ge(`in_time`).classList.remove(`changed`),document.querySelectorAll(`input.changed`).length===0?ge(`commit_btn`).disabled=!0:ge(`commit_btn`).disabled=!1},xhr.send(epoch.toString())}
|
||||
// --- 2. POST DATA ---
|
||||
function commit_params(){ge(`commit_btn`).disabled=!0,document.querySelectorAll(`input.changed`).forEach(input=>{let xhr=new XMLHttpRequest;if(input.id===`in_time`){
|
||||
// Time handling
|
||||
let epoch=Math.floor(new Date(input.value).getTime()/1e3);xhr.open(`POST`,`http://192.168.4.1/st`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.send(JSON.stringify({time:epoch})),input.classList.remove(`changed`)}else{
|
||||
function commit_params(){ge(`commit_btn`).disabled=!0;let changedInputs=document.querySelectorAll(`input.changed`);if(sp={},changedInputs.forEach(input=>{if(input.id===`in_time`)commit_time();else if(input.id.startsWith(`in_`)){
|
||||
// Parameter handling
|
||||
let id=input.id.split(`_`)[1],val=input.value.toLowerCase()===`null`?null:parseFloat(input.value);xhr.open(`POST`,`/sp`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.onload=function(){xhr.status===200&&input.classList.remove(`changed`)},xhr.send(JSON.stringify({id:parseInt(id),value:val}))}})}function uploadFirmware(){let fileInput=ge(`firmware_file`);if(!fileInput.files.length){alert(`No file selected`);return}let file=fileInput.files[0],xhr=new XMLHttpRequest;xhr.open(`POST`,`http://192.168.4.1/ota`,!0),xhr.setRequestHeader(`Content-Type`,`application/octet-stream`),xhr.onload=function(){xhr.status===200?alert(`Upload successful. Device may reboot.`):alert(`Upload failed: `+xhr.status)},xhr.onerror=function(){alert(`Network error during upload`)},xhr.send(file)}async function downloadLogFile(){try{let response=await fetch(`/log`);if(!response.ok)throw Error(`Network response was not ok`);let blob=await response.blob(),now=/* @__PURE__ */ new Date,formattedDate=`${String(now.getDate()).padStart(2,`0`)}${[`JAN`,`FEB`,`MAR`,`APR`,`MAY`,`JUN`,`JUL`,`AUG`,`SEP`,`OCT`,`NOV`,`DEC`][now.getMonth()]}${now.getFullYear()}-${String(now.getHours()).padStart(2,`0`)}${String(now.getMinutes()).padStart(2,`0`)}`,url=URL.createObjectURL(blob),a=document.createElement(`a`);a.href=url,a.download=`storage-${formattedDate}.bin`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(url)}catch(error){console.error(`Download failed:`,error)}}
|
||||
let id=input.id.split(`_`)[1],val=input.value.toLowerCase()===`null`?null:parseFloat(input.value);sp[id]=val}}),Object.keys(sp).length!==0){let xhr2=new XMLHttpRequest;xhr2.open(`POST`,`./sp`,!0),xhr2.setRequestHeader(`Content-Type`,`application/json`),xhr2.onload=function(){xhr2.status===200?changedInputs.forEach(input=>{input.id!==`in_time`&&input.classList.remove(`changed`)}):ge(`commit_btn`).disabled=!1},xhr2.send(JSON.stringify(sp))}fetchStatus()}function uploadFirmware(){let fileInput=ge(`firmware_file`);if(!fileInput.files.length){alert(`No file selected`);return}let file=fileInput.files[0],xhr=new XMLHttpRequest;xhr.open(`POST`,`./ota`,!0),xhr.setRequestHeader(`Content-Type`,`application/octet-stream`),xhr.onload=function(){xhr.status===200?alert(`Upload successful. Device may reboot.`):alert(`Upload failed: `+xhr.status)},xhr.onerror=function(){alert(`Network error during upload`)},xhr.send(file)}function programRF(i){let xhr=new XMLHttpRequest;xhr.open(`POST`,`./prf`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.send(i.toString()),xhr.status}async function downloadLogFile(){try{let response=await fetch(`./log`);if(!response.ok)throw Error(`Network response was not ok`);let blob=await response.blob(),now=/* @__PURE__ */ new Date,formattedDate=`${String(now.getDate()).padStart(2,`0`)}${[`JAN`,`FEB`,`MAR`,`APR`,`MAY`,`JUN`,`JUL`,`AUG`,`SEP`,`OCT`,`NOV`,`DEC`][now.getMonth()]}${now.getFullYear()}-${String(now.getHours()).padStart(2,`0`)}${String(now.getMinutes()).padStart(2,`0`)}`,url=URL.createObjectURL(blob),a=document.createElement(`a`);a.href=url,a.download=`storage-${formattedDate}.bin`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(url)}catch(error){console.error(`Download failed:`,error)}}
|
||||
// Initial Load
|
||||
window.onload=fetchStatus;</script></body></html>
|
||||
412
main/webserver.c
412
main/webserver.c
@@ -8,9 +8,12 @@
|
||||
*/
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "control_fsm.h"
|
||||
#include "endian.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_timer.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "rf_433.h"
|
||||
#include "rtc.h"
|
||||
#include "string.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -56,75 +59,127 @@ static esp_err_t root_get_handler(httpd_req_t *req) {
|
||||
return httpd_resp_send(req, (const char *)html_content, html_content_len);
|
||||
}
|
||||
|
||||
static esp_err_t log_get_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "log_get_handler");
|
||||
static esp_err_t log_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "log_handler");
|
||||
|
||||
|
||||
int32_t tail = -1;
|
||||
|
||||
if (req -> method == HTTP_GET) {
|
||||
// give the whole log
|
||||
}
|
||||
if (req -> method == HTTP_POST) {
|
||||
int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
httpBuffer[ret] = '\0'; // Null-terminate the string
|
||||
|
||||
ESP_LOGI(TAG, "ST POST %.*s", ret, httpBuffer);
|
||||
|
||||
if(sscanf(httpBuffer, "%ld", (long*)&tail) != 1) {
|
||||
// if malformed, just send the whole log.
|
||||
//httpd_resp_send_err(req, 400, "INVALID TAIL POINTER");
|
||||
}
|
||||
}
|
||||
|
||||
const esp_partition_t *storage_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||
if (storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Storage partition not found");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Storage partition not found");
|
||||
}
|
||||
|
||||
int32_t start = get_log_tail();
|
||||
int32_t end = get_log_head();
|
||||
int32_t total_size = end - start;
|
||||
int32_t offset = start;
|
||||
if (start >= end) {
|
||||
total_size = storage_partition->size - start + end - get_log_offset();
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "start/end: %ld/%ld -> %ld", (long)start, (long)end, (long)total_size);
|
||||
|
||||
//size_t total_size = storage_partition->size;
|
||||
// Figure out the bounds of data
|
||||
if (tail < 0)
|
||||
tail = get_log_tail();
|
||||
int32_t head = get_log_head();
|
||||
int32_t total_size = head - tail + 8; // 8 bytes for the head/tail pointers
|
||||
int32_t offset = tail;
|
||||
int32_t sent = 0;
|
||||
|
||||
if (tail >= head) {
|
||||
total_size = storage_partition->size - tail + head - get_log_offset() + 8;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "start/end: %ld/%ld -> %ld", (long)tail, (long)head, (long)total_size);
|
||||
|
||||
// Send header
|
||||
char len_str[16];
|
||||
sprintf(len_str, "%u", (unsigned)total_size);
|
||||
httpd_resp_set_type(req, "application/octet-stream");
|
||||
httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=\"sc_storage.bin\"");
|
||||
httpd_resp_set_hdr(req, "Content-Length", len_str);
|
||||
|
||||
|
||||
int32_t htail = htobe32(tail);
|
||||
int32_t hhead = htobe32(head);
|
||||
// Send head/tail pointers
|
||||
memcpy(&httpBuffer[0], &(htail), 4);
|
||||
memcpy(&httpBuffer[4], &(hhead), 4);
|
||||
if (httpd_resp_send_chunk(req, (const char *)httpBuffer, 8) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send chunk");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
sent += 8;
|
||||
|
||||
if (start >= end) {
|
||||
ESP_LOGI(TAG, "STARTING");
|
||||
while (offset < storage_partition->size) {
|
||||
// if wrapped around, just go from the start all the way to the end of storage
|
||||
// then set start = get_log_offset();
|
||||
// and continue
|
||||
size_t to_read = MIN(sizeof(httpBuffer), storage_partition->size - offset);
|
||||
if (tail != head) {
|
||||
// Send data between tail and end (if wrapped)
|
||||
if (tail >= head) {
|
||||
ESP_LOGI(TAG, "STARTING wrapped section (tail=%ld, head=%ld)", (long)tail, (long)head);
|
||||
while (offset < storage_partition->size) {
|
||||
// FIXED: Don't limit by head in this section - read to end of partition
|
||||
size_t to_read = MIN(sizeof(httpBuffer), storage_partition->size - offset);
|
||||
esp_err_t err = esp_partition_read(storage_partition, offset, httpBuffer, to_read);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read partition: %s", esp_err_to_name(err));
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read storage");
|
||||
}
|
||||
ESP_LOGI(TAG, "Sending wrapped chunk: offset=%ld size=%d", (long)offset, to_read);
|
||||
if (httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send chunk");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
sent += to_read;
|
||||
offset += to_read;
|
||||
}
|
||||
// Loop back to the beginning
|
||||
offset = get_log_offset();
|
||||
ESP_LOGI(TAG, "Wrapped to beginning, offset now=%ld", (long)offset);
|
||||
}
|
||||
|
||||
// Send data between start (or tail) and head
|
||||
ESP_LOGI(TAG, "FINISHING final section (offset=%ld, head=%ld)", (long)offset, (long)head);
|
||||
while (offset < head) {
|
||||
size_t to_read = MIN(sizeof(httpBuffer), head - offset);
|
||||
esp_err_t err = esp_partition_read(storage_partition, offset, httpBuffer, to_read);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read partition: %s", esp_err_to_name(err));
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read storage");
|
||||
}
|
||||
ESP_LOGI(TAG, "Sending %ld x %d", (long) offset, to_read);
|
||||
ESP_LOGI(TAG, "Sending final chunk: offset=%ld size=%d", (long)offset, to_read);
|
||||
if (httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send chunk");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
sent += to_read;
|
||||
offset += to_read;
|
||||
}
|
||||
offset = get_log_offset();
|
||||
}
|
||||
}
|
||||
|
||||
while (offset < end) {
|
||||
ESP_LOGI(TAG, "FINISHING");
|
||||
// if wrapped around, just go from the start all the way to the end of storage
|
||||
// then set start = get_log_offset();
|
||||
// and continue
|
||||
size_t to_read = MIN(sizeof(httpBuffer), end - offset);
|
||||
esp_err_t err = esp_partition_read(storage_partition, offset, httpBuffer, to_read);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read partition: %s", esp_err_to_name(err));
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read storage");
|
||||
}
|
||||
ESP_LOGI(TAG, "Sending %ld x %d", (long) offset, to_read);
|
||||
if (httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send chunk");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
offset += to_read;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Transfer complete: sent %ld bytes (expected %ld)", (long)sent, (long)total_size);
|
||||
|
||||
|
||||
}
|
||||
|
||||
// End chunked transfer
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
if (httpd_resp_send_chunk(req, NULL, 0) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send final empty chunk");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Final empty chunk sent successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -133,25 +188,33 @@ static esp_err_t st_post_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "st_post_handler");
|
||||
// Send the HTML response
|
||||
|
||||
int ret=0;
|
||||
int remaining = req -> content_len;
|
||||
while (remaining > 0) {
|
||||
if ((ret = httpd_req_recv(req, httpBuffer, MIN(remaining, sizeof(httpBuffer))))<= 0) {
|
||||
if(ret == HTTPD_SOCK_ERR_TIMEOUT){
|
||||
continue;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
}
|
||||
int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
httpBuffer[ret] = '\0'; // Null-terminate the string
|
||||
|
||||
ESP_LOGI(TAG, "ST POST %.*s", ret, httpBuffer);
|
||||
ESP_LOGI(TAG, "ST POST %.*s", ret, httpBuffer);
|
||||
int64_t tv = -1;
|
||||
|
||||
/*if(sscanf(httpBuffer, "%d-%d-%dT%d:%d", &tv) == 1) {
|
||||
system_rtc_set_raw_time(tv);
|
||||
}*/
|
||||
if(sscanf(httpBuffer, "%lld", &tv) == 1) {
|
||||
system_rtc_set_raw_time(tv);
|
||||
}
|
||||
|
||||
|
||||
return httpd_resp_send(req, "200 OK", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
// set parameters id & value
|
||||
// set parameters - accepts multiple parameters with mixed key types
|
||||
static esp_err_t sp_post_handler(httpd_req_t *req) {
|
||||
char content[128]; // Buffer for incoming JSON
|
||||
char content[512]; // Increased buffer for multiple parameters
|
||||
size_t recv_size = (req->content_len < sizeof(content)) ? req->content_len : sizeof(content) - 1;
|
||||
|
||||
// 1. Receive the data
|
||||
@@ -172,92 +235,156 @@ static esp_err_t sp_post_handler(httpd_req_t *req) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 3. Extract the ID and Value
|
||||
cJSON *id_item = cJSON_GetObjectItem(root, "id");
|
||||
cJSON *val_item = cJSON_GetObjectItem(root, "value");
|
||||
int params_updated = 0;
|
||||
int params_failed = 0;
|
||||
|
||||
int param_id = -1;
|
||||
//char param_idstring[32] = "";
|
||||
|
||||
if (cJSON_IsNumber(id_item)) {
|
||||
param_id = id_item->valueint;
|
||||
}
|
||||
if (cJSON_IsString(id_item)){
|
||||
//param_idstring = id_item->valuestring;
|
||||
for (uint8_t i=0; i<NUM_PARAMS; i++) {
|
||||
if (strcmp(id_item->valuestring, get_param_name(i))) {
|
||||
param_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
double param_val = cJSON_IsNumber(val_item) ? val_item->valuedouble : 0.0f;
|
||||
|
||||
ESP_LOGI(TAG, "Updating Param ID: %d to Value: %.2f", param_id, param_val);
|
||||
// 3. Iterate through all items in the JSON object
|
||||
cJSON *item = NULL;
|
||||
cJSON_ArrayForEach(item, root) {
|
||||
int param_id = -1;
|
||||
const char *key = item->string;
|
||||
|
||||
switch(get_param_type(param_id)) {
|
||||
case PARAM_TYPE_u8:
|
||||
set_param_value_t(param_id, (param_value_t){.u8 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i8:
|
||||
set_param_value_t(param_id, (param_value_t){.i8 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
set_param_value_t(param_id, (param_value_t){.u16 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
set_param_value_t(param_id, (param_value_t){.i16 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
set_param_value_t(param_id, (param_value_t){.u32 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
set_param_value_t(param_id, (param_value_t){.i32 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u64:
|
||||
set_param_value_t(param_id, (param_value_t){.u64 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i64:
|
||||
set_param_value_t(param_id, (param_value_t){.i64 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_f32:
|
||||
set_param_value_t(param_id, (param_value_t){.f32 = param_val});
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
set_param_value_t(param_id, (param_value_t){.f64 = param_val});
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown parameter type for ID %d", param_id);
|
||||
break;
|
||||
}
|
||||
if (key == NULL) {
|
||||
ESP_LOGW(TAG, "Skipping item with null key");
|
||||
params_failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to parse key as a parameter ID (numeric string like "4")
|
||||
char *endptr;
|
||||
long parsed_id = strtol(key, &endptr, 10);
|
||||
if (*endptr == '\0' && parsed_id >= 0 && parsed_id < NUM_PARAMS) {
|
||||
// Key is a valid numeric string
|
||||
param_id = (int)parsed_id;
|
||||
} else {
|
||||
// Key is a string name, search for matching parameter
|
||||
for (uint8_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (strcmp(key, get_param_name(i)) == 0) {
|
||||
param_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we found a valid parameter
|
||||
if (param_id < 0 || param_id >= NUM_PARAMS) {
|
||||
ESP_LOGW(TAG, "Unknown parameter key: %s", key);
|
||||
params_failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the value
|
||||
if (!cJSON_IsNumber(item)) {
|
||||
ESP_LOGW(TAG, "Parameter %s has non-numeric value", key);
|
||||
params_failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
double param_val = item->valuedouble;
|
||||
|
||||
ESP_LOGI(TAG, "Updating Param '%s' (ID: %d) to Value: %.2f", key, param_id, param_val);
|
||||
|
||||
// Set the parameter based on its type
|
||||
switch(get_param_type(param_id)) {
|
||||
case PARAM_TYPE_u8:
|
||||
set_param_value_t(param_id, (param_value_t){.u8 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i8:
|
||||
set_param_value_t(param_id, (param_value_t){.i8 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
set_param_value_t(param_id, (param_value_t){.u16 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
set_param_value_t(param_id, (param_value_t){.i16 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
set_param_value_t(param_id, (param_value_t){.u32 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
set_param_value_t(param_id, (param_value_t){.i32 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_u64:
|
||||
set_param_value_t(param_id, (param_value_t){.u64 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_i64:
|
||||
set_param_value_t(param_id, (param_value_t){.i64 = round(param_val)});
|
||||
break;
|
||||
case PARAM_TYPE_f32:
|
||||
set_param_value_t(param_id, (param_value_t){.f32 = param_val});
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
set_param_value_t(param_id, (param_value_t){.f64 = param_val});
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown parameter type for ID %d", param_id);
|
||||
params_failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
params_updated++;
|
||||
}
|
||||
|
||||
commit_params();
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
// 5. Send Success Response
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
return httpd_resp_send(req, "{\"status\":\"ok\"}", HTTPD_RESP_USE_STRLEN);
|
||||
return httpd_resp_send(req, "200 OK", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static esp_err_t move_post_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "move_post_handler");
|
||||
static esp_err_t cmd_post_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "cmd_post_handler");
|
||||
// Send the HTML response
|
||||
int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
httpBuffer[ret] = '\0'; // Null-terminate the string
|
||||
|
||||
cJSON *root = cJSON_Parse(httpBuffer);
|
||||
if (root == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to parse JSON: %s", httpBuffer);
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
||||
|
||||
if (!cJSON_IsString(cmd) || cmd->valuestring == NULL)
|
||||
return httpd_resp_send(req, "400 Bad Request", HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
if (strcmp(cmd->valuestring, "stop") == 0) {
|
||||
fsm_request(FSM_CMD_STOP);
|
||||
ESP_LOGI(TAG, "FSM_CMD_STOP");
|
||||
} else if (strcmp(cmd->valuestring, "undo") == 0) {
|
||||
fsm_request(FSM_CMD_UNDO);
|
||||
ESP_LOGI(TAG, "FSM_CMD_UNDO");
|
||||
} else if (strcmp(cmd->valuestring, "start") == 0) {
|
||||
fsm_request(FSM_CMD_START);
|
||||
ESP_LOGI(TAG, "FSM_CMD_START");
|
||||
} else if (strcmp(cmd->valuestring, "rfp") == 0) {
|
||||
cJSON *i = cJSON_GetObjectItem(root, "channel");
|
||||
if (cJSON_IsNumber(i) && i->valueint >= 0 && i->valueint < 8) {
|
||||
rf_433_learn_keycode(i->valueint);
|
||||
} else {
|
||||
rf_433_cancel_learn_keycode();
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Command not valid: %s", httpBuffer);
|
||||
return httpd_resp_send(req, "400 Bad Request", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
return httpd_resp_send(req, "/move NOT IMPLEMENTED", HTTPD_RESP_USE_STRLEN);
|
||||
return httpd_resp_send(req, "200 OK", HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static esp_err_t stop_post_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "stop_post_handler");
|
||||
// Send the HTML response
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
return httpd_resp_send(req, "/stop NOT IMPLEMENTED", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
|
||||
/* Handler for Status GET request*/
|
||||
static esp_err_t status_get_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "status_get_handler");
|
||||
@@ -267,7 +394,11 @@ static esp_err_t status_get_handler(httpd_req_t *req) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
|
||||
// Start building the JSON string with time
|
||||
head += sprintf(httpBuffer+head, "{\"time\":%lld,\" battery\":%f,\" values\":[", system_rtc_get_raw_time(), get_battery_V());
|
||||
head += sprintf(httpBuffer+head, "{\"time\":%lld,\"battery\":%f,\"rtc_set\":%s,\"values\":[",
|
||||
system_rtc_get_raw_time(),
|
||||
get_battery_V(),
|
||||
rtc_is_set()?"true":"false"
|
||||
);
|
||||
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (i > 0) {
|
||||
@@ -299,6 +430,14 @@ static esp_err_t status_get_handler(httpd_req_t *req) {
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "\"%s\"", get_param_name(i));
|
||||
}
|
||||
|
||||
head += sprintf(httpBuffer+head, "], \"units\":[");
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (i > 0) {
|
||||
head += sprintf(httpBuffer+head, ",");
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "\"%s\"", get_param_unit(i));
|
||||
}
|
||||
|
||||
// Close the JSON array and object
|
||||
head += sprintf(httpBuffer+head, "]}");
|
||||
@@ -390,8 +529,8 @@ httpd_uri_t uris[] = {{
|
||||
.user_ctx = NULL
|
||||
},{
|
||||
.uri = "/log",
|
||||
.method = HTTP_GET,
|
||||
.handler = log_get_handler,
|
||||
.method = HTTP_ANY,
|
||||
.handler = log_handler,
|
||||
.user_ctx = NULL
|
||||
},{
|
||||
.uri = "/sp",
|
||||
@@ -399,14 +538,9 @@ httpd_uri_t uris[] = {{
|
||||
.handler = sp_post_handler,
|
||||
.user_ctx = NULL
|
||||
},{
|
||||
.uri = "/move",
|
||||
.uri = "/cmd",
|
||||
.method = HTTP_POST,
|
||||
.handler = move_post_handler,
|
||||
.user_ctx = NULL
|
||||
},{
|
||||
.uri = "/stop",
|
||||
.method = HTTP_POST,
|
||||
.handler = stop_post_handler,
|
||||
.handler = cmd_post_handler,
|
||||
.user_ctx = NULL
|
||||
},{
|
||||
.uri = "/ota",
|
||||
|
||||
Reference in New Issue
Block a user