OTA works, log download works, integrated OTA build sys

This commit is contained in:
Thaddeus Hughes
2025-12-27 11:52:57 -06:00
parent 81a8da24a0
commit 039c29a39d
12 changed files with 219 additions and 112 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/SC-F001/ota_deploy.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/SC-F001}"/>
</launchConfiguration>

View File

@@ -11,6 +11,16 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/OTA SC-F001.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>

View File

@@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(app-template)
project(SC-F001)

View File

@@ -50,7 +50,7 @@ void setRelay(int8_t relay, bool state) {
void driveRelays() {
uint8_t state = 0x00;
relay_states[0] = (current_time / 1000000) % 2;
//relay_states[0] = (current_time / 1000000) % 2; // for testing purposes
for (uint8_t i=0; i<8; i++) {
// if we command and efuse permits it set the relay
@@ -60,7 +60,7 @@ void driveRelays() {
}
}
ESP_LOGI(TAG, "RELAY STATE: %x", state);
//ESP_LOGI(TAG, "RELAY STATE: %x", state);
i2c_set_relays(state);
}

View File

@@ -22,11 +22,26 @@
<td><input type="datetime-local" id="in_time" step="1" onchange="markChanged(this)"/></td>
<td></td>
</tr>
<tr><td colspan="4"><button id="commit_btn" onclick="commit_params()" disabled>Save Changes</button></td></tr>
<tr><td colspan="4">
<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>
</tr>
<tr>
<td>Log File</td>
<td><button id="log_btn" onclick="downloadLogFile()">Download Log</button></td>
<td></td>
</tr>
</table>
<input type="file" id="firmware_file" accept=".bin">
<button id="upload_btn" onclick="uploadFirmware()">Upload Firmware</button>
<script>
let param_values = [];
@@ -152,6 +167,38 @@
xhr.send(file);
}
async function downloadLogFile() {
try {
const response = await fetch('/log');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const blob = await response.blob();
// Get current date and time
const now = new Date();
const day = String(now.getDate()).padStart(2, '0');
const monthNames = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
const month = monthNames[now.getMonth()];
const year = now.getFullYear();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const formattedDate = `${day}${month}${year}-${hours}${minutes}`;
const url = URL.createObjectURL(blob);
const 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>

View File

@@ -125,7 +125,7 @@ esp_err_t process_battery_voltage(void)
} else {
float alpha = get_param_value_t(PARAM_ADC_ALPHA_BATTERY).f32;
if (isnan(raw)) {
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
} else {
if (isnan(ema_battery) || isnan(alpha))
ema_battery = raw;
@@ -184,7 +184,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
} else {
float alpha = get_param_value_t(PARAM_ADC_ALPHA_ISENS).f32;
if (isnan(raw_a)) {
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
channel->ema_current = NAN;
} else {
if (isnan(ema_battery) || isnan(alpha))
@@ -206,7 +206,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
} else {
float alpha = get_param_value_t(PARAM_ADC_ALPHA_IAZ).f32;
if (isnan(raw_a)) {
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
} else {
if (isnan(ema_battery) || isnan(alpha))
channel->az_offset = channel->ema_current;
@@ -248,7 +248,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
if (I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) {
channel->tripped = true;
channel->trip_time = now;
ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
//ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
return ESP_OK; // no more processing, if we're over, we're over
}
@@ -280,8 +280,8 @@ esp_err_t process_bridge_current(bridge_t bridge) {
// channel.heat = 0.0f // I think we should wait for the e-fuse to catch up
}
if (bridge == BRIDGE_DRIVE)
ESP_LOGI(TAG, "FUSE: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
//if (bridge == BRIDGE_DRIVE)
// ESP_LOGI(TAG, "FUSE: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
return ESP_OK;
}

View File

@@ -37,9 +37,6 @@ typedef struct {
size_t num_symbols;
} rf_code_t;
// Global queue for passing decoded codes between tasks
static QueueHandle_t g_code_queue = NULL;
int learn_flag = -1;
// For rmt_rx_register_event_callbacks
@@ -130,7 +127,7 @@ static void rf_433_receiver_task(void* param) {
}
}
// If we got a valid code, send it to processing task
// If we got a valid code, process it
if (code) {
int64_t encoded = ((int64_t)len << 56) | code;
@@ -150,11 +147,6 @@ static void rf_433_receiver_task(void* param) {
.num_symbols = len
};
// Don't do this anymore. No need to pass data between threads. Just act on it.
// Non-blocking send - if queue is full, just drop it
//xQueueSend(g_code_queue, &rf_msg, 0);
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
int64_t match = get_param_value_t(PARAM_KEYCODE_0+i).i64;
if (encoded == match) {
@@ -167,8 +159,6 @@ static void rf_433_receiver_task(void* param) {
}
}
}
}
}
@@ -203,12 +193,11 @@ static void rf_433_receiver_task(void* param) {
}
// Start next receive
rmt_receive(rx_channel, symbols, sizeof(symbols), &rx_config);
}
esp_task_wdt_reset();
}
}
// Cleanup (never reached in this case)
rmt_disable(rx_channel);
@@ -217,13 +206,10 @@ static void rf_433_receiver_task(void* param) {
}
esp_err_t rf_433_init() {
g_code_queue = xQueueCreate(5, sizeof(rf_code_t));
assert(g_code_queue);
xTaskCreate(rf_433_receiver_task, TAG, 4096, NULL, 10, NULL);
return ESP_OK;
}
esp_err_t rf_433_stop() { return ESP_OK; }
void rf_433_set_keycode(uint8_t index, int64_t code) {
@@ -234,35 +220,7 @@ void rf_433_learn_keycode(uint8_t index) {
if (index >= 8) return;
learn_flag = index;
}
void rf_433_cancel_learn_keycode() {
learn_flag = -1;
}
/*int8_t rf_433_get_keycode() {
rf_code_t received_code;
if (xQueueReceive(g_code_queue, &received_code, 0) == pdPASS) {
int64_t newcode = ((int64_t)received_code.num_symbols << 56) | received_code.code;
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
if (newcode == get_param_value_t(PARAM_KEYCODE_0+i).i64)
return i;
}
ESP_LOGI("RF", "Received unknown code 0x%08lx (%d) [0x%16llx]", (unsigned long)received_code.code, received_code.num_symbols, (unsigned long long) newcode);
}
return -1;
}
int64_t rf_433_get_raw_keycode() {
rf_code_t received_code;
int64_t code = -1;
if (xQueueReceive(g_code_queue, &received_code, 0) == pdPASS) {
code = ((int64_t)received_code.num_symbols << 56) | received_code.code;
//ESP_LOGI("RF", "Raw Code 0x%08lx (%d) [0x%16llx]", (unsigned long)received_code.code, received_code.num_symbols, (unsigned long long) code);
}
return code;
}*/
void rf_433_clear_queue() {
xQueueReset(g_code_queue);
}

View File

@@ -64,38 +64,39 @@ typedef enum {
// ============================================================================
#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) /*3*/\
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) /*7*/\
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) /*11*/\
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) /*15*/\
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) /*19*/\
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) /*23*/\
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) /*27*/\
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) /*31*/\
PARAM_DEF(CHG_BULK_S, i64, 20) /*32*/\
PARAM_DEF(RF_PULSE_LENGTH, u64, 350000) \

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
<!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><input accept=.bin id=firmware_file type=file><button id=upload_btn onclick=uploadFirmware()>Upload Firmware</button><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{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)}
// Highlight changed inputs and enable the save button
function markChanged(el){el.classList.add(`changed`),ge(`commit_btn`).disabled=!1}
// --- 1. GET DATA ---
@@ -23,6 +23,6 @@ function commit_params(){ge(`commit_btn`).disabled=!0,document.querySelectorAll(
// 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{
// 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)}
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)}}
// Initial Load
window.onload=fetchStatus;</script></body></html>

