ota updates

This commit is contained in:
Dan Priece
2026-05-28 13:59:56 -04:00
parent 2c5f532717
commit 51dbd5e5b0
5 changed files with 105 additions and 105 deletions
+18 -28
View File
@@ -1,6 +1,10 @@
#include "ChronoBLE.h" #include "ChronoBLE.h"
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
// Flag to signal that a new reading is ready to be processed
volatile bool newReadingAvailable = false;
volatile int lastReadFPS = 0;
static NimBLEClient* _pClient = nullptr; static NimBLEClient* _pClient = nullptr;
static ChronoBLE::ConnectionState currentState; static ChronoBLE::ConnectionState currentState;
static const NimBLEAdvertisedDevice* advDevice = nullptr; static const NimBLEAdvertisedDevice* advDevice = nullptr;
@@ -61,39 +65,25 @@ class ScanCallbacks : public NimBLEScanCallbacks {
//notification callback //notification callback
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
uint16_t speed; uint16_t speed;
char sbuffer[256];
std::string str = (isNotify == true) ? "Notification" : "Indication";
str += " from ";
str += pRemoteCharacteristic->getClient()->getPeerAddress().toString();
str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
str += ", Value = " + std::string((char*)pData, length);
// Serial.printf("%s\n", str.c_str());
// Serial.printf("%d\n",pData);
//if(length>0){ // Extract speed from data - only what's needed, avoid String allocations
speed = ((char*)pData)[0]; if (length >= 2) {
speed = pData[0];
speed <<= 8; speed <<= 8;
speed |= ((char*)pData)[1]; speed |= pData[1];
if (speed>0){ if (speed > 0) {
float energy; // Convert to FPS
float fspeed = speed; float fspeed = speed * 0.0475111859;
/* Draw the speed string */
//if(units == UNITS_IMPERIAL) { // Store the reading for later processing in main loop
fspeed *= 0.0475111859; // This avoids heap fragmentation and display updates in interrupt context
//sprintf (sbuffer, "%d FPS", int(fspeed)); lastReadFPS = (int)fspeed;
Serial.printf("%d FPS\n", int(fspeed)); newReadingAvailable = true;
//} else {
//fspeed *= 0.014481409; Serial.printf("%d FPS\n", (int)fspeed);
//sprintf (sbuffer, "%d M/S", int(fspeed));
//}
//}
if (speedCallback!= nullptr){
speedCallback(fspeed);
} }
} }
} }
+4
View File
@@ -5,6 +5,10 @@
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
#include <GunProfile.h> #include <GunProfile.h>
// Flag to signal that a new reading is ready to be processed
extern volatile bool newReadingAvailable;
extern volatile int lastReadFPS;
typedef void (*CallbackFunction)(); typedef void (*CallbackFunction)();
class ChronoBLE { class ChronoBLE {
+17 -5
View File
@@ -1,5 +1,10 @@
#include "TFT_eSPI.h" #include "TFT_eSPI.h"
#include "ChronoBLE.h" #include "ChronoBLE.h"
#include "ChronoVersion.h"
// External declaration for BLE reading flag and value
extern volatile bool newReadingAvailable;
extern volatile int lastReadFPS;
#include "ChronoReading.h" #include "ChronoReading.h"
#include <LinkedList.h> #include <LinkedList.h>
#include "Display.h" #include "Display.h"
@@ -89,8 +94,9 @@ void setup()
chronoBLE.setSpeedCallback(speedCallback); chronoBLE.setSpeedCallback(speedCallback);
} }
void setDateTime(){ void setDateTime(){
// NTP server configuration // NTP server configuration - ESP32 API uses gmtOffset (seconds) and dstOffset (seconds)
configTime("EST5EDT,M3.2.0/2,M11.1.0/2", "pool.ntp.org", "time.nist.gov"); // EST = UTC-5, EDT = UTC-4, so gmtOffset = -5*3600 = -18000
configTime(-18000, 3600, "pool.ntp.org", "time.nist.gov");
// Wait for time to be set // Wait for time to be set
time_t now; time_t now;
@@ -134,9 +140,7 @@ void updateTime(){
char strftime_buf[64]; char strftime_buf[64];
struct tm timeinfo; struct tm timeinfo;
time(&now); time(&now);
const char* TZ_EST = "EST5EDT,M3.2.0/2,M11.1.0/2"; // Use the timezone that was configured via configTime
setenv("TZ", TZ_EST, 1);
tzset();
localtime_r(&now, &timeinfo); localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%R", &timeinfo); strftime(strftime_buf, sizeof(strftime_buf), "%R", &timeinfo);
@@ -191,6 +195,14 @@ void loop()
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// Check for new BLE readings and process them
// This is done here instead of in the BLE callback to avoid heap fragmentation
// and display updates in interrupt context
if (newReadingAvailable) {
newReadingAvailable = false;
addReading(lastReadFPS);
}
//update display //update display
Display::refreshDisplay(); Display::refreshDisplay();
+63 -71
View File
@@ -5,6 +5,7 @@
#include <FileManager.h> #include <FileManager.h>
#include <ChronoProfile.h> #include <ChronoProfile.h>
#include <GunProfile.h> #include <GunProfile.h>
#include <Update.h>
#include "Display.h" #include "Display.h"
const char *ssid = "routeguy"; const char *ssid = "routeguy";
@@ -73,6 +74,7 @@ void ChronoWebServer::_handleRoot() {
.then(data => console.log(data))\ .then(data => console.log(data))\
.catch(error => console.error('Error:', error));\ .catch(error => console.error('Error:', error));\
}\ }\
fetch('/version').then(r=>r.json()).then(v=>{document.getElementById('currentVersion').textContent=v.version}).catch(()=>{});\
function doOta(){\ function doOta(){\
const fileInput = document.getElementById('otaFile');\ const fileInput = document.getElementById('otaFile');\
if (!fileInput.files.length) {\ if (!fileInput.files.length) {\
@@ -81,17 +83,21 @@ void ChronoWebServer::_handleRoot() {
}\ }\
const formData = new FormData();\ const formData = new FormData();\
formData.append('otaFile', fileInput.files[0]);\ formData.append('otaFile', fileInput.files[0]);\
fetch('/ota', {\ const xhr = new XMLHttpRequest();\
method: 'POST',\ xhr.open('POST', '/ota', true);\
body: formData\ xhr.onload = function () {\
})\ if (xhr.status >= 200 && xhr.status < 300) {\
.then(response => response.text())\ const responseData = JSON.parse(xhr.responseText);\
.then(data => {\ console.log('Success:', responseData);\
console.log(data);\ window.location.href = '/';\
alert('OTA update in progress... Device will restart.');\ } else {\
window.location.href = '/';\ console.error('Server error:', xhr.status);\
})\ }\
.catch(error => console.error('Error:', error));\ };\
xhr.onerror = function () {\
console.error('Network request failed');\
};\
xhr.send(formData);\
}\ }\
document.addEventListener('DOMContentLoaded', function() {\ document.addEventListener('DOMContentLoaded', function() {\
const rows = document.querySelectorAll('.clickable-row');\ const rows = document.querySelectorAll('.clickable-row');\
@@ -134,12 +140,14 @@ void ChronoWebServer::_handleRoot() {
<button onclick='reset()'>Reset</button>\ <button onclick='reset()'>Reset</button>\
<button onclick='format()'>Format</button>\ <button onclick='format()'>Format</button>\
<button onclick='toggleTestMode()'>Toggle Test Mode</button>\ <button onclick='toggleTestMode()'>Toggle Test Mode</button>\
<button onclick='doOta()'>OTA Update</button>\
</div>\ </div>\
<div style='margin-top:20px; padding: 10px; border: 1px solid #999;'>\ <div style='margin-top:20px; padding: 10px; border: 1px solid #999;'>\
<h3>OTA Firmware Update</h3>\ <h3>OTA Firmware Update</h3>\
<input type='file' id='otaFile' accept='.bin,.bin'></input>\ <p style='font-size: 12px; color: #666;'>Current Version: <strong id='currentVersion'>Unknown</strong></p>\
<p style='font-size: 12px; color: #666;'>Select a .bin firmware file to update over-the-air</p>\ <input type='file' id='otaFile' accept='.bin,.bin' style='margin-bottom: 10px;'></input>\
<p style='font-size: 12px; color: #666;'>1. Select a .bin firmware file to update over-the-air</p>\
<button onclick='doOta()' style='margin-top: 5px; padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;'>OTA Upgrade</button>\
<p style='font-size: 12px; color: #666;'>2. Click OTA Upgrade to install</p>\
</div>\ </div>\
<div style='margin-top:100px; position:relative;'>\ <div style='margin-top:100px; position:relative;'>\
<h3>Gun Profiles</h3>\ <h3>Gun Profiles</h3>\
@@ -460,69 +468,46 @@ void ChronoWebServer::_handleTestMode(){
testModeCallback(); testModeCallback();
_server.send(200, "text/html", "ok"); _server.send(200, "text/html", "ok");
} }
void ChronoWebServer::_handleOta(){ void handleOtaUpdate() {
HTTPMethod method = _server.method(); size_t fsize = UPDATE_SIZE_UNKNOWN;
if (_server.hasArg("size")) {
if (method == HTTP_GET) { fsize = _server.arg("size").toInt();
// Serve OTA update page
String temp = "<html>\
<head>\
<title>OTA Firmware Update</title>\
<style>\
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
</style>\
</head>\
<body>\
<h1>OTA Firmware Update</h1>\
<p>Select a .bin firmware file to update over-the-air.</p>\
<form method='POST' action='/ota' enctype='multipart/form-data'>\
<div>\
<h3>Firmware File</h3>\
<input type='file' name='otaFile' accept='.bin'></input>\
</div>\
<div style='margin-top: 20px;'>\
<button type='submit'>Upload</button>\
</div>\
</form>\
</body>\
</html>";
_server.send(200, "text/html", temp);
} }
else if (method == HTTP_POST) { HTTPUpload &upload = _server.upload();
// Handle firmware upload if (upload.status == UPLOAD_FILE_START) {
HTTPUpload& upload = _server.upload();
if (upload.status == UPLOAD_FILE_START) { Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
Serial.println("OTA: Starting update..."); if (!Update.begin(fsize)) {
// Initialize update with the size of the uploaded file //otaDone = 0;
if (!Update.begin(upload.totalSize)) { //Update.printError(Serial);
Serial.println("OTA: Error: Not enough space");
_server.send(500, "text/plain", "Not enough space");
return;
}
} }
else if (upload.status == UPLOAD_FILE_WRITE) { } else if (upload.status == UPLOAD_FILE_WRITE) {
// Write received firmware to flash if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { // Update.printError(Serial);
Serial.println("OTA: Error: Write failed"); } else {
_server.send(500, "text/plain", "Write failed"); // otaDone = 100 * Update.progress() / Update.size();
return;
}
} }
else if (upload.status == UPLOAD_FILE_END) { } else if (upload.status == UPLOAD_FILE_END) {
// End of upload if (Update.end(true)) {
if (Update.end(true)) { Serial.printf("Update Success: %u bytes\nRebooting...\n", upload.totalSize);
Serial.printf("OTA: Update complete: %u bytes\n", upload.totalSize); } else {
_server.send(200, "text/html", "<h1>OTA Update Successful!<br>Restarting...</h1>"); Serial.printf("%s\n", Update.errorString());
delay(1000); // otaDone = 0;
ESP.restart();
} else {
Serial.println("OTA: Error: Update end failed");
_server.send(500, "text/plain", "Update failed");
}
} }
} }
} }
void handleOtaUpdateEnd() {
_server.sendHeader("Connection", "close");
if (Update.hasError()) {
_server.send(502, "text/plain", Update.errorString());
} else {
_server.sendHeader("Refresh", "10");
_server.sendHeader("Location", "/");
_server.send(307);
delay(500);
ESP.restart();
}
}
void ChronoWebServer::init(){ void ChronoWebServer::init(){
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
@@ -552,10 +537,17 @@ void ChronoWebServer::init(){
_server.on("/reset", _handleReset); _server.on("/reset", _handleReset);
_server.on("/format", _handleFormat); _server.on("/format", _handleFormat);
_server.on("/testmode", _handleTestMode); _server.on("/testmode", _handleTestMode);
_server.on("/ota", _handleOta); _server.on("/ota", HTTP_POST, [](){handleOtaUpdateEnd();}, [](){handleOtaUpdate();});
_server.on("/version", HTTP_GET, _handleVersion);
_server.on("/", _handleRoot); _server.on("/", _handleRoot);
_server.begin(); _server.begin();
} }
void ChronoWebServer::process(){ void ChronoWebServer::process(){
_server.handleClient(); _server.handleClient();
} }
void ChronoWebServer::_handleVersion() {
String versionJson = String("{\"version\":\"") + CHRONO_VERSION + "\",\"major\":" + String(CHRONO_VERSION_MAJOR) + ",\"minor\":" + String(CHRONO_VERSION_MINOR) + ",\"patch\":" + String(CHRONO_VERSION_PATCH) + "}";
_server.send(200, "application/json", versionJson);
}
+2
View File
@@ -8,6 +8,7 @@
#include <Update.h> #include <Update.h>
#include <ChronoBLE.h> #include <ChronoBLE.h>
#include <GunProfile.h> #include <GunProfile.h>
#include "ChronoVersion.h"
class ChronoWebServer { class ChronoWebServer {
public: public:
@@ -34,6 +35,7 @@ class ChronoWebServer {
static void _handleFormat(); static void _handleFormat();
static void _handleTestMode(); static void _handleTestMode();
static void _handleOta(); static void _handleOta();
static void _handleVersion();
}; };