diff --git a/ChronoBLE.cpp b/ChronoBLE.cpp index ec9b056..8800146 100644 --- a/ChronoBLE.cpp +++ b/ChronoBLE.cpp @@ -1,6 +1,10 @@ #include "ChronoBLE.h" #include +// 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 ChronoBLE::ConnectionState currentState; static const NimBLEAdvertisedDevice* advDevice = nullptr; @@ -61,39 +65,25 @@ class ScanCallbacks : public NimBLEScanCallbacks { //notification callback void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { 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){ - speed = ((char*)pData)[0]; + // Extract speed from data - only what's needed, avoid String allocations + if (length >= 2) { + speed = pData[0]; speed <<= 8; - speed |= ((char*)pData)[1]; + speed |= pData[1]; - if (speed>0){ - float energy; - float fspeed = speed; - /* Draw the speed string */ - //if(units == UNITS_IMPERIAL) { - fspeed *= 0.0475111859; - //sprintf (sbuffer, "%d FPS", int(fspeed)); - Serial.printf("%d FPS\n", int(fspeed)); - //} else { - //fspeed *= 0.014481409; - //sprintf (sbuffer, "%d M/S", int(fspeed)); - //} - //} - if (speedCallback!= nullptr){ - speedCallback(fspeed); + if (speed > 0) { + // Convert to FPS + float fspeed = speed * 0.0475111859; + + // Store the reading for later processing in main loop + // This avoids heap fragmentation and display updates in interrupt context + lastReadFPS = (int)fspeed; + newReadingAvailable = true; + + Serial.printf("%d FPS\n", (int)fspeed); } } - } diff --git a/ChronoBLE.h b/ChronoBLE.h index 9034f68..6a94530 100644 --- a/ChronoBLE.h +++ b/ChronoBLE.h @@ -5,6 +5,10 @@ #include #include +// Flag to signal that a new reading is ready to be processed +extern volatile bool newReadingAvailable; +extern volatile int lastReadFPS; + typedef void (*CallbackFunction)(); class ChronoBLE { diff --git a/ChronoDisplay.ino b/ChronoDisplay.ino index dd3828a..22ff5e7 100644 --- a/ChronoDisplay.ino +++ b/ChronoDisplay.ino @@ -1,5 +1,10 @@ #include "TFT_eSPI.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 #include "Display.h" @@ -89,8 +94,9 @@ void setup() chronoBLE.setSpeedCallback(speedCallback); } void setDateTime(){ - // NTP server configuration - configTime("EST5EDT,M3.2.0/2,M11.1.0/2", "pool.ntp.org", "time.nist.gov"); + // NTP server configuration - ESP32 API uses gmtOffset (seconds) and dstOffset (seconds) + // 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 time_t now; @@ -134,9 +140,7 @@ void updateTime(){ char strftime_buf[64]; struct tm timeinfo; time(&now); - const char* TZ_EST = "EST5EDT,M3.2.0/2,M11.1.0/2"; - setenv("TZ", TZ_EST, 1); - tzset(); + // Use the timezone that was configured via configTime localtime_r(&now, &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 Display::refreshDisplay(); diff --git a/ChronoWebServer.cpp b/ChronoWebServer.cpp index e9ded74..7f233ac 100644 --- a/ChronoWebServer.cpp +++ b/ChronoWebServer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Display.h" const char *ssid = "routeguy"; @@ -73,6 +74,7 @@ void ChronoWebServer::_handleRoot() { .then(data => console.log(data))\ .catch(error => console.error('Error:', error));\ }\ + fetch('/version').then(r=>r.json()).then(v=>{document.getElementById('currentVersion').textContent=v.version}).catch(()=>{});\ function doOta(){\ const fileInput = document.getElementById('otaFile');\ if (!fileInput.files.length) {\ @@ -81,17 +83,21 @@ void ChronoWebServer::_handleRoot() { }\ const formData = new FormData();\ formData.append('otaFile', fileInput.files[0]);\ - fetch('/ota', {\ - method: 'POST',\ - body: formData\ - })\ - .then(response => response.text())\ - .then(data => {\ - console.log(data);\ - alert('OTA update in progress... Device will restart.');\ - window.location.href = '/';\ - })\ - .catch(error => console.error('Error:', error));\ + const xhr = new XMLHttpRequest();\ + xhr.open('POST', '/ota', true);\ + xhr.onload = function () {\ + if (xhr.status >= 200 && xhr.status < 300) {\ + const responseData = JSON.parse(xhr.responseText);\ + console.log('Success:', responseData);\ + window.location.href = '/';\ + } else {\ + console.error('Server error:', xhr.status);\ + }\ + };\ + xhr.onerror = function () {\ + console.error('Network request failed');\ + };\ + xhr.send(formData);\ }\ document.addEventListener('DOMContentLoaded', function() {\ const rows = document.querySelectorAll('.clickable-row');\ @@ -134,12 +140,14 @@ void ChronoWebServer::_handleRoot() { \ \ \ - \ \
\