View File

@@ -29,6 +29,8 @@
//#include "mdns.h"
#include "webpage.h"
#include "esp_partition.h"
#define HOSTNAME "sc.local"
#define SOFT_AP_SSID "stockcropper"
@@ -54,9 +56,39 @@ static esp_err_t root_get_handler(httpd_req_t *req) {
static esp_err_t log_get_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "log_get_handler");
// Send the HTML response
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, html_content, HTTPD_RESP_USE_STRLEN);
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");
}
size_t total_size = storage_partition->size;
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);
uint8_t buf[1024];
size_t offset = 0;
while (offset < total_size) {
size_t to_read = MIN(sizeof(buf), total_size - offset);
esp_err_t err = esp_partition_read(storage_partition, offset, buf, 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");
}
if (httpd_resp_send_chunk(req, (const char *)buf, to_read) != ESP_OK) {
ESP_LOGE(TAG, "Failed to send chunk");
return ESP_FAIL;
}
offset += to_read;
}
// End chunked transfer
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
// set time: timestamp (unix epoch, seconds)
@@ -203,6 +235,8 @@ static esp_err_t ota_post_handler(httpd_req_t *req) {
// Send response FIRST
httpd_resp_send(req, "OTA update successful, rebooting...", HTTPD_RESP_USE_STRLEN);
set_param_value_t(PARAM_BOOT_TIME, (param_value_t){.i64 = system_rtc_get_raw_time()});
// THEN delay and reboot
vTaskDelay(pdMS_TO_TICKS(2000)); // Give time for TCP to close properly
esp_restart();

49
ota_deploy.bat Normal file
View File

@@ -0,0 +1,49 @@
@echo off
REM ESP32 OTA Deployment Script for Windows
setlocal
REM Configuration
set ESP32_IP=192.168.4.1
set PROJECT_NAME=SC-F001
set BUILD_DIR=build
set BINARY_FILE=%BUILD_DIR%\%PROJECT_NAME%.bin
echo ========================================
echo ESP32 OTA Deployment Script
echo ========================================
REM Step 1: Check if binary exists
if not exist "%BINARY_FILE%" (
echo Error: Binary file not found at %BINARY_FILE%
echo Please update PROJECT_NAME in this script
exit /b 1
)
for %%A in ("%BINARY_FILE%") do set BINARY_SIZE=%%~zA
echo Binary size: %BINARY_SIZE% bytes
REM Step 2: Upload via OTA
echo.
echo [2/3] Uploading to ESP32 at %ESP32_IP%...
curl -X POST --data-binary @"%BINARY_FILE%" -w "HTTP Code: %%{http_code}\n" -o nul "http://%ESP32_IP%/ota"
if %ERRORLEVEL% NEQ 0 (
echo Upload failed! Is the ESP32 reachable at %ESP32_IP%?
exit /b 1
)
echo Upload successful!
echo ESP32 should be rebooting now...
REM Step 3: Wait for reboot
echo.
echo [3/3] Waiting for ESP32 to reboot...
timeout /t 5 /nobreak > nul
echo Deployment complete!
echo Check your device to verify it's running the new firmware.
exit /b 0