ota updates
This commit is contained in:
+18
-28
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|
||||||
|
|||||||
+60
-68
@@ -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);\
|
|
||||||
alert('OTA update in progress... Device will restart.');\
|
|
||||||
window.location.href = '/';\
|
window.location.href = '/';\
|
||||||
})\
|
} else {\
|
||||||
.catch(error => console.error('Error:', error));\
|
console.error('Server error:', xhr.status);\
|
||||||
|
}\
|
||||||
|
};\
|
||||||
|
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,67 +468,44 @@ 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
|
|
||||||
HTTPUpload& upload = _server.upload();
|
|
||||||
|
|
||||||
if (upload.status == UPLOAD_FILE_START) {
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
Serial.println("OTA: Starting update...");
|
|
||||||
// Initialize update with the size of the uploaded file
|
Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
|
||||||
if (!Update.begin(upload.totalSize)) {
|
if (!Update.begin(fsize)) {
|
||||||
Serial.println("OTA: Error: Not enough space");
|
//otaDone = 0;
|
||||||
_server.send(500, "text/plain", "Not enough space");
|
//Update.printError(Serial);
|
||||||
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) {
|
||||||
Serial.println("OTA: Error: Write failed");
|
// Update.printError(Serial);
|
||||||
_server.send(500, "text/plain", "Write failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (upload.status == UPLOAD_FILE_END) {
|
|
||||||
// End of upload
|
|
||||||
if (Update.end(true)) {
|
|
||||||
Serial.printf("OTA: Update complete: %u bytes\n", upload.totalSize);
|
|
||||||
_server.send(200, "text/html", "<h1>OTA Update Successful!<br>Restarting...</h1>");
|
|
||||||
delay(1000);
|
|
||||||
ESP.restart();
|
|
||||||
} else {
|
} else {
|
||||||
Serial.println("OTA: Error: Update end failed");
|
// otaDone = 100 * Update.progress() / Update.size();
|
||||||
_server.send(500, "text/plain", "Update failed");
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (Update.end(true)) {
|
||||||
|
Serial.printf("Update Success: %u bytes\nRebooting...\n", upload.totalSize);
|
||||||
|
} else {
|
||||||
|
Serial.printf("%s\n", Update.errorString());
|
||||||
|
// otaDone = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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(){
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user