OTA Firmware Update

\ - \ -

Select a .bin firmware file to update over-the-air

\ +

Current Version: Unknown

\ + \ +

1. Select a .bin firmware file to update over-the-air

\ + \ +

2. Click OTA Upgrade to install

\
\
\

Gun Profiles

\ @@ -460,69 +468,46 @@ void ChronoWebServer::_handleTestMode(){ testModeCallback(); _server.send(200, "text/html", "ok"); } -void ChronoWebServer::_handleOta(){ - HTTPMethod method = _server.method(); - - if (method == HTTP_GET) { - // Serve OTA update page - String temp = "\ - \ - OTA Firmware Update\ - \ - \ - \ -

OTA Firmware Update

\ -

Select a .bin firmware file to update over-the-air.

\ -
\ -
\ -

Firmware File

\ - \ -
\ -
\ - \ -
\ -
\ - \ - "; - _server.send(200, "text/html", temp); +void handleOtaUpdate() { + size_t fsize = UPDATE_SIZE_UNKNOWN; + if (_server.hasArg("size")) { + fsize = _server.arg("size").toInt(); } - else if (method == HTTP_POST) { - // Handle firmware upload - HTTPUpload& upload = _server.upload(); - - if (upload.status == UPLOAD_FILE_START) { - Serial.println("OTA: Starting update..."); - // Initialize update with the size of the uploaded file - if (!Update.begin(upload.totalSize)) { - Serial.println("OTA: Error: Not enough space"); - _server.send(500, "text/plain", "Not enough space"); - return; - } + HTTPUpload &upload = _server.upload(); + if (upload.status == UPLOAD_FILE_START) { + + Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize); + if (!Update.begin(fsize)) { + //otaDone = 0; + //Update.printError(Serial); } - else if (upload.status == UPLOAD_FILE_WRITE) { - // Write received firmware to flash - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - Serial.println("OTA: Error: Write failed"); - _server.send(500, "text/plain", "Write failed"); - return; - } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + // Update.printError(Serial); + } else { + // otaDone = 100 * Update.progress() / Update.size(); } - 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", "

OTA Update Successful!
Restarting...

"); - delay(1000); - ESP.restart(); - } else { - Serial.println("OTA: Error: Update end failed"); - _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(){ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); @@ -552,10 +537,17 @@ void ChronoWebServer::init(){ _server.on("/reset", _handleReset); _server.on("/format", _handleFormat); _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.begin(); } + void ChronoWebServer::process(){ _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); } \ No newline at end of file diff --git a/ChronoWebServer.h b/ChronoWebServer.h index 3243375..3e0ad0b 100644 --- a/ChronoWebServer.h +++ b/ChronoWebServer.h @@ -8,6 +8,7 @@ #include #include #include +#include "ChronoVersion.h" class ChronoWebServer { public: @@ -34,6 +35,7 @@ class ChronoWebServer { static void _handleFormat(); static void _handleTestMode(); static void _handleOta(); + static void _handleVersion(); };