From 984d20e7a3db014309555e83a820da2cd787dc15 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 14:44:00 +0100 Subject: [PATCH 01/19] Stop tracking __vm files --- SmartFuseBox/__vm/Compile.vmps.xml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 SmartFuseBox/__vm/Compile.vmps.xml diff --git a/SmartFuseBox/__vm/Compile.vmps.xml b/SmartFuseBox/__vm/Compile.vmps.xml deleted file mode 100644 index 1a81d15..0000000 --- a/SmartFuseBox/__vm/Compile.vmps.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From 4f85e8a15b7043e72570101c73f64d94cb71e875 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 14:46:37 +0100 Subject: [PATCH 02/19] Keep a couple of changes --- BoatControlPanel/BoatControlPanel.ino | 4 --- BoatControlPanel/BoatControlPanel.vcxproj | 6 ++-- .../BoatControlPanel.vcxproj.filters | 2 +- Shared/ToneManager.cpp | 29 ------------------- Shared/WarningType.h | 3 +- SmartFuseBox/SmartFuseBox.vcxproj.filters | 12 ++++++++ 6 files changed, 18 insertions(+), 38 deletions(-) diff --git a/BoatControlPanel/BoatControlPanel.ino b/BoatControlPanel/BoatControlPanel.ino index 6941d7c..779e7f0 100644 --- a/BoatControlPanel/BoatControlPanel.ino +++ b/BoatControlPanel/BoatControlPanel.ino @@ -50,11 +50,7 @@ #include "ToneManager.h" #include "RgbLedFade.h" -// sensors -//#include "TLVCompassHandler.h" - #include "GpsSensorHandler.h" -#include "SensorCommandHandler.h" #if defined(ARDUINO_MEGA2560) #define NEXTION_SERIAL Serial1 diff --git a/BoatControlPanel/BoatControlPanel.vcxproj b/BoatControlPanel/BoatControlPanel.vcxproj index 181fe63..81b9d53 100644 --- a/BoatControlPanel/BoatControlPanel.vcxproj +++ b/BoatControlPanel/BoatControlPanel.vcxproj @@ -123,7 +123,7 @@ CppCode true - + @@ -182,7 +182,7 @@ VisualMicroDebugger - $(ProjectDir)..\BoatControlPanel;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\TinyGPSPlus\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\variants\mega;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\SoftwareSerial\src;$(ProjectDir)..\Shared\Sensors + $(ProjectDir)..\BoatControlPanel;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\TinyGPSPlus\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\variants\mega;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\SoftwareSerial\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\bin\avr-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\bin\avr-g++ false @@ -203,7 +203,7 @@ - $(ProjectDir)..\BoatControlPanel;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\TinyGPSPlus\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\variants\mega;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\SoftwareSerial\src;$(ProjectDir)..\Shared\Sensors;%(AdditionalIncludeDirectories) + $(ProjectDir)..\BoatControlPanel;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\TinyGPSPlus\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\variants\mega;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\lib\gcc\avr\7.3.0\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\NextionControl\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\avr\1.8.6\libraries\SoftwareSerial\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\bin\avr-g++ gnu++11 gnu11 diff --git a/BoatControlPanel/BoatControlPanel.vcxproj.filters b/BoatControlPanel/BoatControlPanel.vcxproj.filters index 1a4d880..4560cd1 100644 --- a/BoatControlPanel/BoatControlPanel.vcxproj.filters +++ b/BoatControlPanel/BoatControlPanel.vcxproj.filters @@ -321,7 +321,7 @@ Header Files\Sensors - + Header Files\SharedCode diff --git a/Shared/ToneManager.cpp b/Shared/ToneManager.cpp index bf11904..5eef3e4 100644 --- a/Shared/ToneManager.cpp +++ b/Shared/ToneManager.cpp @@ -18,41 +18,24 @@ void ToneManager::configSet(SoundSignalConfig* config) void ToneManager::play(ToneType type) { - Serial.print(F("[ToneManager::play] Type=")); - Serial.println(type == ToneType::Good ? F("Good") : F("Bad")); - stop(); buildSequence(type); if (_totalSteps > 0) { - Serial.print(F("[ToneManager::play] Starting sequence - steps=")); - Serial.println(_totalSteps); _playing = true; _currentStep = 0; _stepStartTime = millis(); startCurrentStep(); } - else - { - Serial.println(F("[ToneManager::play] No steps built")); - } } void ToneManager::stop() { - Serial.print(F("[ToneManager::stop] Called - _playing=")); - Serial.println(_playing); - if (_playing) { - Serial.println(F("[ToneManager::stop] Calling noTone()")); noTone(_pin); } - else - { - Serial.println(F("[ToneManager::stop] Skipped noTone() - not playing")); - } _playing = false; _currentStep = 0; @@ -80,12 +63,10 @@ void ToneManager::update(unsigned long now) if (_currentStep >= _totalSteps) { - Serial.println(F("[ToneManager::update] Sequence complete")); stop(); return; } - Serial.println(F("[ToneManager::update] Advancing to next step")); _stepStartTime = now; startCurrentStep(); } @@ -99,23 +80,13 @@ void ToneManager::startCurrentStep() { const ToneStep& step = _steps[_currentStep]; - Serial.print(F("[ToneManager::startCurrentStep] Step ")); - Serial.print(_currentStep); - Serial.print(F("/")); - Serial.print(_totalSteps); - Serial.print(F(" - Hz=")); - Serial.print(step.frequencyHz); - Serial.print(F(", ms=")); - Serial.println(step.durationMs); if (step.frequencyHz > 0) { - Serial.println(F("[ToneManager::startCurrentStep] Calling tone()")); tone(_pin, step.frequencyHz, step.durationMs); } else { - Serial.println(F("[ToneManager::startCurrentStep] Calling noTone() - silence step")); noTone(_pin); } } diff --git a/Shared/WarningType.h b/Shared/WarningType.h index cf28f85..b310bef 100644 --- a/Shared/WarningType.h +++ b/Shared/WarningType.h @@ -28,6 +28,7 @@ enum class WarningType : uint32_t { WifiInvalidConfig = 1UL << 7, // 0x00000080 - WiFi configuration invalid WeakWifiSignal = 1UL << 8, // 0x00000100 - WiFi signal weak SyncFailed = 1UL << 9, // 0x00000200 - Configuration sync issue detected + SdCardError = 1UL << 10, // 0x00000400 - SD card read/write error // Sensor warnings (bits 20+) SensorFailure = 1UL << 20, // 0x00100000 - Sensor communication failure @@ -48,7 +49,7 @@ static const char WT_6[] PROGMEM = "Wifi Init Failed"; static const char WT_7[] PROGMEM = "Wifi Invalid Config"; static const char WT_8[] PROGMEM = "Weak Wifi Signal"; static const char WT_9[] PROGMEM = "Synchronization Failed"; -static const char WT_10[] PROGMEM = ""; +static const char WT_10[] PROGMEM = "SD Card Error"; static const char WT_11[] PROGMEM = ""; static const char WT_12[] PROGMEM = ""; static const char WT_13[] PROGMEM = ""; diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index 7c39dc6..19b29c1 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -184,6 +184,12 @@ Source Files + + Source Files + + + Source Files + @@ -345,6 +351,12 @@ Header Files + + Header Files + + + Header Files + From de050fa2090cf6ceee41aa9fde25b05480543379 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 15:46:01 +0100 Subject: [PATCH 03/19] Initial sd card reader --- SmartFuseBox/LightSensorHandler.h | 5 +- SmartFuseBox/SdCardLogger.cpp | 326 ++++++++++++++++++++++ SmartFuseBox/SdCardLogger.h | 148 ++++++++++ SmartFuseBox/SensorDataRecord.h | 35 +++ SmartFuseBox/SmartFuseBox.ino | 14 +- SmartFuseBox/SmartFuseBox.vcxproj | 7 +- SmartFuseBox/SmartFuseBox.vcxproj.filters | 9 + SmartFuseBox/SmartFuseBoxConstants.h | 5 + SmartFuseBox/__vm/Upload.vmps.xml | 13 - 9 files changed, 544 insertions(+), 18 deletions(-) create mode 100644 SmartFuseBox/SdCardLogger.cpp create mode 100644 SmartFuseBox/SdCardLogger.h create mode 100644 SmartFuseBox/SensorDataRecord.h delete mode 100644 SmartFuseBox/__vm/Upload.vmps.xml diff --git a/SmartFuseBox/LightSensorHandler.h b/SmartFuseBox/LightSensorHandler.h index e63ef99..89e146c 100644 --- a/SmartFuseBox/LightSensorHandler.h +++ b/SmartFuseBox/LightSensorHandler.h @@ -11,6 +11,7 @@ class LightSensorHandler : public BaseSensor, public BroadcastLoggerSupport SensorCommandHandler* _sensorCommandHandler; WarningManager* _warningManager; const uint8_t _sensorPin; + const uint8_t _analogPin; bool _isPinActive; bool _isDaytime; protected: @@ -42,9 +43,9 @@ class LightSensorHandler : public BaseSensor, public BroadcastLoggerSupport } public: LightSensorHandler(MessageBus* messageBus, BroadcastManager* broadcastManager, SensorCommandHandler* sensorCommandHandler, - WarningManager* warningManager, uint8_t sensorPin) + WarningManager* warningManager, uint8_t sensorPin, uint8_t analogPin) : BroadcastLoggerSupport(broadcastManager), _messageBus(messageBus), _sensorCommandHandler(sensorCommandHandler), - _warningManager(warningManager), _sensorPin(sensorPin), _isPinActive(false), _isDaytime(true) + _warningManager(warningManager), _sensorPin(sensorPin), _analogPin(analogPin), _isPinActive(false), _isDaytime(true) { } diff --git a/SmartFuseBox/SdCardLogger.cpp b/SmartFuseBox/SdCardLogger.cpp new file mode 100644 index 0000000..655972f --- /dev/null +++ b/SmartFuseBox/SdCardLogger.cpp @@ -0,0 +1,326 @@ +#include "SdCardLogger.h" +#include "DateTimeManager.h" +#include "SmartFuseBoxConstants.h" + +SdCardLogger::SdCardLogger(MessageBus* messageBus, WarningManager* warningManager) + : _messageBus(messageBus) + , _warningManager(warningManager) + , _initialized(false) + , _sdCardPresent(false) + , _bufferHead(0) + , _bufferTail(0) + , _bufferCount(0) + , _currentDay(0) + , _lastWriteTime(0) + , _lastFileCheckTime(0) + , _totalRecordsLogged(0) + , _recordsDropped(0) + , _sdCardErrorRaised(false) +{ + memset(_currentFileName, 0, sizeof(_currentFileName)); +} + +bool SdCardLogger::initialize() +{ + // Subscribe to MessageBus events + _messageBus->subscribe([this](float temp) + { + this->onTemperatureUpdated(temp); + }); + + _messageBus->subscribe([this](uint8_t humidity) + { + this->onHumidityUpdated(humidity); + }); + + _messageBus->subscribe([this](uint16_t level, uint16_t avgLevel) + { + this->onWaterLevelUpdated(level, avgLevel); + }); + + _messageBus->subscribe([this](bool isDayTime) + { + this->onLightSensorUpdated(isDayTime); + }); + + // Initialize SD card + if (!initializeSdCard()) + { + _warningManager->raiseWarning(WarningType::SdCardError); + _sdCardErrorRaised = true; + return false; + } + + _initialized = true; + _sdCardPresent = true; + + return true; +} + +bool SdCardLogger::initializeSdCard() +{ + // Initialize SPI and SD card + if (!_sd.begin(SD_CHIP_SELECT_PIN)) + { + return false; + } + + return true; +} + +void SdCardLogger::update(unsigned long now) +{ + if (!_initialized || !_sdCardPresent) + { + return; + } + + // Check for date change periodically + if (now - _lastFileCheckTime >= SD_FILE_CHECK_INTERVAL_MS) + { + checkForDateChange(now); + _lastFileCheckTime = now; + } + + // Write buffered records if enough time has passed + if (now - _lastWriteTime >= SD_WRITE_INTERVAL_MS) + { + if (!isBufferEmpty()) + { + if (writeRecordsToCard(SD_MAX_WRITES_PER_LOOP)) + { + _lastWriteTime = now; + + // Clear SD card error if it was raised and we can write again + if (_sdCardErrorRaised) + { + _warningManager->clearWarning(WarningType::SdCardError); + _sdCardErrorRaised = false; + } + } else { + // Write failed + if (!_sdCardErrorRaised) + { + _warningManager->raiseWarning(WarningType::SdCardError); + _sdCardErrorRaised = true; + } + } + } + } +} + +bool SdCardLogger::writeRecordsToCard(uint8_t maxRecords) +{ + uint8_t recordsWritten = 0; + + // Open or create file if needed + if (!_currentFile) + { + if (!openOrCreateFile(millis())) + { + return false; + } + } + + // Write up to maxRecords from buffer + while (!isBufferEmpty() && recordsWritten < maxRecords) + { + const SensorDataRecord& record = _buffer[_bufferTail]; + + writeRecordToCsv(record); + + // Move tail forward (circular) + _bufferTail = (_bufferTail + 1) % SD_BUFFER_SIZE; + _bufferCount--; + _totalRecordsLogged++; + recordsWritten++; + } + + // Flush to ensure data is written + if (recordsWritten > 0) + { + _currentFile.flush(); + } + + return true; +} + +void SdCardLogger::writeRecordToCsv(const SensorDataRecord& record) +{ + if (!_currentFile) + { + return; + } + + // Format: Timestamp,DateTime,SensorType,Value1,Value2 + + // Timestamp (millis) + _currentFile.print(record.timestamp); + _currentFile.print(','); + + // DateTime (formatted) + char dateTimeBuf[DateTimeBufferLength]; + DateTimeManager::formatDateTime(dateTimeBuf, sizeof(dateTimeBuf)); + _currentFile.print(dateTimeBuf); + _currentFile.print(','); + + // Sensor type + _currentFile.print(getSensorTypeName(record.sensorType)); + _currentFile.print(','); + + // Value1 + _currentFile.print(record.value1, 2); + _currentFile.print(','); + + // Value2 + _currentFile.print(record.value2, 2); + _currentFile.println(); +} + +bool SdCardLogger::openOrCreateFile(unsigned long now) +{ + // Close existing file if open + if (_currentFile) + { + closeCurrentFile(); + } + + // Generate filename based on current date + updateFileName(now); + + // Check if file exists + bool fileExists = _sd.exists(_currentFileName); + + // Open file in append mode (O_RDWR | O_CREAT | O_APPEND) + if (!_currentFile.open(_currentFileName, O_RDWR | O_CREAT | O_AT_END)) + { + return false; + } + + // Write CSV header if new file + if (!fileExists) + { + _currentFile.println(F("Timestamp,DateTime,SensorType,Value1,Value2")); + _currentFile.flush(); + } + + return true; +} + +void SdCardLogger::closeCurrentFile() +{ + if (_currentFile) + { + _currentFile.flush(); + _currentFile.close(); + } +} + +void SdCardLogger::updateFileName(unsigned long now) +{ + uint16_t year = DateTimeManager::getYear(); + uint8_t month = DateTimeManager::getMonth(); + uint8_t day = DateTimeManager::getDay(); + + // Format: YYYYMMDD.csv + snprintf(_currentFileName, sizeof(_currentFileName), + "%04d%02d%02d.csv", year, month, day); + + _currentDay = day; +} + +void SdCardLogger::checkForDateChange(unsigned long now) +{ + uint8_t currentDay = DateTimeManager::getDay(); + + if (currentDay != _currentDay && _currentDay != 0) + { + // Date has changed, close current file and open new one + closeCurrentFile(); + openOrCreateFile(now); + } +} + +void SdCardLogger::addRecordToBuffer(const SensorDataRecord& record) +{ + if (isBufferFull()) + { + // Buffer is full, drop oldest record (overwrite tail) + _recordsDropped++; + _bufferTail = (_bufferTail + 1) % SD_BUFFER_SIZE; + _bufferCount--; + } + + // Add new record at head + _buffer[_bufferHead] = record; + _bufferHead = (_bufferHead + 1) % SD_BUFFER_SIZE; + _bufferCount++; +} + +bool SdCardLogger::isBufferFull() const { + return _bufferCount >= SD_BUFFER_SIZE; +} + +bool SdCardLogger::isBufferEmpty() const { + return _bufferCount == 0; +} + +const char* SdCardLogger::getSensorTypeName(SensorDataType type) const { + switch (type) + { + case SensorDataType::Temperature: + return "Temperature"; + case SensorDataType::Humidity: + return "Humidity"; + case SensorDataType::WaterLevel: + return "WaterLevel"; + case SensorDataType::LightLevel: + return "LightLevel"; + default: + return "Unknown"; + } +} + +void SdCardLogger::flush() +{ + if ( !_initialized) + { + return; + } + + // Write all buffered records (blocking) + while (!isBufferEmpty()) + { + writeRecordsToCard(SD_MAX_WRITES_PER_LOOP); + } + + closeCurrentFile(); +} + +// MessageBus callback implementations +void SdCardLogger::onTemperatureUpdated(float temperature) +{ + SensorDataRecord record(millis(), SensorDataType::Temperature, temperature); + addRecordToBuffer(record); +} + +void SdCardLogger::onHumidityUpdated(uint8_t humidity) +{ + SensorDataRecord record(millis(), SensorDataType::Humidity, static_cast(humidity)); + addRecordToBuffer(record); +} + +void SdCardLogger::onWaterLevelUpdated(uint16_t waterLevel, uint16_t averageWaterLevel) +{ + SensorDataRecord record(millis(), SensorDataType::WaterLevel, + static_cast(waterLevel), + static_cast(averageWaterLevel)); + addRecordToBuffer(record); +} + +void SdCardLogger::onLightSensorUpdated(bool isDayTime) +{ + SensorDataRecord record(millis(), SensorDataType::LightLevel, + isDayTime ? 1.0f : 0.0f); + addRecordToBuffer(record); +} diff --git a/SmartFuseBox/SdCardLogger.h b/SmartFuseBox/SdCardLogger.h new file mode 100644 index 0000000..ac49f6f --- /dev/null +++ b/SmartFuseBox/SdCardLogger.h @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include +#include "SensorDataRecord.h" +#include "MessageBus.h" +#include "WarningManager.h" + +constexpr uint8_t SD_CHIP_SELECT_PIN = 10; +constexpr uint8_t SD_BUFFER_SIZE = 64; // Number of records to buffer +constexpr uint8_t SD_MAX_WRITES_PER_LOOP = 5; // Max records to write per update() call +constexpr uint16_t SD_WRITE_INTERVAL_MS = 1000; // Minimum time between write operations +constexpr uint16_t SD_FILE_CHECK_INTERVAL_MS = 60000; // Check for date change every minute + +/** + * @class SdCardLogger + * @brief Non-blocking SD card logger for sensor data + * + * Features: + * - Subscribes to MessageBus sensor events + * - Buffers sensor readings in circular buffer + * - Non-blocking writes (max N records per loop iteration) + * - Date-based file naming (YYYYMMDD.csv) + * - Automatic file rotation at midnight + * - Integrates with WarningManager for error reporting + * - Configurable via Config struct + * + * Architecture: + * - Sensor events -> MessageBus -> SdCardLogger buffer -> SD card (chunked writes) + * + * Usage: + * @code + * SdCardLogger logger(&messageBus, &warningManager); + * + * void setup() { + * Config* config = ConfigManager::getConfigPtr(); + * if (!logger.initialize(config)) { + * // Handle initialization failure + * } + * } + * + * void loop() { + * unsigned long now = millis(); + * logger.update(now); + * } + * @endcode + */ +class SdCardLogger { +private: + MessageBus* _messageBus; + WarningManager* _warningManager; + + // SD card + SdFat _sd; // Main SD card object + FsFile _currentFile; // Current log file + + // State + bool _initialized; + bool _sdCardPresent; + + // Circular buffer + SensorDataRecord _buffer[SD_BUFFER_SIZE]; + uint8_t _bufferHead; // Next position to write + uint8_t _bufferTail; // Next position to read + uint8_t _bufferCount; // Number of records in buffer + char _currentFileName[13]; // "YYYYMMDD.csv\0" + uint8_t _currentDay; // Track current day for file rotation + + // Timing + unsigned long _lastWriteTime; + unsigned long _lastFileCheckTime; + + // Statistics + unsigned long _totalRecordsLogged; + unsigned long _recordsDropped; + bool _sdCardErrorRaised; + + // Internal methods + bool initializeSdCard(); + bool openOrCreateFile(unsigned long now); + void closeCurrentFile(); + bool writeRecordsToCard(uint8_t maxRecords); + void writeRecordToCsv(const SensorDataRecord& record); + void addRecordToBuffer(const SensorDataRecord& record); + bool isBufferFull() const; + bool isBufferEmpty() const; + void updateFileName(unsigned long now); + void checkForDateChange(unsigned long now); + const char* getSensorTypeName(SensorDataType type) const; + + // MessageBus callbacks + void onTemperatureUpdated(float temperature); + void onHumidityUpdated(uint8_t humidity); + void onWaterLevelUpdated(uint16_t waterLevel, uint16_t averageWaterLevel); + void onLightSensorUpdated(bool isDayTime); + +public: + /** + * @brief Constructor + * @param messageBus Pointer to MessageBus for subscribing to events + * @param warningManager Pointer to WarningManager for error reporting + */ + SdCardLogger(MessageBus* messageBus, WarningManager* warningManager); + + /** + * @brief Initialize SD card logger with configuration + * @param config Configuration structure + * @return true if initialization successful, false otherwise + */ + bool initialize(); + + /** + * @brief Update logger - processes buffered writes in non-blocking manner + * @param now Current time from millis() + */ + void update(unsigned long now); + + /** + * @brief Check if SD card is initialized and working + * @return true if SD card is ready, false otherwise + */ + bool isSdCardReady() const { return _initialized && _sdCardPresent; } + + /** + * @brief Get total number of records successfully logged + * @return Total records logged + */ + unsigned long getTotalRecordsLogged() const { return _totalRecordsLogged; } + + /** + * @brief Get number of records dropped due to buffer overflow + * @return Number of dropped records + */ + unsigned long getRecordsDropped() const { return _recordsDropped; } + + /** + * @brief Get current buffer utilization + * @return Number of records currently in buffer + */ + uint8_t getBufferCount() const { return _bufferCount; } + + /** + * @brief Flush all buffered records to SD card (blocking) + * Use sparingly, typically only during shutdown + */ + void flush(); +}; diff --git a/SmartFuseBox/SensorDataRecord.h b/SmartFuseBox/SensorDataRecord.h new file mode 100644 index 0000000..e76a973 --- /dev/null +++ b/SmartFuseBox/SensorDataRecord.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +/** + * @enum SensorDataType + * @brief Types of sensor data that can be logged + */ +enum class SensorDataType : uint8_t { + Temperature = 0x01, + Humidity = 0x02, + WaterLevel = 0x03, + LightLevel = 0x04, +}; + +/** + * @struct SensorDataRecord + * @brief Standardized structure for logging sensor data + * + * This structure holds a single sensor reading with timestamp. + * Designed to be compact for efficient memory usage in circular buffer. + */ +struct SensorDataRecord { + unsigned long timestamp; // millis() when reading was taken + SensorDataType sensorType; // Type of sensor data + float value1; // Primary value (e.g., temperature, water level) + float value2; // Secondary value (e.g., humidity, average water level) + + SensorDataRecord() + : timestamp(0), sensorType(SensorDataType::Temperature), value1(0.0f), value2(0.0f) {} + + SensorDataRecord(unsigned long ts, SensorDataType type, float val1, float val2 = 0.0f) + : timestamp(ts), sensorType(type), value1(val1), value2(val2) {} +}; diff --git a/SmartFuseBox/SmartFuseBox.ino b/SmartFuseBox/SmartFuseBox.ino index 508793c..a6540d5 100644 --- a/SmartFuseBox/SmartFuseBox.ino +++ b/SmartFuseBox/SmartFuseBox.ino @@ -2,6 +2,7 @@ #include #include #include +#include #include "SystemCpuMonitor.h" #include "DateTimeManager.h" @@ -50,6 +51,8 @@ #endif #include "MessageBus.h" +#include "SensorDataRecord.h" +#include "SdCardLogger.h" #define COMPUTER_SERIAL Serial @@ -96,7 +99,7 @@ SystemCommandHandler systemCommandHandler(&broadcastManager, &warningManager); // Sensors WaterSensorHandler waterSensorHandler(&messageBus, &broadcastManager, &sensorCommandHandler, WaterSensorPin, WaterSensorActivePin); Dht11SensorHandler dht11SensorHandler(&messageBus, &broadcastManager, &sensorCommandHandler, &warningManager, Dht11SensorPin); -LightSensorHandler lightSensorHandler(&messageBus, &broadcastManager, &sensorCommandHandler, &warningManager, LightSensorPin); +LightSensorHandler lightSensorHandler(&messageBus, &broadcastManager, &sensorCommandHandler, &warningManager, LightSensorPin, LightSensorAnalogPin); BaseSensorHandler* sensorHandlers[] = { &waterSensorHandler, &dht11SensorHandler, &lightSensorHandler @@ -131,6 +134,9 @@ WarningNetworkHandler warningNetworkHandler(&warningManager); SystemNetworkHandler systemNetworkHandler(&wifiController); SensorNetworkHandler sensorNetworkHandler(&sensorController); +// SD card logger +SdCardLogger sdCardLogger(&messageBus, &warningManager); + void setup() { @@ -175,6 +181,8 @@ void setup() relayHandler.configUpdated(config); sensorManager.setup(); + // Initialize SD card logger (always enabled) + sdCardLogger.initialize(); #if defined(ARDUINO_UNO_R4) && defined(LED_MANAGER) ledManager.Initialize(); @@ -230,6 +238,10 @@ void loop() configSyncManager.update(now); SystemCpuMonitor::endTask(); + SystemCpuMonitor::startTask(); + sdCardLogger.update(now); + SystemCpuMonitor::endTask(); + SystemCpuMonitor::update(); delay(DefaultDelay); } diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index 2490ef6..41d09b5 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -74,6 +74,7 @@ + @@ -118,6 +119,8 @@ true + + @@ -176,7 +179,7 @@ VisualMicroDebugger - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ false @@ -203,7 +206,7 @@ - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ c++17 gnu11 diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index 19b29c1..0804dbf 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -190,6 +190,9 @@ Source Files + + Source Files + @@ -357,6 +360,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/SmartFuseBox/SmartFuseBoxConstants.h b/SmartFuseBox/SmartFuseBoxConstants.h index 9b26e2a..6a0053f 100644 --- a/SmartFuseBox/SmartFuseBoxConstants.h +++ b/SmartFuseBox/SmartFuseBoxConstants.h @@ -8,9 +8,14 @@ constexpr unsigned long serialInitTimeoutMs = 300; constexpr uint8_t WaterSensorPin = A0; +constexpr uint8_t LightSensorAnalogPin = A1; constexpr uint8_t LightSensorPin = D3; constexpr uint8_t WaterSensorActivePin = D8; constexpr uint8_t Dht11SensorPin = D9; +constexpr uint8_t SdCardCsPin = D10; +constexpr uint8_t SdCardMosiPin = D11; +constexpr uint8_t SdCardMisoPin = D12; +constexpr uint8_t SdCardSckPin = D13; // Digital pins for relays diff --git a/SmartFuseBox/__vm/Upload.vmps.xml b/SmartFuseBox/__vm/Upload.vmps.xml deleted file mode 100644 index 1a81d15..0000000 --- a/SmartFuseBox/__vm/Upload.vmps.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From 68b27bbc2f885d4eddf2d52a78fc60d63428f58c Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 16:54:57 +0100 Subject: [PATCH 04/19] Minor fixes --- .../CommandHandlers/SensorCommandHandler.cpp | 116 ++++++++++++++++-- .../CommandHandlers/SystemCommandHandler.cpp | 3 +- Shared/DateTimeManager.cpp | 105 ++++++++++------ Shared/Sensors/GpsSensorHandler.h | 86 +++++++++---- 4 files changed, 238 insertions(+), 72 deletions(-) diff --git a/Shared/CommandHandlers/SensorCommandHandler.cpp b/Shared/CommandHandlers/SensorCommandHandler.cpp index 12b4697..90a032d 100644 --- a/Shared/CommandHandlers/SensorCommandHandler.cpp +++ b/Shared/CommandHandlers/SensorCommandHandler.cpp @@ -241,54 +241,148 @@ bool SensorCommandHandler::handleCommand(SerialCommandManager* sender, const cha // Handle query requests (no parameters) - return current sensor values if (paramCount == 0) { - char buffer[32]; + char buffer[64]; + sendDebugMessage(F("Query request - no params"), F("SensorCommandHandler")); - if (strcmp(command, SensorLightSensor) == 0) - { - // S9 query - return current day/night status - snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _isDaytime ? 1 : 0); - sender->sendCommand(SensorLightSensor, buffer); - sendAckOk(sender, command); - return true; - } - else if (strcmp(command, SensorTemperature) == 0) + if (strcmp(command, SensorTemperature) == 0) { + // S0 query snprintf_P(buffer, sizeof(buffer), PSTR("v=%.1f"), _lastTemperature); sender->sendCommand(SensorTemperature, buffer); sendAckOk(sender, command); + sendDebugMessage(F("Returned Temperature"), F("SensorCommandHandler")); return true; } else if (strcmp(command, SensorHumidity) == 0) { + // S1 query snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _lastHumidity); sender->sendCommand(SensorHumidity, buffer); sendAckOk(sender, command); + sendDebugMessage(F("Returned Humidity"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorBearing) == 0) + { + // S2 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%.1f"), _lastBearing); + sender->sendCommand(SensorBearing, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned Bearing"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorDirection) == 0) + { + // S3 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%s"), _gpsDirection ? _gpsDirection : "N"); + sender->sendCommand(SensorDirection, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned Direction"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorSpeed) == 0) + { + // S4 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _lastSpeed); + sender->sendCommand(SensorSpeed, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned Speed"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorCompassTemp) == 0) + { + // S5 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%.1f"), _lastCompassTemp); + sender->sendCommand(SensorCompassTemp, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned Compass Temp"), F("SensorCommandHandler")); return true; } else if (strcmp(command, SensorWaterLevel) == 0) { + // S6 query snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _lastWaterLevel); sender->sendCommand(SensorWaterLevel, buffer); sendAckOk(sender, command); + sendDebugMessage(F("Returned Water Level"), F("SensorCommandHandler")); return true; } else if (strcmp(command, SensorWaterPumpActive) == 0) { + // S7 query snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _lastWaterPumpActive ? 1 : 0); sender->sendCommand(SensorWaterPumpActive, buffer); sendAckOk(sender, command); + sendDebugMessage(F("Returned Water Pump"), F("SensorCommandHandler")); return true; } else if (strcmp(command, SensorHornActive) == 0) { + // S8 query snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _lastHornActive ? 1 : 0); sender->sendCommand(SensorHornActive, buffer); sendAckOk(sender, command); + sendDebugMessage(F("Returned Horn Active"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorLightSensor) == 0) + { + // S9 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%d"), _isDaytime ? 1 : 0); + sender->sendCommand(SensorLightSensor, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned Light Sensor"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorGpsLatLong) == 0) + { + // S10 query + snprintf_P(buffer, sizeof(buffer), PSTR("lat=%.6f&lon=%.6f"), _gpsLatitude, _gpsLongitude); + sender->sendCommand(SensorGpsLatLong, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned GPS LatLong"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorGpsAltitude) == 0) + { + // S11 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%.2f"), _altitude); + sender->sendCommand(SensorGpsAltitude, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned GPS Altitude"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorGpsSpeed) == 0) + { + // S12 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%.2f&course=%.2f&dir=%s"), + (double)_lastSpeed, _gpsCourse, _gpsDirection ? _gpsDirection : "N"); + sender->sendCommand(SensorGpsSpeed, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned GPS Speed"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorGpsSatellites) == 0) + { + // S13 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%lu"), (unsigned long)_gpsSatellites); + sender->sendCommand(SensorGpsSatellites, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned GPS Satellites"), F("SensorCommandHandler")); + return true; + } + else if (strcmp(command, SensorGpsDistance) == 0) + { + // S14 query + snprintf_P(buffer, sizeof(buffer), PSTR("v=%.2f"), _gpsDistance); + sender->sendCommand(SensorGpsDistance, buffer); + sendAckOk(sender, command); + sendDebugMessage(F("Returned GPS Distance"), F("SensorCommandHandler")); return true; } // Unknown query command - sendDebugMessage(F("No parameters in sensor command"), F("SensorCommandHandler")); + sendErrorMessage(F("Unknown sensor query command"), "SensorCommandHandler"); return false; } diff --git a/Shared/CommandHandlers/SystemCommandHandler.cpp b/Shared/CommandHandlers/SystemCommandHandler.cpp index 1b0d6c3..5d47ac7 100644 --- a/Shared/CommandHandlers/SystemCommandHandler.cpp +++ b/Shared/CommandHandlers/SystemCommandHandler.cpp @@ -107,7 +107,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha else if (strcmp(command, SystemSetDateTime) == 0 && paramCount == 1) { bool success = false; - + _broadcaster->getComputerSerial()->sendDebug(command, params[0].value); // Try ISO 8601 format first (contains 'T' or '-') if (strchr(params[0].value, 'T') != nullptr || strchr(params[0].value, '-') != nullptr) { @@ -132,6 +132,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha } else { + _broadcaster->getComputerSerial()->sendDebug(command, F("Invalid Datetime")); sendAckErr(sender, command, F("Invalid datetime format")); } diff --git a/Shared/DateTimeManager.cpp b/Shared/DateTimeManager.cpp index 7690b71..81af215 100644 --- a/Shared/DateTimeManager.cpp +++ b/Shared/DateTimeManager.cpp @@ -7,20 +7,24 @@ unsigned long DateTimeManager::_syncedTimestamp = 0; unsigned long DateTimeManager::_syncedMillis = 0; bool DateTimeManager::_isSet = false; -void DateTimeManager::setDateTime() { +void DateTimeManager::setDateTime() +{ // Set to default: January 1, 2025 00:00:00 setDateTime(DefaultTimestamp); } -void DateTimeManager::setDateTime(unsigned long unixTimestamp) { +void DateTimeManager::setDateTime(unsigned long unixTimestamp) +{ _syncedTimestamp = unixTimestamp; _syncedMillis = millis(); _isSet = true; } -bool DateTimeManager::setDateTimeISO(const char* isoDateTime) { +bool DateTimeManager::setDateTimeISO(const char* isoDateTime) +{ // Expected format: YYYY-MM-DDTHH:MM:SS (19 characters minimum) - if (strlen(isoDateTime) < 19) { + if (strlen(isoDateTime) < 19) + { return false; } @@ -42,7 +46,8 @@ bool DateTimeManager::setDateTimeISO(const char* isoDateTime) { day < 1 || day > 31 || hour > 23 || minute > 59 || - second > 59) { + second > 59) + { return false; } @@ -52,8 +57,10 @@ bool DateTimeManager::setDateTimeISO(const char* isoDateTime) { return true; } -unsigned long DateTimeManager::getCurrentTime() { - if (!_isSet) { +unsigned long DateTimeManager::getCurrentTime() +{ + if (!_isSet) + { return 0; } @@ -61,7 +68,8 @@ unsigned long DateTimeManager::getCurrentTime() { unsigned long currentMillis = millis(); unsigned long elapsedMillis; - if (currentMillis >= _syncedMillis) { + if (currentMillis >= _syncedMillis) + { elapsedMillis = currentMillis - _syncedMillis; } else { // millis() has overflowed (after ~49.7 days) @@ -73,20 +81,23 @@ unsigned long DateTimeManager::getCurrentTime() { return _syncedTimestamp + elapsedSeconds; } -bool DateTimeManager::isTimeSet() { +bool DateTimeManager::isTimeSet() +{ return _isSet; } unsigned long DateTimeManager::getSecondsSinceSync() { - if (!_isSet) { + if (!_isSet) + { return 0; } unsigned long currentMillis = millis(); unsigned long elapsedMillis; - if (currentMillis >= _syncedMillis) { + if (currentMillis >= _syncedMillis) + { elapsedMillis = currentMillis - _syncedMillis; } else { elapsedMillis = (0xFFFFFFFF - _syncedMillis) + currentMillis + 1; @@ -95,8 +106,10 @@ unsigned long DateTimeManager::getSecondsSinceSync() return elapsedMillis / 1000; } -uint16_t DateTimeManager::getYear() { - if (!_isSet) { +uint16_t DateTimeManager::getYear() +{ + if (!_isSet) + { return 0; } @@ -106,8 +119,10 @@ uint16_t DateTimeManager::getYear() { return year; } -uint8_t DateTimeManager::getMonth() { - if (!_isSet) { +uint8_t DateTimeManager::getMonth() +{ + if (!_isSet) + { return 0; } @@ -117,8 +132,10 @@ uint8_t DateTimeManager::getMonth() { return month; } -uint8_t DateTimeManager::getDay() { - if (!_isSet) { +uint8_t DateTimeManager::getDay() +{ + if (!_isSet) + { return 0; } @@ -128,8 +145,10 @@ uint8_t DateTimeManager::getDay() { return day; } -uint8_t DateTimeManager::getHour() { - if (!_isSet) { +uint8_t DateTimeManager::getHour() +{ + if (!_isSet) + { return 0; } @@ -139,8 +158,10 @@ uint8_t DateTimeManager::getHour() { return hour; } -uint8_t DateTimeManager::getMinute() { - if (!_isSet) { +uint8_t DateTimeManager::getMinute() +{ + if (!_isSet) + { return 0; } @@ -150,8 +171,10 @@ uint8_t DateTimeManager::getMinute() { return minute; } -uint8_t DateTimeManager::getSecond() { - if (!_isSet) { +uint8_t DateTimeManager::getSecond() +{ + if (!_isSet) + { return 0; } @@ -163,7 +186,8 @@ uint8_t DateTimeManager::getSecond() { bool DateTimeManager::formatDateTime(char* buffer, const uint8_t bufferLength) { - if (!_isSet) { + if (!_isSet) + { return false; } @@ -212,19 +236,23 @@ unsigned long DateTimeManager::dateTimeToUnix(uint16_t year, uint8_t month, uint unsigned long days = 0; // Years - for (uint16_t y = 1970; y < year; y++) { + for (uint16_t y = 1970; y < year; y++) + { days += 365; // Add leap day if leap year - if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) { + if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) + { days++; } } // Months - for (uint8_t m = 1; m < month; m++) { + for (uint8_t m = 1; m < month; m++) + { days += daysInMonth[m - 1]; // Add leap day for February in leap year - if (m == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { + if (m == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) + { days++; } } @@ -242,7 +270,8 @@ unsigned long DateTimeManager::dateTimeToUnix(uint16_t year, uint8_t month, uint } void DateTimeManager::unixToDateTime(unsigned long unixTime, uint16_t& year, uint8_t& month, uint8_t& day, - uint8_t& hour, uint8_t& minute, uint8_t& second) { + uint8_t& hour, uint8_t& minute, uint8_t& second) +{ // Extract time components second = unixTime % 60; unixTime /= 60; @@ -253,13 +282,16 @@ void DateTimeManager::unixToDateTime(unsigned long unixTime, uint16_t& year, uin // Calculate year year = 1970; - while (true) { + while (true) + { uint16_t daysInYear = 365; - if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { + if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) + { daysInYear = 366; } - if (days < daysInYear) { + if (days < daysInYear) + { break; } @@ -272,13 +304,16 @@ void DateTimeManager::unixToDateTime(unsigned long unixTime, uint16_t& year, uin bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); month = 1; - while (month <= 12) { + while (month <= 12) + { uint8_t monthDays = daysInMonth[month - 1]; - if (month == 2 && isLeapYear) { + if (month == 2 && isLeapYear) + { monthDays = 29; } - if (days < monthDays) { + if (days < monthDays) + { break; } diff --git a/Shared/Sensors/GpsSensorHandler.h b/Shared/Sensors/GpsSensorHandler.h index 491bfc7..17b4d34 100644 --- a/Shared/Sensors/GpsSensorHandler.h +++ b/Shared/Sensors/GpsSensorHandler.h @@ -63,6 +63,7 @@ class GpsSensorHandler : public BaseSensor, public BroadcastLoggerSupport /** * @brief Synchronize DateTimeManager with GPS UTC time. * Converts GPS date/time to Unix timestamp and updates DateTimeManager. + * Supports partial updates: if only date or time is valid, uses existing value from DateTimeManager for the missing component. */ void syncTimeFromGps() { @@ -71,34 +72,69 @@ class GpsSensorHandler : public BaseSensor, public BroadcastLoggerSupport return; } - // Check if GPS date and time are valid - if (_gps->date.isValid() && _gps->time.isValid()) + bool hasGpsDate = _gps->date.isValid(); + bool hasGpsTime = _gps->time.isValid(); + + // Need at least one valid component + if (!hasGpsDate && !hasGpsTime) { - // Extract date/time components - uint16_t year = _gps->date.year(); - uint8_t month = _gps->date.month(); - uint8_t day = _gps->date.day(); - uint8_t hour = _gps->time.hour(); - uint8_t minute = _gps->time.minute(); - uint8_t second = _gps->time.second(); - - // Format as ISO 8601 string: YYYY-MM-DDTHH:MM:SS - char isoDateTime[20]; - snprintf_P(isoDateTime, sizeof(isoDateTime), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), - year, month, day, hour, minute, second); - - // Update DateTimeManager with GPS time (UTC) - if (DateTimeManager::setDateTimeISO(isoDateTime)) - { + return; + } - // Send F6 command to notify of time update - StringKeyValue timeParam; - strncpy(timeParam.key, ValueParamName, sizeof(timeParam.key)); - strncpy(timeParam.value, isoDateTime, sizeof(timeParam.value)); - sendCommand(SystemSetDateTime, &timeParam, 1); + // Extract date/time components + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; - _lastTimeSync = millis(); - } + // Get date components (from GPS or existing DateTimeManager) + if (hasGpsDate) + { + year = _gps->date.year(); + month = _gps->date.month(); + day = _gps->date.day(); + } + else + { + // Use existing date from DateTimeManager + year = DateTimeManager::getYear(); + month = DateTimeManager::getMonth(); + day = DateTimeManager::getDay(); + } + + // Get time components (from GPS or existing DateTimeManager) + if (hasGpsTime) + { + hour = _gps->time.hour(); + minute = _gps->time.minute(); + second = _gps->time.second(); + } + else + { + // Use existing time from DateTimeManager + hour = DateTimeManager::getHour(); + minute = DateTimeManager::getMinute(); + second = DateTimeManager::getSecond(); + } + + // Format as ISO 8601 string: YYYY-MM-DDTHH:MM:SS + char isoDateTime[20]; + snprintf_P(isoDateTime, sizeof(isoDateTime), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), + year, month, day, hour, minute, second); + + // Update DateTimeManager with GPS time (UTC) + if (DateTimeManager::setDateTimeISO(isoDateTime)) + { + + // Send F6 command to notify of time update + StringKeyValue timeParam; + strncpy(timeParam.key, ValueParamName, sizeof(timeParam.key)); + strncpy(timeParam.value, isoDateTime, sizeof(timeParam.value)); + sendCommand(SystemSetDateTime, &timeParam, 1); + + _lastTimeSync = millis(); } } From b1f1769d7bda9108f3f04bad1d3b4ad351a6f50b Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 16:55:18 +0100 Subject: [PATCH 05/19] New Warning Type --- Shared/WarningType.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Shared/WarningType.h b/Shared/WarningType.h index b310bef..a950e5e 100644 --- a/Shared/WarningType.h +++ b/Shared/WarningType.h @@ -29,6 +29,7 @@ enum class WarningType : uint32_t { WeakWifiSignal = 1UL << 8, // 0x00000100 - WiFi signal weak SyncFailed = 1UL << 9, // 0x00000200 - Configuration sync issue detected SdCardError = 1UL << 10, // 0x00000400 - SD card read/write error + SdCardMissing = 1UL << 11, // 0x00000800 - SD card not detected // Sensor warnings (bits 20+) SensorFailure = 1UL << 20, // 0x00100000 - Sensor communication failure @@ -50,7 +51,7 @@ static const char WT_7[] PROGMEM = "Wifi Invalid Config"; static const char WT_8[] PROGMEM = "Weak Wifi Signal"; static const char WT_9[] PROGMEM = "Synchronization Failed"; static const char WT_10[] PROGMEM = "SD Card Error"; -static const char WT_11[] PROGMEM = ""; +static const char WT_11[] PROGMEM = "SD Card Not Found"; static const char WT_12[] PROGMEM = ""; static const char WT_13[] PROGMEM = ""; static const char WT_14[] PROGMEM = ""; From 0b2c1cc07e365ea56fc8941c191645e509a7d19b Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 7 Feb 2026 17:08:04 +0100 Subject: [PATCH 06/19] Fix time sync --- .../CommandHandlers/SystemCommandHandler.cpp | 19 ++++++------------- Shared/Sensors/GpsSensorHandler.h | 8 +++++--- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Shared/CommandHandlers/SystemCommandHandler.cpp b/Shared/CommandHandlers/SystemCommandHandler.cpp index 5d47ac7..43123c8 100644 --- a/Shared/CommandHandlers/SystemCommandHandler.cpp +++ b/Shared/CommandHandlers/SystemCommandHandler.cpp @@ -107,20 +107,13 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha else if (strcmp(command, SystemSetDateTime) == 0 && paramCount == 1) { bool success = false; - _broadcaster->getComputerSerial()->sendDebug(command, params[0].value); - // Try ISO 8601 format first (contains 'T' or '-') - if (strchr(params[0].value, 'T') != nullptr || strchr(params[0].value, '-') != nullptr) - { - success = DateTimeManager::setDateTimeISO(params[0].value); - } - else + + // Only supports Unix timestamp (all digits) + unsigned long timestamp = static_cast(strtoul(params[0].value, nullptr, 0)); + if (timestamp > 0) { - // Try Unix timestamp (all digits) - unsigned long timestamp = static_cast(strtoul(params[0].value, nullptr, 0)); - if (timestamp > 0) { - DateTimeManager::setDateTime(timestamp); - success = true; - } + DateTimeManager::setDateTime(timestamp); + success = true; } if (success) diff --git a/Shared/Sensors/GpsSensorHandler.h b/Shared/Sensors/GpsSensorHandler.h index 17b4d34..9ebdd9e 100644 --- a/Shared/Sensors/GpsSensorHandler.h +++ b/Shared/Sensors/GpsSensorHandler.h @@ -119,7 +119,7 @@ class GpsSensorHandler : public BaseSensor, public BroadcastLoggerSupport second = DateTimeManager::getSecond(); } - // Format as ISO 8601 string: YYYY-MM-DDTHH:MM:SS + // Convert to Unix timestamp using ISO format (setDateTimeISO handles the conversion) char isoDateTime[20]; snprintf_P(isoDateTime, sizeof(isoDateTime), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), year, month, day, hour, minute, second); @@ -127,11 +127,13 @@ class GpsSensorHandler : public BaseSensor, public BroadcastLoggerSupport // Update DateTimeManager with GPS time (UTC) if (DateTimeManager::setDateTimeISO(isoDateTime)) { + // Get the Unix timestamp we just set + unsigned long unixTime = DateTimeManager::getCurrentTime(); - // Send F6 command to notify of time update + // Send F6 command with Unix timestamp StringKeyValue timeParam; strncpy(timeParam.key, ValueParamName, sizeof(timeParam.key)); - strncpy(timeParam.value, isoDateTime, sizeof(timeParam.value)); + snprintf_P(timeParam.value, sizeof(timeParam.value), PSTR("%lu"), unixTime); sendCommand(SystemSetDateTime, &timeParam, 1); _lastTimeSync = millis(); From a1737c2e415f04eee7869ff93ff1e4cdddf554f1 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 10:33:25 +0100 Subject: [PATCH 07/19] Minor tweaks --- SmartFuseBox/BluetoothSystemService.cpp | 4 - SmartFuseBox/ConfigSyncManager.cpp | 2 +- SmartFuseBox/SdCardLogger.cpp | 407 ++++++++++++++++++------ SmartFuseBox/SmartFuseBox.ino | 5 +- SmartFuseBox/SmartFuseBox.vcxproj | 4 +- 5 files changed, 314 insertions(+), 108 deletions(-) diff --git a/SmartFuseBox/BluetoothSystemService.cpp b/SmartFuseBox/BluetoothSystemService.cpp index adbf316..090423b 100644 --- a/SmartFuseBox/BluetoothSystemService.cpp +++ b/SmartFuseBox/BluetoothSystemService.cpp @@ -28,7 +28,6 @@ bool BluetoothSystemService::begin() { if (!_commandHandler) { - Serial.println(F("[BluetoothSystemService] Error: Command handler is null")); return false; } @@ -37,7 +36,6 @@ bool BluetoothSystemService::begin() if (!_service) { - Serial.println(F("[BluetoothSystemService] Error: Failed to create service")); return false; } @@ -62,7 +60,6 @@ bool BluetoothSystemService::begin() // Add service to BLE BLE.addService(*_service); - Serial.println(F("[BluetoothSystemService] Service initialized successfully")); return true; } @@ -112,7 +109,6 @@ void BluetoothSystemService::notifyInitialized() if (_charInitialized) { _charInitialized->writeValue((uint8_t)1); - Serial.println(F("[BluetoothSystemService] Notified clients: System Initialized")); } } diff --git a/SmartFuseBox/ConfigSyncManager.cpp b/SmartFuseBox/ConfigSyncManager.cpp index 8807ad2..f241e6d 100644 --- a/SmartFuseBox/ConfigSyncManager.cpp +++ b/SmartFuseBox/ConfigSyncManager.cpp @@ -149,7 +149,7 @@ void ConfigSyncManager::saveConfigIfChanged() if (_computerSerial) { - _computerSerial->sendError("Config synced and saved", "ConfigSync"); + _computerSerial->sendDebug("Config synced and saved", "ConfigSync"); } } else diff --git a/SmartFuseBox/SdCardLogger.cpp b/SmartFuseBox/SdCardLogger.cpp index 655972f..63c579e 100644 --- a/SmartFuseBox/SdCardLogger.cpp +++ b/SmartFuseBox/SdCardLogger.cpp @@ -1,53 +1,44 @@ #include "SdCardLogger.h" #include "DateTimeManager.h" #include "SmartFuseBoxConstants.h" +#include -SdCardLogger::SdCardLogger(MessageBus* messageBus, WarningManager* warningManager) - : _messageBus(messageBus) - , _warningManager(warningManager) - , _initialized(false) - , _sdCardPresent(false) - , _bufferHead(0) - , _bufferTail(0) - , _bufferCount(0) - , _currentDay(0) - , _lastWriteTime(0) - , _lastFileCheckTime(0) - , _totalRecordsLogged(0) - , _recordsDropped(0) - , _sdCardErrorRaised(false) +SdCardLogger::SdCardLogger(SensorCommandHandler* sensorHandler, WarningManager* warningManager, uint8_t csPin) + : _sensorHandler(sensorHandler), + _warningManager(warningManager), + _csPin(csPin), + _initialized(false), + _sdCardPresent(false), + _bufferHead(0), + _bufferTail(0), + _bufferCount(0), + _currentDay(0), + _lastWriteTime(0), + _lastFileCheckTime(0), + _lastCardPresenceCheck(0), + _totalRecordsLogged(0), + _recordsDropped(0), + _sdCardErrorRaised(false), + _sdCardMissingRaised(false) { memset(_currentFileName, 0, sizeof(_currentFileName)); } bool SdCardLogger::initialize() { - // Subscribe to MessageBus events - _messageBus->subscribe([this](float temp) - { - this->onTemperatureUpdated(temp); - }); - - _messageBus->subscribe([this](uint8_t humidity) - { - this->onHumidityUpdated(humidity); - }); - - _messageBus->subscribe([this](uint16_t level, uint16_t avgLevel) - { - this->onWaterLevelUpdated(level, avgLevel); - }); - - _messageBus->subscribe([this](bool isDayTime) - { - this->onLightSensorUpdated(isDayTime); - }); - - // Initialize SD card if (!initializeSdCard()) { - _warningManager->raiseWarning(WarningType::SdCardError); - _sdCardErrorRaised = true; + // Check if it's a card missing error or other error + if (isCardMissingError()) + { + _warningManager->raiseWarning(WarningType::SdCardMissing); + _sdCardMissingRaised = true; + } + else + { + _warningManager->raiseWarning(WarningType::SdCardError); + _sdCardErrorRaised = true; + } return false; } @@ -59,38 +50,93 @@ bool SdCardLogger::initialize() bool SdCardLogger::initializeSdCard() { - // Initialize SPI and SD card - if (!_sd.begin(SD_CHIP_SELECT_PIN)) + // Explicitly set CS pin as output (best practice, though SdFat usually handles this) + pinMode(_csPin, OUTPUT); + digitalWrite(_csPin, HIGH); // Deselect card initially + + // Explicitly initialize SPI before SD card + // SPI.begin() automatically configures: + // - MOSI (D11) as OUTPUT + // - MISO (D12) as INPUT + // - SCK (D13) as OUTPUT + SPI.begin(); + + // Small delay to allow SPI to stabilize + delay(10); + + Serial.println(F("Initializing SD card...")); + Serial.print(F("CS Pin: ")); + Serial.println(_csPin); + + // Initialize SD card with explicit CS pin and slower speed for reliability + // SD_SCK_MHZ(4) = 4MHz (try 1, 2, or 4 if having issues) + if (!_sd.begin(_csPin, SD_SCK_MHZ(4))) { + Serial.println(F("SD card initialization FAILED")); + Serial.print(F("Error code: 0x")); + Serial.println(_sd.card()->errorCode(), HEX); + Serial.print(F("Error data: 0x")); + Serial.println(_sd.card()->errorData(), HEX); return false; } + Serial.println(F("SD card initialized successfully!")); + + // Print card info for debugging + uint32_t cardSize = _sd.card()->sectorCount() * 512UL / (1024 * 1024); + Serial.print(F("Card size: ")); + Serial.print(cardSize); + Serial.println(F(" MB")); + return true; } void SdCardLogger::update(unsigned long now) { + bool checkingPresence = false; + + // Periodically check for card presence changes (insertion/removal) + if (now - _lastCardPresenceCheck >= SD_CARD_PRESENCE_CHECK_MS) + { + checkingPresence = true; + Serial.println("Checking card presence"); + checkCardPresence(now); + _lastCardPresenceCheck = now; + } + if (!_initialized || !_sdCardPresent) { + if (checkingPresence) + { + Serial.println("SD not initialized/present"); + } + return; } - + // Check for date change periodically if (now - _lastFileCheckTime >= SD_FILE_CHECK_INTERVAL_MS) { checkForDateChange(now); _lastFileCheckTime = now; } - - // Write buffered records if enough time has passed + + // Capture sensor snapshot and write to SD every second if (now - _lastWriteTime >= SD_WRITE_INTERVAL_MS) { + Serial.println("Capturing sensor snapshot"); + + // Capture current sensor state + captureSensorSnapshot(); + + // Write buffered records if (!isBufferEmpty()) { + Serial.println("Writing logs to SD"); if (writeRecordsToCard(SD_MAX_WRITES_PER_LOOP)) { _lastWriteTime = now; - + // Clear SD card error if it was raised and we can write again if (_sdCardErrorRaised) { @@ -106,6 +152,10 @@ void SdCardLogger::update(unsigned long now) } } } + else + { + _lastWriteTime = now; + } } } @@ -125,9 +175,9 @@ bool SdCardLogger::writeRecordsToCard(uint8_t maxRecords) // Write up to maxRecords from buffer while (!isBufferEmpty() && recordsWritten < maxRecords) { - const SensorDataRecord& record = _buffer[_bufferTail]; + const SensorSnapshot& snapshot = _buffer[_bufferTail]; - writeRecordToCsv(record); + writeSnapshotToCsv(snapshot); // Move tail forward (circular) _bufferTail = (_bufferTail + 1) % SD_BUFFER_SIZE; @@ -145,18 +195,14 @@ bool SdCardLogger::writeRecordsToCard(uint8_t maxRecords) return true; } -void SdCardLogger::writeRecordToCsv(const SensorDataRecord& record) +void SdCardLogger::writeSnapshotToCsv(const SensorSnapshot& snapshot) { if (!_currentFile) { return; } - // Format: Timestamp,DateTime,SensorType,Value1,Value2 - - // Timestamp (millis) - _currentFile.print(record.timestamp); - _currentFile.print(','); + // Format: Timestamp,Temp,Humidity,Bearing,CompassTemp,Speed,WaterLevel,WaterPump,GPSLat,GPSLon,Altitude,GPSCourse,GPSSats,GPSDistance,Warnings,Horn // DateTime (formatted) char dateTimeBuf[DateTimeBufferLength]; @@ -164,21 +210,98 @@ void SdCardLogger::writeRecordToCsv(const SensorDataRecord& record) _currentFile.print(dateTimeBuf); _currentFile.print(','); - // Sensor type - _currentFile.print(getSensorTypeName(record.sensorType)); + // Temperature + if (isnan(snapshot.temperature)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.temperature, 1); + _currentFile.print(','); + + // Humidity + _currentFile.print(snapshot.humidity); + _currentFile.print(','); + + // Bearing + if (isnan(snapshot.bearing)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.bearing, 1); + _currentFile.print(','); + + // CompassTemp + if (isnan(snapshot.compassTemp)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.compassTemp, 1); + _currentFile.print(','); + + // Speed + _currentFile.print(snapshot.speed); + _currentFile.print(','); + + // WaterLevel + _currentFile.print(snapshot.waterLevel); + _currentFile.print(','); + + // WaterPump (0 or 1) + _currentFile.print(snapshot.waterPumpActive ? '1' : '0'); + _currentFile.print(','); + + // GPSLat + if (isnan(snapshot.gpsLat)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.gpsLat, 6); + _currentFile.print(','); + + // GPSLon + if (isnan(snapshot.gpsLon)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.gpsLon, 6); + _currentFile.print(','); + + // Altitude + if (isnan(snapshot.altitude)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.altitude, 1); + _currentFile.print(','); + + // GPSCourse + if (isnan(snapshot.gpsCourse)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.gpsCourse, 1); + _currentFile.print(','); + + // GPSSats + _currentFile.print(snapshot.gpsSats); _currentFile.print(','); - // Value1 - _currentFile.print(record.value1, 2); + // GPSDistance + if (isnan(snapshot.gpsDistance)) + _currentFile.print('?'); + else + _currentFile.print(snapshot.gpsDistance, 2); _currentFile.print(','); - // Value2 - _currentFile.print(record.value2, 2); + // Warnings (hex format with leading zeros to 8 digits) + _currentFile.print(F("0x")); + char hexBuf[9]; + snprintf(hexBuf, sizeof(hexBuf), "%08lX", (unsigned long)snapshot.warnings); + _currentFile.print(hexBuf); + _currentFile.print(','); + + // Horn (0 or 1) + _currentFile.print(snapshot.hornActive ? '1' : '0'); + _currentFile.println(); } bool SdCardLogger::openOrCreateFile(unsigned long now) { + Serial.println("SD open or create file"); // Close existing file if open if (_currentFile) { @@ -200,7 +323,8 @@ bool SdCardLogger::openOrCreateFile(unsigned long now) // Write CSV header if new file if (!fileExists) { - _currentFile.println(F("Timestamp,DateTime,SensorType,Value1,Value2")); + Serial.println("Writing SD header"); + _currentFile.println(F("Timestamp,Temp,Humidity,Bearing,CompassTemp,Speed,WaterLevel,WaterPump,GPSLat,GPSLon,Altitude,GPSCourse,GPSSats,GPSDistance,Warnings,Horn")); _currentFile.flush(); } @@ -211,6 +335,7 @@ void SdCardLogger::closeCurrentFile() { if (_currentFile) { + Serial.println("SD closing file"); _currentFile.flush(); _currentFile.close(); } @@ -218,6 +343,7 @@ void SdCardLogger::closeCurrentFile() void SdCardLogger::updateFileName(unsigned long now) { + (void)now; // Unused parameter, but could be used for future enhancements uint16_t year = DateTimeManager::getYear(); uint8_t month = DateTimeManager::getMonth(); uint8_t day = DateTimeManager::getDay(); @@ -241,46 +367,63 @@ void SdCardLogger::checkForDateChange(unsigned long now) } } -void SdCardLogger::addRecordToBuffer(const SensorDataRecord& record) +void SdCardLogger::captureSensorSnapshot() +{ + if (!_sensorHandler) + { + return; + } + + SensorSnapshot snapshot; + + // Capture all sensor values + snapshot.temperature = _sensorHandler->getTemperature(); + snapshot.humidity = _sensorHandler->getHumidity(); + snapshot.bearing = _sensorHandler->getBearing(); + snapshot.compassTemp = _sensorHandler->getCompassTemperature(); + snapshot.speed = _sensorHandler->getSpeed(); + snapshot.waterLevel = _sensorHandler->getWaterLevel(); + snapshot.waterPumpActive = _sensorHandler->getWaterPumpActive(); + snapshot.gpsLat = _sensorHandler->getGpsLatitude(); + snapshot.gpsLon = _sensorHandler->getGpsLongitude(); + snapshot.altitude = _sensorHandler->getGpsAltitude(); + snapshot.gpsCourse = _sensorHandler->getGpsCourse(); + snapshot.gpsSats = _sensorHandler->getGpsSatellites(); + snapshot.gpsDistance = _sensorHandler->getGpsDistance(); + snapshot.warnings = _warningManager->getActiveWarningsMask(); + snapshot.hornActive = _sensorHandler->getHornActive(); + + addSnapshotToBuffer(snapshot); +} + +void SdCardLogger::addSnapshotToBuffer(const SensorSnapshot& snapshot) { if (isBufferFull()) { + Serial.println("SD buffer full"); // Buffer is full, drop oldest record (overwrite tail) _recordsDropped++; _bufferTail = (_bufferTail + 1) % SD_BUFFER_SIZE; _bufferCount--; } + Serial.println("Add to SD buffer"); // Add new record at head - _buffer[_bufferHead] = record; + _buffer[_bufferHead] = snapshot; _bufferHead = (_bufferHead + 1) % SD_BUFFER_SIZE; _bufferCount++; } -bool SdCardLogger::isBufferFull() const { +bool SdCardLogger::isBufferFull() const +{ return _bufferCount >= SD_BUFFER_SIZE; } -bool SdCardLogger::isBufferEmpty() const { +bool SdCardLogger::isBufferEmpty() const +{ return _bufferCount == 0; } -const char* SdCardLogger::getSensorTypeName(SensorDataType type) const { - switch (type) - { - case SensorDataType::Temperature: - return "Temperature"; - case SensorDataType::Humidity: - return "Humidity"; - case SensorDataType::WaterLevel: - return "WaterLevel"; - case SensorDataType::LightLevel: - return "LightLevel"; - default: - return "Unknown"; - } -} - void SdCardLogger::flush() { if ( !_initialized) @@ -297,30 +440,96 @@ void SdCardLogger::flush() closeCurrentFile(); } -// MessageBus callback implementations -void SdCardLogger::onTemperatureUpdated(float temperature) +void SdCardLogger::checkCardPresence(unsigned long now) { - SensorDataRecord record(millis(), SensorDataType::Temperature, temperature); - addRecordToBuffer(record); -} + (void)now; // Unused parameter, but could be used for future enhancements -void SdCardLogger::onHumidityUpdated(uint8_t humidity) -{ - SensorDataRecord record(millis(), SensorDataType::Humidity, static_cast(humidity)); - addRecordToBuffer(record); -} + // Try to re-initialize SD card with slower speed for reliability + bool cardPresent = _sd.begin(_csPin, SD_SCK_MHZ(4)); -void SdCardLogger::onWaterLevelUpdated(uint16_t waterLevel, uint16_t averageWaterLevel) -{ - SensorDataRecord record(millis(), SensorDataType::WaterLevel, - static_cast(waterLevel), - static_cast(averageWaterLevel)); - addRecordToBuffer(record); + // Card state changed from missing to present + if (cardPresent && !_sdCardPresent) + { + Serial.println("Card Present"); + _sdCardPresent = true; + _initialized = true; + + // Clear any SD card warnings + if (_sdCardMissingRaised) + { + _warningManager->clearWarning(WarningType::SdCardMissing); + _sdCardMissingRaised = false; + } + + if (_sdCardErrorRaised) + { + _warningManager->clearWarning(WarningType::SdCardError); + _sdCardErrorRaised = false; + } + } + // Card state changed from present to missing + else if (!cardPresent && _sdCardPresent) + { + Serial.println("Card not present"); + _sdCardPresent = false; + _initialized = false; + + // Close any open files + closeCurrentFile(); + + // Determine if card is missing or there's an error + if (isCardMissingError()) + { + // Clear error warning if it was raised + if (_sdCardErrorRaised) + { + _warningManager->clearWarning(WarningType::SdCardError); + _sdCardErrorRaised = false; + } + + // Raise missing warning if not already raised + if (!_sdCardMissingRaised) + { + _warningManager->raiseWarning(WarningType::SdCardMissing); + _sdCardMissingRaised = true; + } + } + else + { + Serial.println("Card Missing or other error"); + // Clear missing warning if it was raised + if (_sdCardMissingRaised) + { + _warningManager->clearWarning(WarningType::SdCardMissing); + _sdCardMissingRaised = false; + } + + // Raise error warning if not already raised + if (!_sdCardErrorRaised) + { + _warningManager->raiseWarning(WarningType::SdCardError); + _sdCardErrorRaised = true; + } + } + } } -void SdCardLogger::onLightSensorUpdated(bool isDayTime) +bool SdCardLogger::isCardMissingError() { - SensorDataRecord record(millis(), SensorDataType::LightLevel, - isDayTime ? 1.0f : 0.0f); - addRecordToBuffer(record); + // Check the SD card error code to determine if card is missing + // Common error codes for missing card: + // - SD_CARD_ERROR_CMD0: Card not responding to CMD0 (GO_IDLE_STATE) + // - SD_CARD_ERROR_ACMD41: Card not responding to ACMD41 (initialization) + // - 0xFF or 0x01: Card not present (no response on SPI bus) + + uint8_t errorCode = _sd.card()->errorCode(); + + Serial.print("Card error code: "); + Serial.println(errorCode); + + // Error codes that typically indicate missing card: + // 0x01 = SD_CARD_ERROR_CMD0 (card not present/responding) + // 0x02 = SD_CARD_ERROR_CMD8 (card not responding to voltage check) + // 0xFF = No card or no response + return (errorCode == 0x01 || errorCode == 0x02 || errorCode == 0xFF); } diff --git a/SmartFuseBox/SmartFuseBox.ino b/SmartFuseBox/SmartFuseBox.ino index a6540d5..4602a93 100644 --- a/SmartFuseBox/SmartFuseBox.ino +++ b/SmartFuseBox/SmartFuseBox.ino @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "SystemCpuMonitor.h" @@ -135,7 +136,7 @@ SystemNetworkHandler systemNetworkHandler(&wifiController); SensorNetworkHandler sensorNetworkHandler(&sensorController); // SD card logger -SdCardLogger sdCardLogger(&messageBus, &warningManager); +SdCardLogger sdCardLogger(&sensorCommandHandler, &warningManager, SdCardCsPin); void setup() @@ -181,7 +182,7 @@ void setup() relayHandler.configUpdated(config); sensorManager.setup(); - // Initialize SD card logger (always enabled) + // Initialize SD card logger sdCardLogger.initialize(); #if defined(ARDUINO_UNO_R4) && defined(LED_MANAGER) diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index 41d09b5..6a6b1e0 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -179,7 +179,7 @@ VisualMicroDebugger - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ false @@ -206,7 +206,7 @@ - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ c++17 gnu11 From 809585eeb85a9a87deab27933bea98f1e6089751 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 10:42:13 +0100 Subject: [PATCH 08/19] Partially updated --- SmartFuseBox/SdCardLogger.cpp | 16 +--------------- SmartFuseBox/SdCardLogger.h | 11 ++++++----- SmartFuseBox/SmartFuseBox.vcxproj | 1 - 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/SmartFuseBox/SdCardLogger.cpp b/SmartFuseBox/SdCardLogger.cpp index 63c579e..3739884 100644 --- a/SmartFuseBox/SdCardLogger.cpp +++ b/SmartFuseBox/SdCardLogger.cpp @@ -52,31 +52,17 @@ bool SdCardLogger::initializeSdCard() { // Explicitly set CS pin as output (best practice, though SdFat usually handles this) pinMode(_csPin, OUTPUT); - digitalWrite(_csPin, HIGH); // Deselect card initially + digitalWrite(_csPin, HIGH); - // Explicitly initialize SPI before SD card - // SPI.begin() automatically configures: - // - MOSI (D11) as OUTPUT - // - MISO (D12) as INPUT - // - SCK (D13) as OUTPUT SPI.begin(); // Small delay to allow SPI to stabilize delay(10); - Serial.println(F("Initializing SD card...")); - Serial.print(F("CS Pin: ")); - Serial.println(_csPin); - // Initialize SD card with explicit CS pin and slower speed for reliability // SD_SCK_MHZ(4) = 4MHz (try 1, 2, or 4 if having issues) if (!_sd.begin(_csPin, SD_SCK_MHZ(4))) { - Serial.println(F("SD card initialization FAILED")); - Serial.print(F("Error code: 0x")); - Serial.println(_sd.card()->errorCode(), HEX); - Serial.print(F("Error data: 0x")); - Serial.println(_sd.card()->errorData(), HEX); return false; } diff --git a/SmartFuseBox/SdCardLogger.h b/SmartFuseBox/SdCardLogger.h index ac49f6f..04223d1 100644 --- a/SmartFuseBox/SdCardLogger.h +++ b/SmartFuseBox/SdCardLogger.h @@ -4,10 +4,9 @@ #include #include #include "SensorDataRecord.h" -#include "MessageBus.h" +#include "SensorCommandHandler.h" #include "WarningManager.h" -constexpr uint8_t SD_CHIP_SELECT_PIN = 10; constexpr uint8_t SD_BUFFER_SIZE = 64; // Number of records to buffer constexpr uint8_t SD_MAX_WRITES_PER_LOOP = 5; // Max records to write per update() call constexpr uint16_t SD_WRITE_INTERVAL_MS = 1000; // Minimum time between write operations @@ -46,10 +45,12 @@ constexpr uint16_t SD_FILE_CHECK_INTERVAL_MS = 60000; // Check for date change e * } * @endcode */ -class SdCardLogger { +class SdCardLogger +{ private: - MessageBus* _messageBus; + SensorCommandHandler* _sensorHandler; WarningManager* _warningManager; + uint8_t _csPin; // SD card SdFat _sd; // Main SD card object @@ -101,7 +102,7 @@ class SdCardLogger { * @param messageBus Pointer to MessageBus for subscribing to events * @param warningManager Pointer to WarningManager for error reporting */ - SdCardLogger(MessageBus* messageBus, WarningManager* warningManager); + SdCardLogger(SensorCommandHandler* sensorHandler, WarningManager* warningManager, uint8_t csPin); /** * @brief Initialize SD card logger with configuration diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index 6a6b1e0..df3e36e 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -119,7 +119,6 @@ true - From 441b8924b91d7bf62c464d7cb91bad89a23b43ab Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 10:43:55 +0100 Subject: [PATCH 09/19] Fixed header after data loss --- SmartFuseBox/SdCardLogger.h | 48 ++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/SmartFuseBox/SdCardLogger.h b/SmartFuseBox/SdCardLogger.h index 04223d1..de13809 100644 --- a/SmartFuseBox/SdCardLogger.h +++ b/SmartFuseBox/SdCardLogger.h @@ -3,7 +3,6 @@ #include #include #include -#include "SensorDataRecord.h" #include "SensorCommandHandler.h" #include "WarningManager.h" @@ -11,6 +10,35 @@ constexpr uint8_t SD_BUFFER_SIZE = 64; // Number of records to buff constexpr uint8_t SD_MAX_WRITES_PER_LOOP = 5; // Max records to write per update() call constexpr uint16_t SD_WRITE_INTERVAL_MS = 1000; // Minimum time between write operations constexpr uint16_t SD_FILE_CHECK_INTERVAL_MS = 60000; // Check for date change every minute +constexpr uint16_t SD_CARD_PRESENCE_CHECK_MS = 5000; // Check for card presence every 5 seconds + +/** + * @struct SensorSnapshot + * @brief Snapshot of all sensor values at a point in time + */ +struct SensorSnapshot { + float temperature; + uint8_t humidity; + float bearing; + float compassTemp; + uint8_t speed; + uint16_t waterLevel; + bool waterPumpActive; + double gpsLat; + double gpsLon; + double altitude; + double gpsCourse; + uint32_t gpsSats; + double gpsDistance; + uint32_t warnings; + bool hornActive; + + SensorSnapshot() + : temperature(NAN), humidity(0), bearing(NAN), compassTemp(NAN), + speed(0), waterLevel(0), waterPumpActive(false), + gpsLat(NAN), gpsLon(NAN), altitude(NAN), gpsCourse(NAN), + gpsSats(0), gpsDistance(NAN), warnings(0), hornActive(false) {} +}; /** * @class SdCardLogger @@ -61,7 +89,7 @@ class SdCardLogger bool _sdCardPresent; // Circular buffer - SensorDataRecord _buffer[SD_BUFFER_SIZE]; + SensorSnapshot _buffer[SD_BUFFER_SIZE]; uint8_t _bufferHead; // Next position to write uint8_t _bufferTail; // Next position to read uint8_t _bufferCount; // Number of records in buffer @@ -71,30 +99,28 @@ class SdCardLogger // Timing unsigned long _lastWriteTime; unsigned long _lastFileCheckTime; + unsigned long _lastCardPresenceCheck; // Statistics unsigned long _totalRecordsLogged; unsigned long _recordsDropped; bool _sdCardErrorRaised; + bool _sdCardMissingRaised; // Internal methods bool initializeSdCard(); bool openOrCreateFile(unsigned long now); void closeCurrentFile(); bool writeRecordsToCard(uint8_t maxRecords); - void writeRecordToCsv(const SensorDataRecord& record); - void addRecordToBuffer(const SensorDataRecord& record); + void writeSnapshotToCsv(const SensorSnapshot& snapshot); + void captureSensorSnapshot(); + void addSnapshotToBuffer(const SensorSnapshot& snapshot); bool isBufferFull() const; bool isBufferEmpty() const; void updateFileName(unsigned long now); void checkForDateChange(unsigned long now); - const char* getSensorTypeName(SensorDataType type) const; - - // MessageBus callbacks - void onTemperatureUpdated(float temperature); - void onHumidityUpdated(uint8_t humidity); - void onWaterLevelUpdated(uint16_t waterLevel, uint16_t averageWaterLevel); - void onLightSensorUpdated(bool isDayTime); + void checkCardPresence(unsigned long now); + bool isCardMissingError(); public: /** From b86981536c5dec8f77a133d70062b1a232bcd27d Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 11:30:51 +0100 Subject: [PATCH 10/19] Working sd card --- BoatControlPanel/BoatControlPanel.ino | 2 +- Shared/CommandHandlers/AckCommandHandler.cpp | 2 + Shared/WarningManager.cpp | 62 +++++++++++++++++--- Shared/WarningManager.h | 8 +++ SmartFuseBox/SdCardLogger.cpp | 57 +++++------------- SmartFuseBox/SdCardLogger.h | 2 +- SmartFuseBox/SmartFuseBox.ino | 2 +- SmartFuseBox/SmartFuseBox.vcxproj.filters | 5 +- 8 files changed, 86 insertions(+), 54 deletions(-) diff --git a/BoatControlPanel/BoatControlPanel.ino b/BoatControlPanel/BoatControlPanel.ino index 779e7f0..c42ce22 100644 --- a/BoatControlPanel/BoatControlPanel.ino +++ b/BoatControlPanel/BoatControlPanel.ino @@ -174,7 +174,7 @@ void setup() #if defined(ARDUINO_MEGA2560) SystemFunctions::initializeSerial(NEXTION_SERIAL, 19200, false); - SystemFunctions::initializeSerial(LINK_SERIAL, 9600, false); + SystemFunctions::initializeSerial(LINK_SERIAL, 19200, false); SystemFunctions::initializeSerial(GPS_SERIAL, 9600, false); #elif defined(ARDUINO_R4_MINIMA) NEXTION_SERIAL.begin(19200); diff --git a/Shared/CommandHandlers/AckCommandHandler.cpp b/Shared/CommandHandlers/AckCommandHandler.cpp index 8a47ba7..e1987a4 100644 --- a/Shared/CommandHandlers/AckCommandHandler.cpp +++ b/Shared/CommandHandlers/AckCommandHandler.cpp @@ -116,6 +116,8 @@ void AckCommandHandler::setConfigSyncManager(ConfigSyncManager* syncManager, Con bool AckCommandHandler::processConfigAck(SerialCommandManager* sender, const char* key, const char* value) { + (void)sender; + // Handle C1 (ConfigGetSettings) acknowledgement if (strcmp(key, ConfigGetSettings) == 0 && strcmp(value, AckSuccess) == 0) { diff --git a/Shared/WarningManager.cpp b/Shared/WarningManager.cpp index 3192d48..68180ac 100644 --- a/Shared/WarningManager.cpp +++ b/Shared/WarningManager.cpp @@ -108,13 +108,28 @@ void WarningManager::raiseWarning(WarningType type) return; uint32_t warningBit = static_cast(type); + bool wasActive = (_localWarnings & warningBit) != 0; _localWarnings |= warningBit; - + + // Broadcast warning change if it's new + if (!wasActive) + { + broadcastWarningChange(type, true); + } + // Auto-raise SensorFailure for any sensor-related warning (bit 20+) if (warningBit & SENSOR_WARNING_MASK) { - _localWarnings |= static_cast(WarningType::SensorFailure); + uint32_t sensorFailureBit = static_cast(WarningType::SensorFailure); + bool sensorFailureWasActive = (_localWarnings & sensorFailureBit) != 0; + _localWarnings |= sensorFailureBit; + + // Broadcast SensorFailure if it wasn't already active + if (!sensorFailureWasActive) + { + broadcastWarningChange(WarningType::SensorFailure, true); + } } - + #if defined(BOAT_CONTROL_PANEL) updateLedStatus(); #endif @@ -126,17 +141,32 @@ void WarningManager::clearWarning(WarningType type) return; uint32_t warningBit = static_cast(type); + bool wasActive = (_localWarnings & warningBit) != 0; _localWarnings &= ~warningBit; - + + // Broadcast warning change if it was active + if (wasActive) + { + broadcastWarningChange(type, false); + } + // Auto-clear SensorFailure only if no sensor warnings remain (check bits 20+) if (warningBit & SENSOR_WARNING_MASK) { // Check if any other sensor warnings are still active (excluding SensorFailure itself) uint32_t otherSensorWarnings = _localWarnings & SENSOR_WARNING_MASK & ~static_cast(WarningType::SensorFailure); if (otherSensorWarnings == 0) { - _localWarnings &= ~static_cast(WarningType::SensorFailure); + uint32_t sensorFailureBit = static_cast(WarningType::SensorFailure); + bool sensorFailureWasActive = (_localWarnings & sensorFailureBit) != 0; + _localWarnings &= ~sensorFailureBit; + + // Broadcast SensorFailure clear if it was active + if (sensorFailureWasActive) + { + broadcastWarningChange(WarningType::SensorFailure, false); + } } } - + #if defined(BOAT_CONTROL_PANEL) updateLedStatus(); #endif @@ -228,12 +258,30 @@ uint32_t WarningManager::getRemoteWarningsMask() const void WarningManager::updateRemoteWarnings(uint32_t remoteWarningMask) { _remoteWarnings = remoteWarningMask; - + #if defined(BOAT_CONTROL_PANEL) updateLedStatus(); #endif } +void WarningManager::broadcastWarningChange(WarningType type, bool isActive) +{ + if (!_commandMgr || type == WarningType::None) + return; + + // Format warning type as hex string (e.g., "0x800") + char warningHex[12]; + uint32_t warningValue = static_cast(type); + snprintf_P(warningHex, sizeof(warningHex), PSTR("%s%lx"), HexPrefix, warningValue); + + // Format as W2:=<0|1> + char params[32]; + snprintf_P(params, sizeof(params), PSTR("%s=%d"), warningHex, isActive ? 1 : 0); + + // Send W2 command via LINK + _commandMgr->sendCommand(WarningStatus, params); +} + #if defined(BOAT_CONTROL_PANEL) void WarningManager::updateLedStatus() diff --git a/Shared/WarningManager.h b/Shared/WarningManager.h index 875217e..e4ba6eb 100644 --- a/Shared/WarningManager.h +++ b/Shared/WarningManager.h @@ -76,6 +76,14 @@ class WarningManager { */ void updateConnection(unsigned long now); + /** + * @brief Broadcast warning change to connected device via LINK. + * Sends W2 command with warning status. + * @param type The warning type that changed + * @param isActive True if warning is now active, false if cleared + */ + void broadcastWarningChange(WarningType type, bool isActive); + #if defined(BOAT_CONTROL_PANEL) void updateLedStatus(); #endif diff --git a/SmartFuseBox/SdCardLogger.cpp b/SmartFuseBox/SdCardLogger.cpp index 3739884..a0af473 100644 --- a/SmartFuseBox/SdCardLogger.cpp +++ b/SmartFuseBox/SdCardLogger.cpp @@ -66,37 +66,20 @@ bool SdCardLogger::initializeSdCard() return false; } - Serial.println(F("SD card initialized successfully!")); - - // Print card info for debugging - uint32_t cardSize = _sd.card()->sectorCount() * 512UL / (1024 * 1024); - Serial.print(F("Card size: ")); - Serial.print(cardSize); - Serial.println(F(" MB")); - return true; } void SdCardLogger::update(unsigned long now) { - bool checkingPresence = false; - // Periodically check for card presence changes (insertion/removal) if (now - _lastCardPresenceCheck >= SD_CARD_PRESENCE_CHECK_MS) { - checkingPresence = true; - Serial.println("Checking card presence"); - checkCardPresence(now); + checkCardPresence(); _lastCardPresenceCheck = now; } if (!_initialized || !_sdCardPresent) { - if (checkingPresence) - { - Serial.println("SD not initialized/present"); - } - return; } @@ -110,15 +93,12 @@ void SdCardLogger::update(unsigned long now) // Capture sensor snapshot and write to SD every second if (now - _lastWriteTime >= SD_WRITE_INTERVAL_MS) { - Serial.println("Capturing sensor snapshot"); - // Capture current sensor state captureSensorSnapshot(); // Write buffered records if (!isBufferEmpty()) { - Serial.println("Writing logs to SD"); if (writeRecordsToCard(SD_MAX_WRITES_PER_LOOP)) { _lastWriteTime = now; @@ -287,7 +267,6 @@ void SdCardLogger::writeSnapshotToCsv(const SensorSnapshot& snapshot) bool SdCardLogger::openOrCreateFile(unsigned long now) { - Serial.println("SD open or create file"); // Close existing file if open if (_currentFile) { @@ -309,7 +288,6 @@ bool SdCardLogger::openOrCreateFile(unsigned long now) // Write CSV header if new file if (!fileExists) { - Serial.println("Writing SD header"); _currentFile.println(F("Timestamp,Temp,Humidity,Bearing,CompassTemp,Speed,WaterLevel,WaterPump,GPSLat,GPSLon,Altitude,GPSCourse,GPSSats,GPSDistance,Warnings,Horn")); _currentFile.flush(); } @@ -321,7 +299,6 @@ void SdCardLogger::closeCurrentFile() { if (_currentFile) { - Serial.println("SD closing file"); _currentFile.flush(); _currentFile.close(); } @@ -386,14 +363,12 @@ void SdCardLogger::addSnapshotToBuffer(const SensorSnapshot& snapshot) { if (isBufferFull()) { - Serial.println("SD buffer full"); // Buffer is full, drop oldest record (overwrite tail) _recordsDropped++; _bufferTail = (_bufferTail + 1) % SD_BUFFER_SIZE; _bufferCount--; } - Serial.println("Add to SD buffer"); // Add new record at head _buffer[_bufferHead] = snapshot; _bufferHead = (_bufferHead + 1) % SD_BUFFER_SIZE; @@ -426,17 +401,14 @@ void SdCardLogger::flush() closeCurrentFile(); } -void SdCardLogger::checkCardPresence(unsigned long now) +void SdCardLogger::checkCardPresence() { - (void)now; // Unused parameter, but could be used for future enhancements - // Try to re-initialize SD card with slower speed for reliability bool cardPresent = _sd.begin(_csPin, SD_SCK_MHZ(4)); // Card state changed from missing to present if (cardPresent && !_sdCardPresent) { - Serial.println("Card Present"); _sdCardPresent = true; _initialized = true; @@ -453,17 +425,19 @@ void SdCardLogger::checkCardPresence(unsigned long now) _sdCardErrorRaised = false; } } - // Card state changed from present to missing - else if (!cardPresent && _sdCardPresent) + // Card is not present (either just removed or still missing) + else if (!cardPresent) { - Serial.println("Card not present"); + // Close any open files if card was previously present + if (_sdCardPresent) + { + closeCurrentFile(); + } + _sdCardPresent = false; _initialized = false; - // Close any open files - closeCurrentFile(); - - // Determine if card is missing or there's an error + // Determine if card is missing or there's another error if (isCardMissingError()) { // Clear error warning if it was raised @@ -473,7 +447,7 @@ void SdCardLogger::checkCardPresence(unsigned long now) _sdCardErrorRaised = false; } - // Raise missing warning if not already raised + // Raise/maintain missing warning if (!_sdCardMissingRaised) { _warningManager->raiseWarning(WarningType::SdCardMissing); @@ -482,7 +456,6 @@ void SdCardLogger::checkCardPresence(unsigned long now) } else { - Serial.println("Card Missing or other error"); // Clear missing warning if it was raised if (_sdCardMissingRaised) { @@ -490,7 +463,7 @@ void SdCardLogger::checkCardPresence(unsigned long now) _sdCardMissingRaised = false; } - // Raise error warning if not already raised + // Raise/maintain error warning for non-missing errors if (!_sdCardErrorRaised) { _warningManager->raiseWarning(WarningType::SdCardError); @@ -498,6 +471,7 @@ void SdCardLogger::checkCardPresence(unsigned long now) } } } + // Card present and state unchanged - no action needed } bool SdCardLogger::isCardMissingError() @@ -510,9 +484,6 @@ bool SdCardLogger::isCardMissingError() uint8_t errorCode = _sd.card()->errorCode(); - Serial.print("Card error code: "); - Serial.println(errorCode); - // Error codes that typically indicate missing card: // 0x01 = SD_CARD_ERROR_CMD0 (card not present/responding) // 0x02 = SD_CARD_ERROR_CMD8 (card not responding to voltage check) diff --git a/SmartFuseBox/SdCardLogger.h b/SmartFuseBox/SdCardLogger.h index de13809..ac4a043 100644 --- a/SmartFuseBox/SdCardLogger.h +++ b/SmartFuseBox/SdCardLogger.h @@ -119,7 +119,7 @@ class SdCardLogger bool isBufferEmpty() const; void updateFileName(unsigned long now); void checkForDateChange(unsigned long now); - void checkCardPresence(unsigned long now); + void checkCardPresence(); bool isCardMissingError(); public: diff --git a/SmartFuseBox/SmartFuseBox.ino b/SmartFuseBox/SmartFuseBox.ino index 4602a93..146ddf3 100644 --- a/SmartFuseBox/SmartFuseBox.ino +++ b/SmartFuseBox/SmartFuseBox.ino @@ -144,7 +144,7 @@ void setup() // Serial initialization is performed first to ensure that any logging or error messages // from DateTimeManager or ConfigManager during initialization are properly output. SystemFunctions::initializeSerial(COMPUTER_SERIAL, 115200, true); - SystemFunctions::initializeSerial(LINK_SERIAL, 9600, true); + SystemFunctions::initializeSerial(LINK_SERIAL, 19200, true); DateTimeManager::setDateTime(); diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index 0804dbf..bed5360 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -193,6 +193,9 @@ Source Files + + Source Files + @@ -363,7 +366,7 @@ Header Files - + Header Files From 775dedc3b51d4219ea3df1b59ad75003cf8e420c Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 11:54:48 +0100 Subject: [PATCH 11/19] card loader config --- SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md | 355 +++++++++ SmartFuseBox/SD_CONFIG_README.md | 339 +++++++++ .../SmartFuseBox/SdCardConfigLoader.cpp | 709 ++++++++++++++++++ .../SmartFuseBox/SdCardConfigLoader.h | 136 ++++ SmartFuseBox/config.txt | 146 ++++ 5 files changed, 1685 insertions(+) create mode 100644 SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md create mode 100644 SmartFuseBox/SD_CONFIG_README.md create mode 100644 SmartFuseBox/SmartFuseBox/SdCardConfigLoader.cpp create mode 100644 SmartFuseBox/SmartFuseBox/SdCardConfigLoader.h create mode 100644 SmartFuseBox/config.txt diff --git a/SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md b/SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md new file mode 100644 index 0000000..5614d6b --- /dev/null +++ b/SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md @@ -0,0 +1,355 @@ +# SD Card Configuration Loader - Manual Implementation Guide + +## Overview +This guide lists all manual edits required to complete the SD Card Configuration Loader implementation. The following files have been automatically created: +- ✅ `SmartFuseBox/SdCardConfigLoader.h` +- ✅ `SmartFuseBox/SdCardConfigLoader.cpp` +- ✅ `SD_CONFIG_README.md` + +The following files require manual edits due to whitespace matching issues with the editing tool. + +--- + +## Manual Edits Required + +### 1. Shared/SystemDefinitions.h +**Location**: After line 74 (after `constexpr char ControlPanelTones[] = "C28";`) + +**Add**: +```cpp +constexpr char ConfigReloadFromSd[] = "C29"; +constexpr char ConfigExportToSd[] = "C30"; +``` + +--- + +### 2. Shared/CommandHandlers/ConfigCommandHandler.h +**Location**: Update the class declaration + +**Change**: +```cpp +// Forward declarations +class BluetoothController; +class ConfigSyncManager; + +class ConfigCommandHandler : public BaseCommandHandler +{ +private: + WifiController* _wifiController; + ConfigController* _configController; + ConfigSyncManager* _configSyncManager; + +public: + explicit ConfigCommandHandler(WifiController* wifiController, ConfigController* configController); + + void setConfigSyncManager(ConfigSyncManager* syncManager); +``` + +**To**: +```cpp +// Forward declarations +class BluetoothController; +class ConfigSyncManager; +class SdCardConfigLoader; // ADD THIS LINE + +class ConfigCommandHandler : public BaseCommandHandler +{ +private: + WifiController* _wifiController; + ConfigController* _configController; + ConfigSyncManager* _configSyncManager; + SdCardConfigLoader* _sdCardConfigLoader; // ADD THIS LINE + +public: + explicit ConfigCommandHandler(WifiController* wifiController, ConfigController* configController); + + void setConfigSyncManager(ConfigSyncManager* syncManager); + void setSdCardConfigLoader(SdCardConfigLoader* sdCardConfigLoader); // ADD THIS LINE +``` + +--- + +### 3. Shared/CommandHandlers/ConfigCommandHandler.cpp + +#### 3a. Add include at top +**Location**: After `#include "ConfigSyncManager.h"` + +**Add**: +```cpp +#include "SdCardConfigLoader.h" +``` + +#### 3b. Update constructor +**Location**: Lines 10-14 + +**Change**: +```cpp +ConfigCommandHandler::ConfigCommandHandler(WifiController* wifiController, ConfigController* configController) + : _wifiController(wifiController), + _configController(configController), + _configSyncManager(nullptr) +{ +} +``` + +**To**: +```cpp +ConfigCommandHandler::ConfigCommandHandler(WifiController* wifiController, ConfigController* configController) + : _wifiController(wifiController), + _configController(configController), + _configSyncManager(nullptr), + _sdCardConfigLoader(nullptr) // ADD THIS LINE +{ +} +``` + +#### 3c. Add setter method +**Location**: After `setConfigSyncManager` method (around line 19) + +**Add**: +```cpp +void ConfigCommandHandler::setSdCardConfigLoader(SdCardConfigLoader* sdCardConfigLoader) +{ + _sdCardConfigLoader = sdCardConfigLoader; +} +``` + +#### 3d. Add C29 and C30 command handlers +**Location**: After the C28 (ControlPanelTones) handler and before the `else` clause (around line 635) + +**Add**: +```cpp + else if (strcmp(command, "C29") == 0) // ConfigReloadFromSd + { + // C29 - Reload config from SD card + if (_sdCardConfigLoader) + { + if (_sdCardConfigLoader->reloadConfigFromSd()) + { + result = ConfigResult::Success; + } + else + { + sendAckErr(sender, command, F("Failed to reload from SD")); + return true; + } + } + else + { + sendAckErr(sender, command, F("SD config loader not available")); + return true; + } + } + else if (strcmp(command, "C30") == 0) // ConfigExportToSd + { + // C30 - Export current config to SD card + if (_sdCardConfigLoader) + { + if (_sdCardConfigLoader->exportConfigToSd()) + { + result = ConfigResult::Success; + } + else + { + sendAckErr(sender, command, F("Failed to export to SD")); + return true; + } + } + else + { + sendAckErr(sender, command, F("SD config loader not available")); + return true; + } + } +``` + +#### 3e. Update supportedCommands method +**Location**: Around line 693 + +**Change**: +```cpp +const char* const* ConfigCommandHandler::supportedCommands(size_t& count) const +{ + static const char* cmds[] = { + ConfigSaveSettings, ConfigGetSettings, ConfigResetSettings, + ConfigRename, ConfigRenameRelay, ConfigMapHomeButton, ConfigSetButtonColor, + ConfigBoatType, ConfigSoundRelayId, ConfigSoundStartDelay, +#if defined(ARDUINO_UNO_R4) + ConfigBluetoothEnable, ConfigWifiEnable, ConfigWifiMode, ConfigWifiSSID, + ConfigWifiPassword, ConfigWifiPort, ConfigWifiState, ConfigWifiApIpAddress, +#endif + ConfigDefaultRelayState, ConfigLinkRelays, + ConfigTimeZoneOffset, ConfigMmsi, ConfigCallSign, ConfigHomePort, + ConfigLedColor, ConfigLedBrightness, ConfigLedAutoSwitch, ConfigLedEnable, + ControlPanelTones + }; + count = sizeof(cmds) / sizeof(cmds[0]); + return cmds; +} +``` + +**To**: +```cpp +const char* const* ConfigCommandHandler::supportedCommands(size_t& count) const +{ + static const char* cmds[] = { + ConfigSaveSettings, ConfigGetSettings, ConfigResetSettings, + ConfigRename, ConfigRenameRelay, ConfigMapHomeButton, ConfigSetButtonColor, + ConfigBoatType, ConfigSoundRelayId, ConfigSoundStartDelay, +#if defined(ARDUINO_UNO_R4) + ConfigBluetoothEnable, ConfigWifiEnable, ConfigWifiMode, ConfigWifiSSID, + ConfigWifiPassword, ConfigWifiPort, ConfigWifiState, ConfigWifiApIpAddress, +#endif + ConfigDefaultRelayState, ConfigLinkRelays, + ConfigTimeZoneOffset, ConfigMmsi, ConfigCallSign, ConfigHomePort, + ConfigLedColor, ConfigLedBrightness, ConfigLedAutoSwitch, ConfigLedEnable, + ControlPanelTones, + "C29", "C30" // ADD THIS LINE + }; + count = sizeof(cmds) / sizeof(cmds[0]); + return cmds; +} +``` + +--- + +### 4. SmartFuseBox.ino + +#### 4a. Add include +**Location**: After `#include "SdCardLogger.h"` (around line 54) + +**Add**: +```cpp +#include "SdCardConfigLoader.h" +``` + +#### 4b. Add SD card config loader instantiation +**Location**: After the SD card logger instantiation (around line 141) + +**Add**: +```cpp +// SD card config loader +SdCardConfigLoader sdCardConfigLoader(&commandMgrComputer, &commandMgrLink, &configController, &configSyncManager, SdCardCsPin); +``` + +#### 4c. Link config loader to config handler +**Location**: In setup() function, after `configHandler.setConfigSyncManager(&configSyncManager);` (around line 177) + +**Add**: +```cpp + configHandler.setSdCardConfigLoader(&sdCardConfigLoader); +``` + +#### 4d. Load SD config at boot +**Location**: In setup() function, after `sdCardLogger.initialize();` (around line 188) + +**Add**: +```cpp + // Load config from SD card if present (this will disable ConfigSyncManager if SD config found) + bool sdConfigLoaded = sdCardConfigLoader.loadConfigFromSd(); +``` + +#### 4e. Conditional config sync +**Location**: In setup() function, replace the line `configSyncManager.requestSync();` (around line 202) + +**Change**: +```cpp + configSyncManager.requestSync(); +``` + +**To**: +```cpp + // Only request config sync if SD config was not loaded + if (!sdConfigLoaded) + { + configSyncManager.requestSync(); + } +``` + +--- + +### 5. Commands.md + +#### 5a. Add C29 and C30 commands +**Location**: After C28 in the Configuration Commands table + +**Add**: +```markdown +| `C29` — Reload Config from SD (SFB) | `C29` | Reload configuration from SD card config.txt file. If successful, applies all settings from SD card, saves to EEPROM, and syncs to control panel via LINK. ConfigSyncManager is disabled when SD config is active. No params. Returns error if SD card not present or config file invalid. | +| `C30` — Export Config to SD (SFB) | `C30` | Export current in-memory configuration to SD card config.txt file. Creates a new file with all current settings in command format, suitable for editing and reloading. Overwrites existing config.txt if present. No params. Returns error if SD card not present or write fails. | +``` + +#### 5b. Update error responses +**Location**: In the "Common error responses" section (after C28) + +**Change**: +```markdown +Common error responses you may see: `Missing param`, `Missing params`, `Missing name`, `Empty name`, `Index out of range`, `Button out of range`, `Slot out of range`, `Relay out of range (or 255 to clear)`, `Invalid value (0 or 1)`, `Invalid mode (0=AP, 1=Client)`, `Only available in Client mode`, `Invalid port`, `Invalid offset (-12 to +14)`, `MMSI must be 9 digits`, `Invalid boat type`, `No available link slots`, `EEPROM commit failed`, `Unknown config command`, `Invalid type (0=day, 1=night)`, `Brightness must be 0-100`, `Invalid value (true/false or 0/1)`, `Missing params (t,r,g,b)`, `Missing params (t,b)`, `Missing params (g,w,s)`. +``` + +**To**: +```markdown +Common error responses you may see: `Missing param`, `Missing params`, `Missing name`, `Empty name`, `Index out of range`, `Button out of range`, `Slot out of range`, `Relay out of range (or 255 to clear)`, `Invalid value (0 or 1)`, `Invalid mode (0=AP, 1=Client)`, `Only available in Client mode`, `Invalid port`, `Invalid offset (-12 to +14)`, `MMSI must be 9 digits`, `Invalid boat type`, `No available link slots`, `EEPROM commit failed`, `Unknown config command`, `Invalid type (0=day, 1=night)`, `Brightness must be 0-100`, `Invalid value (true/false or 0/1)`, `Missing params (t,r,g,b)`, `Missing params (t,b)`, `Missing params (g,w,s)`, `Failed to reload from SD`, `Failed to export to SD`, `SD config loader not available`. +``` + +--- + +## Testing Checklist + +After making all manual edits, test the following: + +### Compilation +- [ ] Project compiles without errors +- [ ] No warnings related to SD config loader + +### Boot Sequence (No SD Card) +- [ ] System boots normally +- [ ] EEPROM config is loaded +- [ ] ConfigSyncManager is enabled +- [ ] Serial shows: "SD card not present or not accessible" + +### Boot Sequence (With SD Card + config.txt) +- [ ] System boots and loads SD config +- [ ] EEPROM is updated with SD config +- [ ] ConfigSyncManager is disabled +- [ ] Control panel receives config via LINK +- [ ] Serial shows: "SD config loaded: X commands applied, Y errors" + +### C29 Command +- [ ] C29 reloads config from SD card +- [ ] Changes are applied and saved to EEPROM +- [ ] Control panel is synced +- [ ] Returns ACK:C29=ok on success + +### C30 Command +- [ ] C30 exports current config to SD card +- [ ] config.txt is created/overwritten +- [ ] File format is correct and parseable +- [ ] Returns ACK:C30=ok on success + +### Error Handling +- [ ] Parse errors are logged to Serial +- [ ] Invalid commands don't crash the system +- [ ] Missing SD card returns appropriate error +- [ ] Missing config.txt falls back to EEPROM + +--- + +## Summary + +**Files Created**: +- `SmartFuseBox/SdCardConfigLoader.h` +- `SmartFuseBox/SdCardConfigLoader.cpp` +- `SD_CONFIG_README.md` +- `SD_CONFIG_IMPLEMENTATION.md` (this file) + +**Files Requiring Manual Edits**: +1. `Shared/SystemDefinitions.h` - Add C29/C30 constants +2. `Shared/CommandHandlers/ConfigCommandHandler.h` - Add SD loader member and setter +3. `Shared/CommandHandlers/ConfigCommandHandler.cpp` - Add C29/C30 handlers +4. `SmartFuseBox.ino` - Integrate SD loader into boot sequence +5. `Commands.md` - Document C29/C30 commands + +**Total Manual Edits**: 11 locations across 5 files + +For detailed usage instructions and examples, see `SD_CONFIG_README.md`. diff --git a/SmartFuseBox/SD_CONFIG_README.md b/SmartFuseBox/SD_CONFIG_README.md new file mode 100644 index 0000000..7c70a54 --- /dev/null +++ b/SmartFuseBox/SD_CONFIG_README.md @@ -0,0 +1,339 @@ +# SD Card Configuration Loader + +## Overview +The SD Card Configuration Loader provides a robust, plug-and-play configuration management system for the SmartFuseBox. It allows users to preconfigure settings on an SD card and automatically apply them at boot, with synchronization to connected control panels. + +## Features +- ✅ **Plug-and-Play**: Insert SD card with `config.txt` and settings are automatically applied +- ✅ **EEPROM Persistence**: SD config is saved to EEPROM for retention after card removal +- ✅ **LINK Synchronization**: Automatically syncs configuration to control panel via LINK +- ✅ **ConfigSyncManager Integration**: Automatically disables ConfigSyncManager when SD config is present +- ✅ **Error Logging**: Comprehensive error reporting via Serial +- ✅ **Command Support**: C29 (reload) and C30 (export) commands for runtime management +- ✅ **Validation**: Full command validation before applying changes +- ✅ **Human-Readable Format**: Easy to edit configuration file + +## Boot Sequence +1. **Check SD Card**: System checks for SD card presence +2. **Load config.txt**: If found, reads and parses all configuration commands +3. **Validate**: Each command is validated before application +4. **Apply Changes**: Configuration is applied to in-memory config +5. **Save to EEPROM**: If changes were made, saves to EEPROM for persistence +6. **Sync via LINK**: Sends configuration to control panel +7. **Disable ConfigSyncManager**: Prevents periodic sync requests from control panel + +If no SD card or config.txt is present, the system boots normally using EEPROM settings and ConfigSyncManager remains enabled. + +## Configuration File Format + +### File Location +- **Filename**: `config.txt` +- **Location**: Root directory of SD card +- **Encoding**: Plain text (ASCII/UTF-8) + +### File Format +The configuration file uses the same command format as the serial protocol: +``` +# Comments start with # +# Empty lines are ignored + +# Boat identification +C3:Sea Wolf + +# Relay names (short|long format) +C4:0=Nav|Navigation +C4:1=Bilge|Bilge Pump +C4:2=Light|Cabin Lights +C4:3=Pump|Water Pump +C4:4=Horn|Sound Horn +C4:5=Fan|Ventilation +C4:6=Spare|Spare 6 +C4:7=Spare|Spare 7 + +# Home button mappings (slot=relay) +C5:0=0 +C5:1=1 +C5:2=2 +C5:3=3 + +# Button colors (button=color_index) +C6:0=4 +C6:1=4 +C6:2=4 +C6:3=4 +C6:4=4 +C6:5=4 +C6:6=4 +C6:7=4 + +# Vessel type (0=Motor, 1=Sail, 2=Fishing, 3=Yacht) +C7:v=0 + +# Sound relay (255=unmapped) +C8:v=255 + +# Sound delay (milliseconds) +C9:v=244 + +# Bluetooth enabled (0=off, 1=on) +C10:v=0 + +# WiFi enabled (0=off, 1=on) +C11:v=1 + +# WiFi mode (0=AP, 1=Client) +C12:v=0 + +# WiFi SSID (no v= prefix) +C13:SmartFuseBox + +# WiFi password (no v= prefix) +C14:sfb-776064 + +# WiFi port +C15:v=80 + +# WiFi AP IP address (no v= prefix) +C17:192.168.4.1 + +# Default relay states (relay=state, 0=off, 1=on) +C18:0=0 +C18:1=0 +C18:2=0 +C18:3=0 +C18:4=0 +C18:5=0 +C18:6=0 +C18:7=0 + +# Linked relays (relay1=relay2, 255=unlink) +C19:255=255 +C19:255=255 + +# Timezone offset (hours from UTC) +C20:v=0 + +# MMSI (9 digits) +C21:000000000 + +# Call sign (no v= prefix, can be empty) +C22: + +# Home port (no v= prefix, can be empty) +C23: + +# LED colors (t=type[0=day,1=night];c=colorset[0=good,1=bad];r=red;g=green;b=blue) +C24:t=0;c=0;r=0;g=80;b=255 +C24:t=0;c=1;r=255;g=140;b=0 +C24:t=1;c=0;r=100;g=0;b=0 +C24:t=1;c=1;r=255;g=50;b=0 + +# LED brightness (t=type[0=day,1=night];b=brightness[0-100]) +C25:t=0;b=80 +C25:t=1;b=20 + +# LED auto switch (0=off, 1=on) +C26:v=1 + +# LED enable states (g=GPS;w=Warning;s=System) +C27:g=1;w=1;s=1 + +# Control panel tones (t=type[0=good,1=bad];h=Hz;d=duration;p=preset;r=repeat) +C28:t=0;h=1000;d=100;p=1;r=0 +C28:t=1;h=400;d=500;p=5;r=30000 +``` + +### Command Reference +See [Commands.md](Commands.md) for detailed documentation on each command format and parameters. + +## Usage + +### Initial Setup +1. Create a `config.txt` file with your desired settings +2. Copy the file to the root directory of a FAT32-formatted SD card +3. Insert the SD card into the SmartFuseBox +4. Power on the system +5. Monitor Serial output for confirmation: `SD_CFG_INFO: SD config loaded: X commands applied, Y errors` + +### Editing Configuration +1. Power off the system +2. Remove the SD card +3. Edit `config.txt` on your computer +4. Insert the SD card back into the SmartFuseBox +5. Power on (configuration will be reloaded automatically) + +**OR** use the C29 command to reload without power cycle: +``` +C29 +``` + +### Exporting Current Configuration +To save your current settings to SD card: +``` +C30 +``` +This creates/overwrites `config.txt` with all current settings. + +### Removing SD Card Configuration +If you want to go back to control panel-driven configuration: +1. Power off the system +2. Remove the SD card (or delete/rename `config.txt`) +3. Power on the system +4. ConfigSyncManager will be re-enabled automatically +5. Control panel can now manage configuration via C1 sync requests + +## Commands + +### C29 — Reload Config from SD +**Format**: `C29` +**Parameters**: None +**Purpose**: Reload configuration from SD card `config.txt` file + +**Behavior**: +- Checks for SD card presence +- Reads and parses `config.txt` +- Applies all valid commands +- Saves changes to EEPROM +- Syncs to control panel via LINK +- Returns `ACK:C29=ok` on success + +**Errors**: +- `Failed to reload from SD` - SD card not present, file not found, or parse errors +- `SD config loader not available` - SD card logger not initialized + +### C30 — Export Config to SD +**Format**: `C30` +**Parameters**: None +**Purpose**: Export current configuration to SD card `config.txt` file + +**Behavior**: +- Checks for SD card presence +- Creates/overwrites `config.txt` +- Writes all current settings in command format +- Includes header comment with timestamp +- Returns `ACK:C30=ok` on success + +**Errors**: +- `Failed to export to SD` - SD card not present or write failed +- `SD config loader not available` - SD card logger not initialized + +## Error Handling + +### Parse Errors +If the config loader encounters an error parsing a command: +- Error is logged to Serial: `SD_CFG_ERROR: Command failed: (result=)` +- The problematic line is logged: `SD_CFG_LINE: ` +- Processing continues with remaining commands +- Partial configuration may be applied + +### SD Card Issues +If SD card is not present or unreadable: +- System boots using EEPROM configuration +- ConfigSyncManager remains enabled +- Info logged: `SD card not present or not accessible` + +### Missing Config File +If SD card is present but `config.txt` is not found: +- System boots using EEPROM configuration +- ConfigSyncManager remains enabled +- Info logged: `Config file not found on SD card` + +## Integration with ConfigSyncManager + +The SD Card Config Loader intelligently manages ConfigSyncManager: + +**When SD config is loaded**: +- `ConfigSyncManager.setEnabled(false)` is called +- Periodic C1 sync requests from control panel are disabled +- SD card becomes the authoritative configuration source +- Control panel receives config via LINK but doesn't initiate syncs + +**When SD config is NOT loaded**: +- ConfigSyncManager remains enabled +- Control panel can request config sync via C1 +- Bidirectional configuration management is active + +This prevents conflicts between SD card configuration and control panel-driven configuration. + +## Best Practices + +### File Management +- ✅ Keep a backup copy of your `config.txt` on your computer +- ✅ Use meaningful relay names in your configuration +- ✅ Add comments to document your configuration choices +- ✅ Test configuration changes incrementally +- ✅ Use C30 to export working configurations as templates + +### Configuration Workflow +1. **Initial Setup**: Use C30 to export default config as starting point +2. **Customize**: Edit exported config on computer +3. **Test**: Load config with SD card, verify behavior +4. **Iterate**: Make adjustments, reload with C29 +5. **Deploy**: Copy final config to production SD cards + +### Troubleshooting +- Enable Serial monitor to view detailed loading logs +- Check for parse errors in Serial output +- Verify command format matches documentation +- Ensure SD card is FAT32 formatted +- Confirm `config.txt` is in root directory (not in subfolder) +- Use C1 command to verify applied configuration + +## Technical Details + +### File Reading +- Line-by-line parsing (max 128 characters per line) +- Comments (#) and empty lines are skipped +- Whitespace and newlines are automatically trimmed +- Commands are validated before application + +### Memory Management +- Minimal memory footprint during parsing +- No large buffers or caching +- Immediate application of each command +- Single-pass file reading + +### SD Card Compatibility +- SdFat library used for robust SD card access +- Supports FAT16/FAT32 file systems +- Compatible with standard SD and SDHC cards +- Chip select pin: D10 (SdCardCsPin) + +### Performance +- Boot time impact: ~1-2 seconds for typical config file +- C29 reload: ~1-2 seconds +- C30 export: ~500ms-1 second +- No impact on runtime performance after boot + +## Examples + +### Minimal Configuration +``` +# Minimal config - just boat name and one relay +C3:My Boat +C4:0=Bilge|Bilge Pump +C18:0=0 +``` + +### Advanced Configuration +See the full example at the top of this document. + +### Multiple Profiles +Create different config files for different scenarios: +- `config_default.txt` - Standard configuration +- `config_winter.txt` - Winter storage settings +- `config_race.txt` - Racing configuration + +Rename the desired profile to `config.txt` before boot. + +## Related Documentation +- [Commands.md](Commands.md) - Complete command reference +- [CONFIG_SYNC_README.md](CONFIG_SYNC_README.md) - ConfigSyncManager documentation +- [SdCardLogger.h](SmartFuseBox/SdCardLogger.h) - SD card logging functionality + +## Version History +- v1.0.0 - Initial implementation + - SD card config loading at boot + - C29 reload command + - C30 export command + - ConfigSyncManager integration diff --git a/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.cpp b/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.cpp new file mode 100644 index 0000000..d941b8d --- /dev/null +++ b/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.cpp @@ -0,0 +1,709 @@ +#include "SdCardConfigLoader.h" +#include "ConfigManager.h" +#include + +SdCardConfigLoader::SdCardConfigLoader(SerialCommandManager* computerSerial, + SerialCommandManager* linkSerial, + ConfigController* configController, + ConfigSyncManager* configSyncManager, + uint8_t csPin) + : _computerSerial(computerSerial), + _linkSerial(linkSerial), + _configController(configController), + _configSyncManager(configSyncManager), + _csPin(csPin), + _sdConfigPresent(false) +{ +} + +bool SdCardConfigLoader::checkSdCard() +{ + if (!_sd.begin(_csPin, SD_SCK_MHZ(50))) + { + return false; + } + return true; +} + +bool SdCardConfigLoader::configFileExists() +{ + return _sd.exists(SD_CONFIG_FILENAME); +} + +bool SdCardConfigLoader::applyConfigCommand(const char* line) +{ + // Skip empty lines and comments + if (line == nullptr || line[0] == '\0' || line[0] == '#' || line[0] == '\r' || line[0] == '\n') + { + return true; + } + + // Create a mutable copy for parsing + char buffer[SD_CONFIG_MAX_LINE_LENGTH]; + strncpy(buffer, line, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + // Trim whitespace and newlines + char* end = buffer + strlen(buffer) - 1; + while (end > buffer && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) + { + *end = '\0'; + end--; + } + + // Parse command using SerialCommandManager's parser + // Format: "CMD:param1=value1;param2=value2" + char* colonPos = strchr(buffer, ':'); + char command[8] = {0}; + + if (colonPos != nullptr) + { + size_t cmdLen = colonPos - buffer; + if (cmdLen >= sizeof(command)) + { + logError("Command too long", line); + return false; + } + strncpy(command, buffer, cmdLen); + command[cmdLen] = '\0'; + } + else + { + // No colon means command with no params (like C0, C1, C2) + strncpy(command, buffer, sizeof(command) - 1); + } + + // Parse parameters manually since we're simulating command input + const char* paramsStr = colonPos ? (colonPos + 1) : ""; + + // Use the computer serial's command parser to validate and route the command + // We'll construct a synthetic command and feed it through the handler chain + + // Parse parameters into key-value pairs + StringKeyValue params[10]; + uint8_t paramCount = 0; + + if (paramsStr && *paramsStr) + { + char paramBuffer[SD_CONFIG_MAX_LINE_LENGTH]; + strncpy(paramBuffer, paramsStr, sizeof(paramBuffer) - 1); + paramBuffer[sizeof(paramBuffer) - 1] = '\0'; + + // Parse semicolon-separated parameters + char* savePtr1 = nullptr; + char* param = strtok_r(paramBuffer, ";", &savePtr1); + + while (param && paramCount < 10) + { + // Each param can be "key=value" or just "value" + char* equalPos = strchr(param, '='); + if (equalPos) + { + *equalPos = '\0'; + params[paramCount].key = param; + params[paramCount].value = equalPos + 1; + } + else + { + // For commands like C13:SSID, the whole thing after : is the value + // with an implied key (often "v" or empty) + params[paramCount].key = ""; + params[paramCount].value = param; + } + paramCount++; + param = strtok_r(nullptr, ";", &savePtr1); + } + } + + // Now apply the command through ConfigController + // We bypass the SerialCommandManager and call ConfigController methods directly + + ConfigResult result = ConfigResult::InvalidCommand; + + if (strcmp(command, "C3") == 0 && paramCount >= 1) + { + result = _configController->rename(params[0].value); + } + else if (strcmp(command, "C4") == 0 && paramCount >= 1) + { + uint8_t idx = static_cast(strtoul(params[0].key, nullptr, 0)); + result = _configController->renameRelay(idx, params[0].value); + } + else if (strcmp(command, "C5") == 0 && paramCount >= 1) + { + uint8_t button = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t relay = static_cast(strtoul(params[0].value, nullptr, 0)); + result = _configController->mapHomeButton(button, relay); + } + else if (strcmp(command, "C6") == 0 && paramCount >= 1) + { + uint8_t button = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t color = static_cast(strtoul(params[0].value, nullptr, 0)); + result = _configController->mapHomeButtonColor(button, color); + } + else if (strcmp(command, "C7") == 0 && paramCount >= 1) + { + uint8_t type = atoi(params[0].value); + result = _configController->setVesselType(type); + } + else if (strcmp(command, "C8") == 0 && paramCount >= 1) + { + uint8_t relay = atoi(params[0].value); + result = _configController->setSoundRelayButton(relay); + } + else if (strcmp(command, "C9") == 0 && paramCount >= 1) + { + uint16_t delay = atoi(params[0].value); + result = _configController->setsoundDelayStart(delay); + } +#if defined(ARDUINO_UNO_R4) + else if (strcmp(command, "C10") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setBluetoothEnabled(enabled); + } + else if (strcmp(command, "C11") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setWifiEnabled(enabled); + } + else if (strcmp(command, "C12") == 0 && paramCount >= 1) + { + uint8_t mode = atoi(params[0].value); + result = _configController->setWifiAccessMode(mode); + } + else if (strcmp(command, "C13") == 0 && paramCount >= 1) + { + // For C13, the entire string after : is the SSID (no v= prefix in file) + // We need to find original param value from line + const char* ssid = strchr(line, ':'); + if (ssid) + { + ssid++; // Skip the colon + result = _configController->setWifiSsid(ssid); + } + } + else if (strcmp(command, "C14") == 0 && paramCount >= 1) + { + // For C14, the entire string after : is the password + const char* password = strchr(line, ':'); + if (password) + { + password++; // Skip the colon + result = _configController->setWifiPassword(password); + } + } + else if (strcmp(command, "C15") == 0 && paramCount >= 1) + { + uint16_t port = atoi(params[0].value); + result = _configController->setWifiPort(port); + } + else if (strcmp(command, "C17") == 0 && paramCount >= 1) + { + // For C17, the entire string after : is the IP address + const char* ip = strchr(line, ':'); + if (ip) + { + ip++; // Skip the colon + result = _configController->setWifiIpAddress(ip); + } + } +#endif + else if (strcmp(command, "C18") == 0 && paramCount >= 1) + { + uint8_t relay = static_cast(atoi(params[0].key)); + bool state = (atoi(params[0].value) != 0); + result = _configController->setRelayDefaultState(relay, state); + } + else if (strcmp(command, "C19") == 0 && paramCount >= 1) + { + uint8_t relay1 = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t relay2 = static_cast(strtoul(params[0].value, nullptr, 0)); + + if (relay2 == 255) + { + result = _configController->unlinkRelay(relay1); + } + else + { + result = _configController->linkRelays(relay1, relay2); + } + } + else if (strcmp(command, "C20") == 0 && paramCount >= 1) + { + int8_t offset = static_cast(atoi(params[0].value)); + result = _configController->setTimezoneOffset(offset); + } + else if (strcmp(command, "C21") == 0 && paramCount >= 1) + { + const char* mmsi = strchr(line, ':'); + if (mmsi) + { + mmsi++; // Skip the colon + result = _configController->setMmsi(mmsi); + } + } + else if (strcmp(command, "C22") == 0) + { + const char* callSign = strchr(line, ':'); + if (callSign && *(callSign + 1) != '\0') + { + callSign++; // Skip the colon + result = _configController->setCallSign(callSign); + } + else + { + result = _configController->setCallSign(""); + } + } + else if (strcmp(command, "C23") == 0) + { + const char* homePort = strchr(line, ':'); + if (homePort && *(homePort + 1) != '\0') + { + homePort++; // Skip the colon + result = _configController->setHomePort(homePort); + } + else + { + result = _configController->setHomePort(""); + } + } + else if (strcmp(command, "C24") == 0 && paramCount >= 5) + { + uint8_t type = 0, colorSet = 0, r = 0, g = 0, b = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "c") == 0) + colorSet = atoi(params[i].value); + else if (strcmp(params[i].key, "r") == 0) + r = atoi(params[i].value); + else if (strcmp(params[i].key, "g") == 0) + g = atoi(params[i].value); + else if (strcmp(params[i].key, "b") == 0) + b = atoi(params[i].value); + } + + result = _configController->setLedColor(type, colorSet, r, g, b); + } + else if (strcmp(command, "C25") == 0 && paramCount >= 2) + { + uint8_t type = 0, brightness = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "b") == 0) + brightness = atoi(params[i].value); + } + + result = _configController->setLedBrightness(type, brightness); + } + else if (strcmp(command, "C26") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setLedAutoSwitch(enabled); + } + else if (strcmp(command, "C27") == 0 && paramCount >= 3) + { + bool gps = false, warning = false, system = false; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "g") == 0) + gps = (atoi(params[i].value) != 0); + else if (strcmp(params[i].key, "w") == 0) + warning = (atoi(params[i].value) != 0); + else if (strcmp(params[i].key, "s") == 0) + system = (atoi(params[i].value) != 0); + } + + result = _configController->setLedEnableStates(gps, warning, system); + } + else if (strcmp(command, "C28") == 0 && paramCount >= 4) + { + uint8_t type = 0, preset = 0; + uint16_t toneHz = 0, durationMs = 0; + uint32_t repeatMs = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "h") == 0) + toneHz = atoi(params[i].value); + else if (strcmp(params[i].key, "d") == 0) + durationMs = atoi(params[i].value); + else if (strcmp(params[i].key, "p") == 0) + preset = atoi(params[i].value); + else if (strcmp(params[i].key, "r") == 0) + repeatMs = strtoul(params[i].value, nullptr, 0); + } + + result = _configController->setControlPanelTones(type, preset, toneHz, durationMs, repeatMs); + } + else + { + logError("Unknown or invalid command", line); + return false; + } + + if (result != ConfigResult::Success) + { + char errorMsg[64]; + snprintf(errorMsg, sizeof(errorMsg), "Command failed: %s (result=%d)", command, static_cast(result)); + logError(errorMsg, line); + return false; + } + + return true; +} + +void SdCardConfigLoader::syncConfigToLink() +{ + // Send C1 (get settings) to trigger config broadcast via LINK + // This will make the control panel receive the new config + if (_linkSerial) + { + _linkSerial->sendCommand("C1", ""); + } +} + +void SdCardConfigLoader::logError(const char* message, const char* line) +{ + if (_computerSerial) + { + _computerSerial->sendError("SD_CFG_ERROR", message); + if (line) + { + _computerSerial->sendError("SD_CFG_LINE", line); + } + } +} + +void SdCardConfigLoader::logInfo(const char* message) +{ + if (_computerSerial) + { + _computerSerial->sendError("SD_CFG_INFO", message); + } +} + +bool SdCardConfigLoader::loadConfigFromSd() +{ + logInfo("Checking for SD config..."); + + if (!checkSdCard()) + { + logInfo("SD card not present or not accessible"); + return false; + } + + if (!configFileExists()) + { + logInfo("Config file not found on SD card"); + return false; + } + + logInfo("Loading config from SD card..."); + + FsFile configFile = _sd.open(SD_CONFIG_FILENAME, O_RDONLY); + if (!configFile) + { + logError("Failed to open config file"); + return false; + } + + // Read and apply commands line by line + char line[SD_CONFIG_MAX_LINE_LENGTH]; + uint16_t lineNumber = 0; + uint16_t successCount = 0; + uint16_t errorCount = 0; + + while (configFile.available()) + { + int len = configFile.fgets(line, sizeof(line)); + lineNumber++; + + if (len > 0) + { + if (applyConfigCommand(line)) + { + successCount++; + } + else + { + errorCount++; + } + } + } + + configFile.close(); + + // Save to EEPROM + if (successCount > 0) + { + logInfo("Saving config to EEPROM..."); + ConfigResult saveResult = _configController->save(); + + if (saveResult == ConfigResult::Success) + { + logInfo("Config saved to EEPROM"); + + // Sync to control panel via LINK + logInfo("Syncing config to control panel..."); + syncConfigToLink(); + + // Disable ConfigSyncManager since SD config is authoritative + if (_configSyncManager) + { + _configSyncManager->setEnabled(false); + logInfo("ConfigSyncManager disabled (SD config active)"); + } + + _sdConfigPresent = true; + + char summary[64]; + snprintf(summary, sizeof(summary), "SD config loaded: %u commands applied, %u errors", successCount, errorCount); + logInfo(summary); + + return true; + } + else + { + logError("Failed to save config to EEPROM"); + } + } + + return false; +} + +bool SdCardConfigLoader::reloadConfigFromSd() +{ + logInfo("Reloading config from SD card..."); + return loadConfigFromSd(); +} + +bool SdCardConfigLoader::exportConfigToSd() +{ + logInfo("Exporting config to SD card..."); + + if (!checkSdCard()) + { + logError("SD card not present or not accessible"); + return false; + } + + // Delete existing config file if present + if (_sd.exists(SD_CONFIG_FILENAME)) + { + _sd.remove(SD_CONFIG_FILENAME); + } + + FsFile configFile = _sd.open(SD_CONFIG_FILENAME, O_WRONLY | O_CREAT); + if (!configFile) + { + logError("Failed to create config file"); + return false; + } + + Config* config = _configController->getConfigPtr(); + if (!config) + { + configFile.close(); + logError("Config not available"); + return false; + } + + // Write header comment + configFile.println("# SmartFuseBox Configuration"); + configFile.println("# Generated by C30 command"); + configFile.println(); + + // C3 - Boat name + if (strlen(config->name) > 0) + { + configFile.print("C3:"); + configFile.println(config->name); + } + + // C4 - Relay names + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C4:"); + configFile.print(i); + configFile.print("="); + configFile.print(config->relayShortNames[i]); + configFile.print("|"); + configFile.println(config->relayLongNames[i]); + } + + // C5 - Home button mappings + for (uint8_t i = 0; i < ConfigHomeButtons; i++) + { + configFile.print("C5:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->homePageMapping[i]); + } + + // C6 - Button colors + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C6:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->buttonImage[i]); + } + + // C7 - Vessel type + configFile.print("C7:v="); + configFile.println(static_cast(config->vesselType)); + + // C8 - Sound relay + configFile.print("C8:v="); + configFile.println(config->hornRelayIndex); + + // C9 - Sound delay + configFile.print("C9:v="); + configFile.println(config->soundStartDelayMs); + +#if defined(ARDUINO_UNO_R4) + // C10 - Bluetooth enabled + configFile.print("C10:v="); + configFile.println(config->bluetoothEnabled ? "1" : "0"); + + // C11 - WiFi enabled + configFile.print("C11:v="); + configFile.println(config->wifiEnabled ? "1" : "0"); + + // C12 - WiFi mode + configFile.print("C12:v="); + configFile.println(config->accessMode); + + // C13 - WiFi SSID + configFile.print("C13:"); + configFile.println(config->apSSID); + + // C14 - WiFi password + configFile.print("C14:"); + configFile.println(config->apPassword); + + // C15 - WiFi port + configFile.print("C15:v="); + configFile.println(config->wifiPort); + + // C17 - WiFi AP IP + configFile.print("C17:"); + configFile.println(config->apIpAddress); +#endif + + // C18 - Default relay states + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C18:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->defaulRelayState[i] ? "1" : "0"); + } + + // C19 - Linked relays + for (uint8_t i = 0; i < ConfigMaxLinkedRelays; i++) + { + configFile.print("C19:"); + configFile.print(config->linkedRelays[i][0]); + configFile.print("="); + configFile.println(config->linkedRelays[i][1]); + } + + // C20 - Timezone offset + configFile.print("C20:v="); + configFile.println(config->timezoneOffset); + + // C21 - MMSI + configFile.print("C21:"); + configFile.println(config->mMSI); + + // C22 - Call sign + configFile.print("C22:"); + configFile.println(config->callSign); + + // C23 - Home port + configFile.print("C23:"); + configFile.println(config->homePort); + + // C24 - LED colors + configFile.print("C24:t=0;c=0;r="); + configFile.print(config->ledConfig.dayGoodColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.dayGoodColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.dayGoodColor[2]); + + configFile.print("C24:t=0;c=1;r="); + configFile.print(config->ledConfig.dayBadColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.dayBadColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.dayBadColor[2]); + + configFile.print("C24:t=1;c=0;r="); + configFile.print(config->ledConfig.nightGoodColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.nightGoodColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.nightGoodColor[2]); + + configFile.print("C24:t=1;c=1;r="); + configFile.print(config->ledConfig.nightBadColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.nightBadColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.nightBadColor[2]); + + // C25 - LED brightness + configFile.print("C25:t=0;b="); + configFile.println(config->ledConfig.dayBrightness); + + configFile.print("C25:t=1;b="); + configFile.println(config->ledConfig.nightBrightness); + + // C26 - LED auto switch + configFile.print("C26:v="); + configFile.println(config->ledConfig.autoSwitch ? "1" : "0"); + + // C27 - LED enable states + configFile.print("C27:g="); + configFile.print(config->ledConfig.gpsEnabled ? "1" : "0"); + configFile.print(";w="); + configFile.print(config->ledConfig.warningEnabled ? "1" : "0"); + configFile.print(";s="); + configFile.println(config->ledConfig.systemEnabled ? "1" : "0"); + + // C28 - Control panel tones + configFile.print("C28:t=0;h="); + configFile.print(config->soundConfig.good_toneHz); + configFile.print(";d="); + configFile.print(config->soundConfig.good_durationMs); + configFile.print(";p="); + configFile.print(config->soundConfig.goodPreset); + configFile.println(";r=0"); + + configFile.print("C28:t=1;h="); + configFile.print(config->soundConfig.bad_toneHz); + configFile.print(";d="); + configFile.print(config->soundConfig.bad_durationMs); + configFile.print(";p="); + configFile.print(config->soundConfig.badPreset); + configFile.print(";r="); + configFile.println(config->soundConfig.bad_repeatMs); + + configFile.close(); + + logInfo("Config exported to SD card"); + return true; +} diff --git a/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.h b/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.h new file mode 100644 index 0000000..f7abe5c --- /dev/null +++ b/SmartFuseBox/SmartFuseBox/SdCardConfigLoader.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include +#include "ConfigController.h" +#include "ConfigSyncManager.h" + +constexpr char SD_CONFIG_FILENAME[] = "config.txt"; +constexpr uint16_t SD_CONFIG_MAX_LINE_LENGTH = 128; + +/** + * @class SdCardConfigLoader + * @brief Loads configuration from SD card and applies to ConfigManager + * + * Boot sequence: + * 1. Check for SD card with config.txt + * 2. Parse and validate all commands + * 3. Compare with current EEPROM config + * 4. If different, apply changes and save to EEPROM + * 5. Send config via LINK to sync control panel + * + * Features: + * - Read-only SD card config (not auto-updated during runtime) + * - Command format validation + * - Error logging to Serial + * - Integration with ConfigSyncManager (disable if SD config loaded) + * - C29: Reload config from SD card + * - C30: Export current config to SD card + * + * Usage: + * @code + * SdCardConfigLoader loader(&commandMgrComputer, &commandMgrLink, + * &configController, &configSyncManager, csPin); + * + * void setup() { + * bool sdConfigLoaded = loader.loadConfigFromSd(); + * if (sdConfigLoaded) { + * // SD config was applied, ConfigSyncManager will be disabled + * } + * } + * @endcode + */ +class SdCardConfigLoader +{ +private: + SerialCommandManager* _computerSerial; + SerialCommandManager* _linkSerial; + ConfigController* _configController; + ConfigSyncManager* _configSyncManager; + uint8_t _csPin; + bool _sdConfigPresent; + + // SD card + SdFat _sd; + + /** + * @brief Check if SD card is accessible + * @return true if SD card is present and readable + */ + bool checkSdCard(); + + /** + * @brief Check if config.txt exists on SD card + * @return true if config file exists + */ + bool configFileExists(); + + /** + * @brief Parse and apply a single config command line + * @param line Command line to parse + * @return true if command was successfully applied + */ + bool applyConfigCommand(const char* line); + + /** + * @brief Send config to LINK serial to sync control panel + */ + void syncConfigToLink(); + + /** + * @brief Log error message to serial + * @param message Error message + * @param line Optional line content that caused error + */ + void logError(const char* message, const char* line = nullptr); + + /** + * @brief Log info message to serial + * @param message Info message + */ + void logInfo(const char* message); + +public: + /** + * @brief Constructor + * @param computerSerial Serial manager for computer communication + * @param linkSerial Serial manager for LINK communication + * @param configController Configuration controller + * @param configSyncManager Configuration sync manager (will be disabled if SD config loaded) + * @param csPin Chip select pin for SD card + */ + SdCardConfigLoader(SerialCommandManager* computerSerial, + SerialCommandManager* linkSerial, + ConfigController* configController, + ConfigSyncManager* configSyncManager, + uint8_t csPin); + + /** + * @brief Load configuration from SD card if present + * + * Reads config.txt, applies all commands, saves to EEPROM if changed, + * and syncs to control panel via LINK. + * + * @return true if SD config was found and applied + */ + bool loadConfigFromSd(); + + /** + * @brief Reload configuration from SD card (C29 command) + * @return true if config was reloaded successfully + */ + bool reloadConfigFromSd(); + + /** + * @brief Export current configuration to SD card (C30 command) + * @return true if config was exported successfully + */ + bool exportConfigToSd(); + + /** + * @brief Check if SD config was loaded at boot + * @return true if SD config is present and was loaded + */ + bool isSdConfigPresent() const { return _sdConfigPresent; } +}; diff --git a/SmartFuseBox/config.txt b/SmartFuseBox/config.txt new file mode 100644 index 0000000..192b7c5 --- /dev/null +++ b/SmartFuseBox/config.txt @@ -0,0 +1,146 @@ +# SmartFuseBox Configuration File +# This file is automatically loaded at boot if present on SD card +# Format: COMMAND:parameters +# Lines starting with # are comments and are ignored +# Empty lines are ignored + +# Boat Name +C3:Sea Wolf + +# Relay Names (format: index=ShortName|LongName) +# Short name: max 5 chars (used on home page) +# Long name: max 20 chars (used on buttons page) +C4:0=Nav|Navigation +C4:1=Bilge|Bilge Pump +C4:2=Light|Cabin Lights +C4:3=Pump|Water Pump +C4:4=Horn|Sound Horn +C4:5=Fan|Ventilation +C4:6=Spare|Spare 6 +C4:7=Spare|Spare 7 + +# Home Button Mappings (format: slot=relay) +# Maps home page buttons (0-3) to relays (0-7 or 255 for unmapped) +C5:0=0 +C5:1=1 +C5:2=2 +C5:3=3 + +# Button Colors (format: relay=color_index) +# Color indices: 0-7 or 255 for default +C6:0=4 +C6:1=4 +C6:2=4 +C6:3=4 +C6:4=4 +C6:5=4 +C6:6=4 +C6:7=4 + +# Vessel Type +# 0=Motor, 1=Sail, 2=Fishing, 3=Yacht +C7:v=0 + +# Sound Relay Button +# Which relay controls the horn (0-7 or 255 for unmapped) +C8:v=255 + +# Sound Start Delay (milliseconds) +C9:v=244 + +# Bluetooth Enabled (0=disabled, 1=enabled) +C10:v=0 + +# WiFi Enabled (0=disabled, 1=enabled) +C11:v=1 + +# WiFi Access Mode (0=AP, 1=Client) +C12:v=0 + +# WiFi SSID (for Client mode) +C13:SmartFuseBox + +# WiFi Password (for Client mode) +C14:sfb-776064 + +# WiFi Port (default: 80) +C15:v=80 + +# WiFi AP IP Address (for AP mode) +C17:192.168.4.1 + +# Default Relay States (format: relay=state) +# 0=off by default, 1=on by default +C18:0=0 +C18:1=0 +C18:2=0 +C18:3=0 +C18:4=0 +C18:5=0 +C18:6=0 +C18:7=0 + +# Linked Relays (format: relay1=relay2) +# When relay1 is toggled, relay2 is toggled as well +# Use 255 to unlink +C19:255=255 +C19:255=255 + +# Timezone Offset (hours from UTC, range: -12 to +14) +C20:v=0 + +# MMSI (Maritime Mobile Service Identity, 9 digits) +C21:000000000 + +# Call Sign (vessel call sign, can be empty) +C22: + +# Home Port (vessel home port, can be empty) +C23: + +# LED Colors (format: t=type;c=colorset;r=red;g=green;b=blue) +# type: 0=day, 1=night +# colorset: 0=good, 1=bad +# r,g,b: 0-255 + +# Day Mode - Good Color (blue) +C24:t=0;c=0;r=0;g=80;b=255 + +# Day Mode - Bad Color (orange) +C24:t=0;c=1;r=255;g=140;b=0 + +# Night Mode - Good Color (dark red) +C24:t=1;c=0;r=100;g=0;b=0 + +# Night Mode - Bad Color (bright orange) +C24:t=1;c=1;r=255;g=50;b=0 + +# LED Brightness (format: t=type;b=brightness) +# type: 0=day, 1=night +# brightness: 0-100 + +# Day Brightness (80%) +C25:t=0;b=80 + +# Night Brightness (20%) +C25:t=1;b=20 + +# LED Auto Day/Night Switch (0=disabled, 1=enabled) +C26:v=1 + +# LED Enable States (format: g=gps;w=warning;s=system) +# 0=disabled, 1=enabled +C27:g=1;w=1;s=1 + +# Control Panel Tones (format: t=type;h=Hz;d=duration;p=preset;r=repeat) +# type: 0=good, 1=bad +# h: tone frequency in Hz +# d: duration in milliseconds +# p: preset (0=custom, 1=submarine ping, 2=double beep, 3=rising chirp, 4=descending alert, 5=nautical bell, 0xFF=no sound) +# r: repeat interval in milliseconds (0=no repeat, only used for bad tones) + +# Good Tone (startup sound) +C28:t=0;h=1000;d=100;p=1;r=0 + +# Bad Tone (warning sound) +C28:t=1;h=400;d=500;p=5;r=30000 From 0f0c0f6cbd560c109bdc20532600c058abe140c8 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 11:59:59 +0100 Subject: [PATCH 12/19] Remove duplicate file --- SmartFuseBox/Shared/Sensors/Dht11SensorHandler.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 SmartFuseBox/Shared/Sensors/Dht11SensorHandler.h diff --git a/SmartFuseBox/Shared/Sensors/Dht11SensorHandler.h b/SmartFuseBox/Shared/Sensors/Dht11SensorHandler.h deleted file mode 100644 index e69de29..0000000 From b12dae62d124ea42db5b2a95848bf25f713d3fe3 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 13:10:04 +0100 Subject: [PATCH 13/19] config md files --- .../SD_CONFIG_IMPLEMENTATION.md => SD_CONFIG_IMPLEMENTATION.md | 0 SmartFuseBox/SD_CONFIG_README.md => SD_CONFIG_README.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md => SD_CONFIG_IMPLEMENTATION.md (100%) rename SmartFuseBox/SD_CONFIG_README.md => SD_CONFIG_README.md (100%) diff --git a/SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md b/SD_CONFIG_IMPLEMENTATION.md similarity index 100% rename from SmartFuseBox/SD_CONFIG_IMPLEMENTATION.md rename to SD_CONFIG_IMPLEMENTATION.md diff --git a/SmartFuseBox/SD_CONFIG_README.md b/SD_CONFIG_README.md similarity index 100% rename from SmartFuseBox/SD_CONFIG_README.md rename to SD_CONFIG_README.md From 08e71ce6ea3919fad05c28721cef440a92598bfe Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 13:10:26 +0100 Subject: [PATCH 14/19] Initial sd card config loader --- SmartFuseBox/SDCardConfigLoader.cpp | 709 ++++++++++++++++++++++++++++ SmartFuseBox/SDCardConfigLoader.h | 136 ++++++ 2 files changed, 845 insertions(+) create mode 100644 SmartFuseBox/SDCardConfigLoader.cpp create mode 100644 SmartFuseBox/SDCardConfigLoader.h diff --git a/SmartFuseBox/SDCardConfigLoader.cpp b/SmartFuseBox/SDCardConfigLoader.cpp new file mode 100644 index 0000000..d941b8d --- /dev/null +++ b/SmartFuseBox/SDCardConfigLoader.cpp @@ -0,0 +1,709 @@ +#include "SdCardConfigLoader.h" +#include "ConfigManager.h" +#include + +SdCardConfigLoader::SdCardConfigLoader(SerialCommandManager* computerSerial, + SerialCommandManager* linkSerial, + ConfigController* configController, + ConfigSyncManager* configSyncManager, + uint8_t csPin) + : _computerSerial(computerSerial), + _linkSerial(linkSerial), + _configController(configController), + _configSyncManager(configSyncManager), + _csPin(csPin), + _sdConfigPresent(false) +{ +} + +bool SdCardConfigLoader::checkSdCard() +{ + if (!_sd.begin(_csPin, SD_SCK_MHZ(50))) + { + return false; + } + return true; +} + +bool SdCardConfigLoader::configFileExists() +{ + return _sd.exists(SD_CONFIG_FILENAME); +} + +bool SdCardConfigLoader::applyConfigCommand(const char* line) +{ + // Skip empty lines and comments + if (line == nullptr || line[0] == '\0' || line[0] == '#' || line[0] == '\r' || line[0] == '\n') + { + return true; + } + + // Create a mutable copy for parsing + char buffer[SD_CONFIG_MAX_LINE_LENGTH]; + strncpy(buffer, line, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + // Trim whitespace and newlines + char* end = buffer + strlen(buffer) - 1; + while (end > buffer && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) + { + *end = '\0'; + end--; + } + + // Parse command using SerialCommandManager's parser + // Format: "CMD:param1=value1;param2=value2" + char* colonPos = strchr(buffer, ':'); + char command[8] = {0}; + + if (colonPos != nullptr) + { + size_t cmdLen = colonPos - buffer; + if (cmdLen >= sizeof(command)) + { + logError("Command too long", line); + return false; + } + strncpy(command, buffer, cmdLen); + command[cmdLen] = '\0'; + } + else + { + // No colon means command with no params (like C0, C1, C2) + strncpy(command, buffer, sizeof(command) - 1); + } + + // Parse parameters manually since we're simulating command input + const char* paramsStr = colonPos ? (colonPos + 1) : ""; + + // Use the computer serial's command parser to validate and route the command + // We'll construct a synthetic command and feed it through the handler chain + + // Parse parameters into key-value pairs + StringKeyValue params[10]; + uint8_t paramCount = 0; + + if (paramsStr && *paramsStr) + { + char paramBuffer[SD_CONFIG_MAX_LINE_LENGTH]; + strncpy(paramBuffer, paramsStr, sizeof(paramBuffer) - 1); + paramBuffer[sizeof(paramBuffer) - 1] = '\0'; + + // Parse semicolon-separated parameters + char* savePtr1 = nullptr; + char* param = strtok_r(paramBuffer, ";", &savePtr1); + + while (param && paramCount < 10) + { + // Each param can be "key=value" or just "value" + char* equalPos = strchr(param, '='); + if (equalPos) + { + *equalPos = '\0'; + params[paramCount].key = param; + params[paramCount].value = equalPos + 1; + } + else + { + // For commands like C13:SSID, the whole thing after : is the value + // with an implied key (often "v" or empty) + params[paramCount].key = ""; + params[paramCount].value = param; + } + paramCount++; + param = strtok_r(nullptr, ";", &savePtr1); + } + } + + // Now apply the command through ConfigController + // We bypass the SerialCommandManager and call ConfigController methods directly + + ConfigResult result = ConfigResult::InvalidCommand; + + if (strcmp(command, "C3") == 0 && paramCount >= 1) + { + result = _configController->rename(params[0].value); + } + else if (strcmp(command, "C4") == 0 && paramCount >= 1) + { + uint8_t idx = static_cast(strtoul(params[0].key, nullptr, 0)); + result = _configController->renameRelay(idx, params[0].value); + } + else if (strcmp(command, "C5") == 0 && paramCount >= 1) + { + uint8_t button = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t relay = static_cast(strtoul(params[0].value, nullptr, 0)); + result = _configController->mapHomeButton(button, relay); + } + else if (strcmp(command, "C6") == 0 && paramCount >= 1) + { + uint8_t button = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t color = static_cast(strtoul(params[0].value, nullptr, 0)); + result = _configController->mapHomeButtonColor(button, color); + } + else if (strcmp(command, "C7") == 0 && paramCount >= 1) + { + uint8_t type = atoi(params[0].value); + result = _configController->setVesselType(type); + } + else if (strcmp(command, "C8") == 0 && paramCount >= 1) + { + uint8_t relay = atoi(params[0].value); + result = _configController->setSoundRelayButton(relay); + } + else if (strcmp(command, "C9") == 0 && paramCount >= 1) + { + uint16_t delay = atoi(params[0].value); + result = _configController->setsoundDelayStart(delay); + } +#if defined(ARDUINO_UNO_R4) + else if (strcmp(command, "C10") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setBluetoothEnabled(enabled); + } + else if (strcmp(command, "C11") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setWifiEnabled(enabled); + } + else if (strcmp(command, "C12") == 0 && paramCount >= 1) + { + uint8_t mode = atoi(params[0].value); + result = _configController->setWifiAccessMode(mode); + } + else if (strcmp(command, "C13") == 0 && paramCount >= 1) + { + // For C13, the entire string after : is the SSID (no v= prefix in file) + // We need to find original param value from line + const char* ssid = strchr(line, ':'); + if (ssid) + { + ssid++; // Skip the colon + result = _configController->setWifiSsid(ssid); + } + } + else if (strcmp(command, "C14") == 0 && paramCount >= 1) + { + // For C14, the entire string after : is the password + const char* password = strchr(line, ':'); + if (password) + { + password++; // Skip the colon + result = _configController->setWifiPassword(password); + } + } + else if (strcmp(command, "C15") == 0 && paramCount >= 1) + { + uint16_t port = atoi(params[0].value); + result = _configController->setWifiPort(port); + } + else if (strcmp(command, "C17") == 0 && paramCount >= 1) + { + // For C17, the entire string after : is the IP address + const char* ip = strchr(line, ':'); + if (ip) + { + ip++; // Skip the colon + result = _configController->setWifiIpAddress(ip); + } + } +#endif + else if (strcmp(command, "C18") == 0 && paramCount >= 1) + { + uint8_t relay = static_cast(atoi(params[0].key)); + bool state = (atoi(params[0].value) != 0); + result = _configController->setRelayDefaultState(relay, state); + } + else if (strcmp(command, "C19") == 0 && paramCount >= 1) + { + uint8_t relay1 = static_cast(strtoul(params[0].key, nullptr, 0)); + uint8_t relay2 = static_cast(strtoul(params[0].value, nullptr, 0)); + + if (relay2 == 255) + { + result = _configController->unlinkRelay(relay1); + } + else + { + result = _configController->linkRelays(relay1, relay2); + } + } + else if (strcmp(command, "C20") == 0 && paramCount >= 1) + { + int8_t offset = static_cast(atoi(params[0].value)); + result = _configController->setTimezoneOffset(offset); + } + else if (strcmp(command, "C21") == 0 && paramCount >= 1) + { + const char* mmsi = strchr(line, ':'); + if (mmsi) + { + mmsi++; // Skip the colon + result = _configController->setMmsi(mmsi); + } + } + else if (strcmp(command, "C22") == 0) + { + const char* callSign = strchr(line, ':'); + if (callSign && *(callSign + 1) != '\0') + { + callSign++; // Skip the colon + result = _configController->setCallSign(callSign); + } + else + { + result = _configController->setCallSign(""); + } + } + else if (strcmp(command, "C23") == 0) + { + const char* homePort = strchr(line, ':'); + if (homePort && *(homePort + 1) != '\0') + { + homePort++; // Skip the colon + result = _configController->setHomePort(homePort); + } + else + { + result = _configController->setHomePort(""); + } + } + else if (strcmp(command, "C24") == 0 && paramCount >= 5) + { + uint8_t type = 0, colorSet = 0, r = 0, g = 0, b = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "c") == 0) + colorSet = atoi(params[i].value); + else if (strcmp(params[i].key, "r") == 0) + r = atoi(params[i].value); + else if (strcmp(params[i].key, "g") == 0) + g = atoi(params[i].value); + else if (strcmp(params[i].key, "b") == 0) + b = atoi(params[i].value); + } + + result = _configController->setLedColor(type, colorSet, r, g, b); + } + else if (strcmp(command, "C25") == 0 && paramCount >= 2) + { + uint8_t type = 0, brightness = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "b") == 0) + brightness = atoi(params[i].value); + } + + result = _configController->setLedBrightness(type, brightness); + } + else if (strcmp(command, "C26") == 0 && paramCount >= 1) + { + bool enabled = (atoi(params[0].value) != 0); + result = _configController->setLedAutoSwitch(enabled); + } + else if (strcmp(command, "C27") == 0 && paramCount >= 3) + { + bool gps = false, warning = false, system = false; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "g") == 0) + gps = (atoi(params[i].value) != 0); + else if (strcmp(params[i].key, "w") == 0) + warning = (atoi(params[i].value) != 0); + else if (strcmp(params[i].key, "s") == 0) + system = (atoi(params[i].value) != 0); + } + + result = _configController->setLedEnableStates(gps, warning, system); + } + else if (strcmp(command, "C28") == 0 && paramCount >= 4) + { + uint8_t type = 0, preset = 0; + uint16_t toneHz = 0, durationMs = 0; + uint32_t repeatMs = 0; + + for (uint8_t i = 0; i < paramCount; i++) + { + if (strcmp(params[i].key, "t") == 0) + type = atoi(params[i].value); + else if (strcmp(params[i].key, "h") == 0) + toneHz = atoi(params[i].value); + else if (strcmp(params[i].key, "d") == 0) + durationMs = atoi(params[i].value); + else if (strcmp(params[i].key, "p") == 0) + preset = atoi(params[i].value); + else if (strcmp(params[i].key, "r") == 0) + repeatMs = strtoul(params[i].value, nullptr, 0); + } + + result = _configController->setControlPanelTones(type, preset, toneHz, durationMs, repeatMs); + } + else + { + logError("Unknown or invalid command", line); + return false; + } + + if (result != ConfigResult::Success) + { + char errorMsg[64]; + snprintf(errorMsg, sizeof(errorMsg), "Command failed: %s (result=%d)", command, static_cast(result)); + logError(errorMsg, line); + return false; + } + + return true; +} + +void SdCardConfigLoader::syncConfigToLink() +{ + // Send C1 (get settings) to trigger config broadcast via LINK + // This will make the control panel receive the new config + if (_linkSerial) + { + _linkSerial->sendCommand("C1", ""); + } +} + +void SdCardConfigLoader::logError(const char* message, const char* line) +{ + if (_computerSerial) + { + _computerSerial->sendError("SD_CFG_ERROR", message); + if (line) + { + _computerSerial->sendError("SD_CFG_LINE", line); + } + } +} + +void SdCardConfigLoader::logInfo(const char* message) +{ + if (_computerSerial) + { + _computerSerial->sendError("SD_CFG_INFO", message); + } +} + +bool SdCardConfigLoader::loadConfigFromSd() +{ + logInfo("Checking for SD config..."); + + if (!checkSdCard()) + { + logInfo("SD card not present or not accessible"); + return false; + } + + if (!configFileExists()) + { + logInfo("Config file not found on SD card"); + return false; + } + + logInfo("Loading config from SD card..."); + + FsFile configFile = _sd.open(SD_CONFIG_FILENAME, O_RDONLY); + if (!configFile) + { + logError("Failed to open config file"); + return false; + } + + // Read and apply commands line by line + char line[SD_CONFIG_MAX_LINE_LENGTH]; + uint16_t lineNumber = 0; + uint16_t successCount = 0; + uint16_t errorCount = 0; + + while (configFile.available()) + { + int len = configFile.fgets(line, sizeof(line)); + lineNumber++; + + if (len > 0) + { + if (applyConfigCommand(line)) + { + successCount++; + } + else + { + errorCount++; + } + } + } + + configFile.close(); + + // Save to EEPROM + if (successCount > 0) + { + logInfo("Saving config to EEPROM..."); + ConfigResult saveResult = _configController->save(); + + if (saveResult == ConfigResult::Success) + { + logInfo("Config saved to EEPROM"); + + // Sync to control panel via LINK + logInfo("Syncing config to control panel..."); + syncConfigToLink(); + + // Disable ConfigSyncManager since SD config is authoritative + if (_configSyncManager) + { + _configSyncManager->setEnabled(false); + logInfo("ConfigSyncManager disabled (SD config active)"); + } + + _sdConfigPresent = true; + + char summary[64]; + snprintf(summary, sizeof(summary), "SD config loaded: %u commands applied, %u errors", successCount, errorCount); + logInfo(summary); + + return true; + } + else + { + logError("Failed to save config to EEPROM"); + } + } + + return false; +} + +bool SdCardConfigLoader::reloadConfigFromSd() +{ + logInfo("Reloading config from SD card..."); + return loadConfigFromSd(); +} + +bool SdCardConfigLoader::exportConfigToSd() +{ + logInfo("Exporting config to SD card..."); + + if (!checkSdCard()) + { + logError("SD card not present or not accessible"); + return false; + } + + // Delete existing config file if present + if (_sd.exists(SD_CONFIG_FILENAME)) + { + _sd.remove(SD_CONFIG_FILENAME); + } + + FsFile configFile = _sd.open(SD_CONFIG_FILENAME, O_WRONLY | O_CREAT); + if (!configFile) + { + logError("Failed to create config file"); + return false; + } + + Config* config = _configController->getConfigPtr(); + if (!config) + { + configFile.close(); + logError("Config not available"); + return false; + } + + // Write header comment + configFile.println("# SmartFuseBox Configuration"); + configFile.println("# Generated by C30 command"); + configFile.println(); + + // C3 - Boat name + if (strlen(config->name) > 0) + { + configFile.print("C3:"); + configFile.println(config->name); + } + + // C4 - Relay names + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C4:"); + configFile.print(i); + configFile.print("="); + configFile.print(config->relayShortNames[i]); + configFile.print("|"); + configFile.println(config->relayLongNames[i]); + } + + // C5 - Home button mappings + for (uint8_t i = 0; i < ConfigHomeButtons; i++) + { + configFile.print("C5:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->homePageMapping[i]); + } + + // C6 - Button colors + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C6:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->buttonImage[i]); + } + + // C7 - Vessel type + configFile.print("C7:v="); + configFile.println(static_cast(config->vesselType)); + + // C8 - Sound relay + configFile.print("C8:v="); + configFile.println(config->hornRelayIndex); + + // C9 - Sound delay + configFile.print("C9:v="); + configFile.println(config->soundStartDelayMs); + +#if defined(ARDUINO_UNO_R4) + // C10 - Bluetooth enabled + configFile.print("C10:v="); + configFile.println(config->bluetoothEnabled ? "1" : "0"); + + // C11 - WiFi enabled + configFile.print("C11:v="); + configFile.println(config->wifiEnabled ? "1" : "0"); + + // C12 - WiFi mode + configFile.print("C12:v="); + configFile.println(config->accessMode); + + // C13 - WiFi SSID + configFile.print("C13:"); + configFile.println(config->apSSID); + + // C14 - WiFi password + configFile.print("C14:"); + configFile.println(config->apPassword); + + // C15 - WiFi port + configFile.print("C15:v="); + configFile.println(config->wifiPort); + + // C17 - WiFi AP IP + configFile.print("C17:"); + configFile.println(config->apIpAddress); +#endif + + // C18 - Default relay states + for (uint8_t i = 0; i < ConfigRelayCount; i++) + { + configFile.print("C18:"); + configFile.print(i); + configFile.print("="); + configFile.println(config->defaulRelayState[i] ? "1" : "0"); + } + + // C19 - Linked relays + for (uint8_t i = 0; i < ConfigMaxLinkedRelays; i++) + { + configFile.print("C19:"); + configFile.print(config->linkedRelays[i][0]); + configFile.print("="); + configFile.println(config->linkedRelays[i][1]); + } + + // C20 - Timezone offset + configFile.print("C20:v="); + configFile.println(config->timezoneOffset); + + // C21 - MMSI + configFile.print("C21:"); + configFile.println(config->mMSI); + + // C22 - Call sign + configFile.print("C22:"); + configFile.println(config->callSign); + + // C23 - Home port + configFile.print("C23:"); + configFile.println(config->homePort); + + // C24 - LED colors + configFile.print("C24:t=0;c=0;r="); + configFile.print(config->ledConfig.dayGoodColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.dayGoodColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.dayGoodColor[2]); + + configFile.print("C24:t=0;c=1;r="); + configFile.print(config->ledConfig.dayBadColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.dayBadColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.dayBadColor[2]); + + configFile.print("C24:t=1;c=0;r="); + configFile.print(config->ledConfig.nightGoodColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.nightGoodColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.nightGoodColor[2]); + + configFile.print("C24:t=1;c=1;r="); + configFile.print(config->ledConfig.nightBadColor[0]); + configFile.print(";g="); + configFile.print(config->ledConfig.nightBadColor[1]); + configFile.print(";b="); + configFile.println(config->ledConfig.nightBadColor[2]); + + // C25 - LED brightness + configFile.print("C25:t=0;b="); + configFile.println(config->ledConfig.dayBrightness); + + configFile.print("C25:t=1;b="); + configFile.println(config->ledConfig.nightBrightness); + + // C26 - LED auto switch + configFile.print("C26:v="); + configFile.println(config->ledConfig.autoSwitch ? "1" : "0"); + + // C27 - LED enable states + configFile.print("C27:g="); + configFile.print(config->ledConfig.gpsEnabled ? "1" : "0"); + configFile.print(";w="); + configFile.print(config->ledConfig.warningEnabled ? "1" : "0"); + configFile.print(";s="); + configFile.println(config->ledConfig.systemEnabled ? "1" : "0"); + + // C28 - Control panel tones + configFile.print("C28:t=0;h="); + configFile.print(config->soundConfig.good_toneHz); + configFile.print(";d="); + configFile.print(config->soundConfig.good_durationMs); + configFile.print(";p="); + configFile.print(config->soundConfig.goodPreset); + configFile.println(";r=0"); + + configFile.print("C28:t=1;h="); + configFile.print(config->soundConfig.bad_toneHz); + configFile.print(";d="); + configFile.print(config->soundConfig.bad_durationMs); + configFile.print(";p="); + configFile.print(config->soundConfig.badPreset); + configFile.print(";r="); + configFile.println(config->soundConfig.bad_repeatMs); + + configFile.close(); + + logInfo("Config exported to SD card"); + return true; +} diff --git a/SmartFuseBox/SDCardConfigLoader.h b/SmartFuseBox/SDCardConfigLoader.h new file mode 100644 index 0000000..f7abe5c --- /dev/null +++ b/SmartFuseBox/SDCardConfigLoader.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include +#include "ConfigController.h" +#include "ConfigSyncManager.h" + +constexpr char SD_CONFIG_FILENAME[] = "config.txt"; +constexpr uint16_t SD_CONFIG_MAX_LINE_LENGTH = 128; + +/** + * @class SdCardConfigLoader + * @brief Loads configuration from SD card and applies to ConfigManager + * + * Boot sequence: + * 1. Check for SD card with config.txt + * 2. Parse and validate all commands + * 3. Compare with current EEPROM config + * 4. If different, apply changes and save to EEPROM + * 5. Send config via LINK to sync control panel + * + * Features: + * - Read-only SD card config (not auto-updated during runtime) + * - Command format validation + * - Error logging to Serial + * - Integration with ConfigSyncManager (disable if SD config loaded) + * - C29: Reload config from SD card + * - C30: Export current config to SD card + * + * Usage: + * @code + * SdCardConfigLoader loader(&commandMgrComputer, &commandMgrLink, + * &configController, &configSyncManager, csPin); + * + * void setup() { + * bool sdConfigLoaded = loader.loadConfigFromSd(); + * if (sdConfigLoaded) { + * // SD config was applied, ConfigSyncManager will be disabled + * } + * } + * @endcode + */ +class SdCardConfigLoader +{ +private: + SerialCommandManager* _computerSerial; + SerialCommandManager* _linkSerial; + ConfigController* _configController; + ConfigSyncManager* _configSyncManager; + uint8_t _csPin; + bool _sdConfigPresent; + + // SD card + SdFat _sd; + + /** + * @brief Check if SD card is accessible + * @return true if SD card is present and readable + */ + bool checkSdCard(); + + /** + * @brief Check if config.txt exists on SD card + * @return true if config file exists + */ + bool configFileExists(); + + /** + * @brief Parse and apply a single config command line + * @param line Command line to parse + * @return true if command was successfully applied + */ + bool applyConfigCommand(const char* line); + + /** + * @brief Send config to LINK serial to sync control panel + */ + void syncConfigToLink(); + + /** + * @brief Log error message to serial + * @param message Error message + * @param line Optional line content that caused error + */ + void logError(const char* message, const char* line = nullptr); + + /** + * @brief Log info message to serial + * @param message Info message + */ + void logInfo(const char* message); + +public: + /** + * @brief Constructor + * @param computerSerial Serial manager for computer communication + * @param linkSerial Serial manager for LINK communication + * @param configController Configuration controller + * @param configSyncManager Configuration sync manager (will be disabled if SD config loaded) + * @param csPin Chip select pin for SD card + */ + SdCardConfigLoader(SerialCommandManager* computerSerial, + SerialCommandManager* linkSerial, + ConfigController* configController, + ConfigSyncManager* configSyncManager, + uint8_t csPin); + + /** + * @brief Load configuration from SD card if present + * + * Reads config.txt, applies all commands, saves to EEPROM if changed, + * and syncs to control panel via LINK. + * + * @return true if SD config was found and applied + */ + bool loadConfigFromSd(); + + /** + * @brief Reload configuration from SD card (C29 command) + * @return true if config was reloaded successfully + */ + bool reloadConfigFromSd(); + + /** + * @brief Export current configuration to SD card (C30 command) + * @return true if config was exported successfully + */ + bool exportConfigToSd(); + + /** + * @brief Check if SD config was loaded at boot + * @return true if SD config is present and was loaded + */ + bool isSdConfigPresent() const { return _sdConfigPresent; } +}; From ca11a92de27a9f8e07bd6a18e620c86a871d6573 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 13:11:47 +0100 Subject: [PATCH 15/19] Hopefully sorted proj files --- SmartFuseBox/SmartFuseBox.vcxproj | 10 +++--- SmartFuseBox/SmartFuseBox.vcxproj.filters | 40 ++++++++--------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index df3e36e..fceefae 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -74,6 +74,7 @@ + @@ -118,8 +119,10 @@ CppCode true - + + + @@ -163,7 +166,6 @@ - @@ -178,7 +180,7 @@ VisualMicroDebugger - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src + $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ false @@ -205,7 +207,7 @@ - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) + $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ c++17 gnu11 diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index bed5360..db6d6a7 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -160,39 +160,18 @@ Source Files\BusinessLogic - - Source Files - - - Source Files\SharedCode\SerialCommandHandlers - - - Source Files - - - Source Files - - - Source Files - Source Files\SharedCode - - Source Files - - + Source Files - + Source Files Source Files - - Source Files - Source Files @@ -342,9 +321,6 @@ Header Files\SharedCode - - Header Files - Header Files @@ -369,6 +345,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + From bbdd22517705ec3411891adb73b5a8af68687276 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 13:38:11 +0100 Subject: [PATCH 16/19] allow config load from sd card --- Commands.md | 3 +- .../CommandHandlers/ConfigCommandHandler.cpp | 53 ++++++++++++++++++- Shared/CommandHandlers/ConfigCommandHandler.h | 5 ++ Shared/SystemDefinitions.h | 2 + SmartFuseBox/SDCardConfigLoader.cpp | 11 ++-- SmartFuseBox/SmartFuseBox.ino | 11 +++- SmartFuseBox/SmartFuseBox.vcxproj | 4 +- 7 files changed, 78 insertions(+), 11 deletions(-) diff --git a/Commands.md b/Commands.md index 5d64772..b4ebcfb 100644 --- a/Commands.md +++ b/Commands.md @@ -59,7 +59,8 @@ These are commands used to configure the system settings and can only be sent fr | `C26` — LED Auto Switch | `C26:v=true` or `C26:v=1` | Enable/disable auto day/night switching. Param format: `v=`. `value` must be true/false or 1/0. | | `C27` — LED Enable States | `C27:g=true;w=true;s=false` | Enable/disable individual LEDs. Param format: `g=;w=;s=`. Each value must be true/false or 1/0. `g`=GPS LED, `w`=Warning LED, `s`=System LED. | | `C28` — Control Panel Tones | `C28:t=0;h=400;d=500;p=0;r=30000` | Configure control panel tones, 2 categories, good and bad, good is only used at startup to indicate all is working, bad is used in response to warnings. t=0 or 1 (0 good or 1 bad), h = tone Hz, d = duration in milliseconds, p = preset 0 custom tone/duration, 1 = submaring ping, 2 = double beep, 3 = rising chirp, 4 = descending alert, 5 = nautical bell, 0xFF = no sound. r = repeat interval in milliseconds where 0 is do not repeat, this is only used for bad sounds. | - +| `C29` — Reload Config from SD (SFB) | `C29` | **Local only (Serial/USB)**. Reload configuration from SD card config.txt file... | +| `C30` — Export Config to SD (SFB) | `C30` | **Local only (Serial/USB)**. Export current in-memory configuration to SD card... | Common error responses you may see: `Missing param`, `Missing params`, `Missing name`, `Empty name`, `Index out of range`, `Button out of range`, `Slot out of range`, `Relay out of range (or 255 to clear)`, `Invalid value (0 or 1)`, `Invalid mode (0=AP, 1=Client)`, `Only available in Client mode`, `Invalid port`, `Invalid offset (-12 to +14)`, `MMSI must be 9 digits`, `Invalid boat type`, `No available link slots`, `EEPROM commit failed`, `Unknown config command`, `Invalid type (0=day, 1=night)`, `Brightness must be 0-100`, `Invalid value (true/false or 0/1)`, `Missing params (t,r,g,b)`, `Missing params (t,b)`, `Missing params (g,w,s)`. diff --git a/Shared/CommandHandlers/ConfigCommandHandler.cpp b/Shared/CommandHandlers/ConfigCommandHandler.cpp index c3d34d6..483bc8e 100644 --- a/Shared/CommandHandlers/ConfigCommandHandler.cpp +++ b/Shared/CommandHandlers/ConfigCommandHandler.cpp @@ -1,5 +1,6 @@ #include "ConfigCommandHandler.h" #include "ConfigSyncManager.h" +#include "SdCardConfigLoader.h" #if defined(ARDUINO_UNO_R4) #include "BluetoothController.h" @@ -10,7 +11,8 @@ ConfigCommandHandler::ConfigCommandHandler(WifiController* wifiController, ConfigController* configController) : _wifiController(wifiController), _configController(configController), - _configSyncManager(nullptr) + _configSyncManager(nullptr), + _sdCardConfigLoader(nullptr) { } @@ -634,6 +636,48 @@ bool ConfigCommandHandler::handleCommand(SerialCommandManager* sender, const cha result = ConfigResult::InvalidParameter; } } + else if (strcmp(command, ConfigReloadFromSd) == 0) // ADD THIS BLOCK + { + // C29 - Reload config from SD card + if (_sdCardConfigLoader) + { + if (_sdCardConfigLoader->reloadConfigFromSd()) + { + result = ConfigResult::Success; + } + else + { + sendAckErr(sender, command, F("Failed to reload from SD")); + return true; + } + } + else + { + sendAckErr(sender, command, F("SD config loader not available")); + return true; + } + } + else if (strcmp(command, ConfigExportToSd) == 0) // ADD THIS BLOCK + { + // C30 - Export current config to SD card + if (_sdCardConfigLoader) + { + if (_sdCardConfigLoader->exportConfigToSd()) + { + result = ConfigResult::Success; + } + else + { + sendAckErr(sender, command, F("Failed to export to SD")); + return true; + } + } + else + { + sendAckErr(sender, command, F("SD config loader not available")); + return true; + } + } else { result = ConfigResult::InvalidCommand; @@ -677,6 +721,11 @@ bool ConfigCommandHandler::handleCommand(SerialCommandManager* sender, const cha return true; } +void ConfigCommandHandler::setSdCardConfigLoader(SdCardConfigLoader* sdCardConfigLoader) +{ + _sdCardConfigLoader = sdCardConfigLoader; +} + const char* const* ConfigCommandHandler::supportedCommands(size_t& count) const { static const char* cmds[] = { @@ -690,7 +739,7 @@ const char* const* ConfigCommandHandler::supportedCommands(size_t& count) const ConfigDefaultRelayState, ConfigLinkRelays, ConfigTimeZoneOffset, ConfigMmsi, ConfigCallSign, ConfigHomePort, ConfigLedColor, ConfigLedBrightness, ConfigLedAutoSwitch, ConfigLedEnable, - ControlPanelTones + ControlPanelTones, ConfigReloadFromSd, ConfigExportToSd }; count = sizeof(cmds) / sizeof(cmds[0]); return cmds; diff --git a/Shared/CommandHandlers/ConfigCommandHandler.h b/Shared/CommandHandlers/ConfigCommandHandler.h index 7f500a0..f633d39 100644 --- a/Shared/CommandHandlers/ConfigCommandHandler.h +++ b/Shared/CommandHandlers/ConfigCommandHandler.h @@ -4,10 +4,13 @@ #include "BaseCommandHandler.h" #include "ConfigController.h" #include "WifiController.h" +#include "ConfigSyncManager.h" +#include "SDCardConfigLoader.h" // Forward declarations class BluetoothController; class ConfigSyncManager; +class SdCardConfigLoader; class ConfigCommandHandler : public BaseCommandHandler { @@ -15,11 +18,13 @@ class ConfigCommandHandler : public BaseCommandHandler WifiController* _wifiController; ConfigController* _configController; ConfigSyncManager* _configSyncManager; + SdCardConfigLoader* _sdCardConfigLoader; public: explicit ConfigCommandHandler(WifiController* wifiController, ConfigController* configController); void setConfigSyncManager(ConfigSyncManager* syncManager); + void setSdCardConfigLoader(SdCardConfigLoader* sdCardConfigLoader); bool handleCommand(SerialCommandManager* sender, const char* command, const StringKeyValue params[], uint8_t paramCount) override; const char* const* supportedCommands(size_t& count) const override; diff --git a/Shared/SystemDefinitions.h b/Shared/SystemDefinitions.h index 90d7885..545455a 100644 --- a/Shared/SystemDefinitions.h +++ b/Shared/SystemDefinitions.h @@ -72,6 +72,8 @@ constexpr char ConfigLedBrightness[] = "C25"; constexpr char ConfigLedAutoSwitch[] = "C26"; constexpr char ConfigLedEnable[] = "C27"; constexpr char ControlPanelTones[] = "C28"; +constexpr char ConfigReloadFromSd[] = "C29"; +constexpr char ConfigExportToSd[] = "C30"; constexpr char WarningsActive[] = "W0"; constexpr char WarningsList[] = "W1"; diff --git a/SmartFuseBox/SDCardConfigLoader.cpp b/SmartFuseBox/SDCardConfigLoader.cpp index d941b8d..b8bbdde 100644 --- a/SmartFuseBox/SDCardConfigLoader.cpp +++ b/SmartFuseBox/SDCardConfigLoader.cpp @@ -100,15 +100,18 @@ bool SdCardConfigLoader::applyConfigCommand(const char* line) if (equalPos) { *equalPos = '\0'; - params[paramCount].key = param; - params[paramCount].value = equalPos + 1; + strncpy(params[paramCount].key, param, sizeof(params[paramCount].key) - 1); + params[paramCount].key[sizeof(params[paramCount].key) - 1] = '\0'; + strncpy(params[paramCount].value, equalPos + 1, sizeof(params[paramCount].value) - 1); + params[paramCount].value[sizeof(params[paramCount].value) - 1] = '\0'; } else { // For commands like C13:SSID, the whole thing after : is the value // with an implied key (often "v" or empty) - params[paramCount].key = ""; - params[paramCount].value = param; + params[paramCount].key[0] = '\0'; + strncpy(params[paramCount].value, param, sizeof(params[paramCount].value) - 1); + params[paramCount].value[sizeof(params[paramCount].value) - 1] = '\0'; } paramCount++; param = strtok_r(nullptr, ";", &savePtr1); diff --git a/SmartFuseBox/SmartFuseBox.ino b/SmartFuseBox/SmartFuseBox.ino index 146ddf3..387ae27 100644 --- a/SmartFuseBox/SmartFuseBox.ino +++ b/SmartFuseBox/SmartFuseBox.ino @@ -54,6 +54,7 @@ #include "MessageBus.h" #include "SensorDataRecord.h" #include "SdCardLogger.h" +#include "SDCardConfigLoader.h" #define COMPUTER_SERIAL Serial @@ -137,7 +138,7 @@ SensorNetworkHandler sensorNetworkHandler(&sensorController); // SD card logger SdCardLogger sdCardLogger(&sensorCommandHandler, &warningManager, SdCardCsPin); - +SdCardConfigLoader sdCardConfigLoader(&commandMgrComputer, &commandMgrLink, &configController, &configSyncManager, SdCardCsPin); void setup() { @@ -173,6 +174,7 @@ void setup() // Link config sync manager to handlers so they can coordinate config synchronization ackHandler.setConfigSyncManager(&configSyncManager, &configController); configHandler.setConfigSyncManager(&configSyncManager); + configHandler.setSdCardConfigLoader(&sdCardConfigLoader); Config* config = ConfigManager::getConfigPtr(); @@ -185,6 +187,8 @@ void setup() // Initialize SD card logger sdCardLogger.initialize(); + bool sdConfigLoaded = sdCardConfigLoader.loadConfigFromSd(); + #if defined(ARDUINO_UNO_R4) && defined(LED_MANAGER) ledManager.Initialize(); #endif @@ -198,7 +202,10 @@ void setup() } } - configSyncManager.requestSync(); + if (!sdConfigLoaded) + { + configSyncManager.requestSync(); + } // indicate system initialized commandMgrComputer.sendCommand(SystemInitialized, ""); diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index fceefae..3fe2cff 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -180,7 +180,7 @@ VisualMicroDebugger - $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ false @@ -207,7 +207,7 @@ - $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) + $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ c++17 gnu11 From 7dd6cdfdf296a0ddfc5dc2379a3b7562eb19faac Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 17:04:01 +0100 Subject: [PATCH 17/19] Finish SD integration --- BoatControlPanel/BoatControlPanel.ino | 16 +- Commands.md | 2 + .../CommandHandlers/ConfigCommandHandler.cpp | 4 +- .../CommandHandlers/SystemCommandHandler.cpp | 44 +++++- Shared/CommandHandlers/SystemCommandHandler.h | 13 +- Shared/SystemDefinitions.h | 2 + Shared/SystemFunctions.h | 19 +++ Shared/WarningManager.cpp | 6 +- SmartFuseBox/SDCardConfigLoader.cpp | 99 +++++++++++- SmartFuseBox/SDCardConfigLoader.h | 8 + SmartFuseBox/SdCardLogger.cpp | 85 +++++++++- SmartFuseBox/SdCardLogger.h | 31 +++- SmartFuseBox/SmartFuseBox.ino | 25 ++- SmartFuseBox/SmartFuseBox.vcxproj.filters | 6 + SmartFuseBox/SystemNetworkHandler.cpp | 21 ++- SmartFuseBox/SystemNetworkHandler.h | 7 + SmartFuseBox/config.txt | 146 ------------------ 17 files changed, 343 insertions(+), 191 deletions(-) delete mode 100644 SmartFuseBox/config.txt diff --git a/BoatControlPanel/BoatControlPanel.ino b/BoatControlPanel/BoatControlPanel.ino index c42ce22..9ec145f 100644 --- a/BoatControlPanel/BoatControlPanel.ino +++ b/BoatControlPanel/BoatControlPanel.ino @@ -265,18 +265,6 @@ void loop() SystemCpuMonitor::update(); } -void resetSerial(Stream& serial) -{ - // Flush outgoing data - serial.flush(); - - // Clear incoming buffer - while (serial.available() > 0) - { - serial.read(); - } -} - void onLinkCommandReceived(SerialCommandManager* mgr) { char cmd[64]; @@ -284,7 +272,7 @@ void onLinkCommandReceived(SerialCommandManager* mgr) commandMgrComputer.sendError(cmd, F("LINKHANDLER")); // Reset serial to clear any residual data - resetSerial(LINK_SERIAL); + SystemFunctions::resetSerial(LINK_SERIAL); } void onComputerCommandReceived(SerialCommandManager* mgr) @@ -295,5 +283,5 @@ void onComputerCommandReceived(SerialCommandManager* mgr) commandMgrComputer.sendError(cmd, F("PCHANDLER")); // Reset serial to clear any residual data - resetSerial(COMPUTER_SERIAL); + SystemFunctions::resetSerial(COMPUTER_SERIAL); } diff --git a/Commands.md b/Commands.md index b4ebcfb..4af32bc 100644 --- a/Commands.md +++ b/Commands.md @@ -14,6 +14,8 @@ These are commands used to configure the system settings and can only be sent fr | `F5` — Wifi Enabled | `F5` | When received will return the current enabled state of wifi 0 off 1 on. No params. | | `F6` — Set DateTime | `F6:v=2025-12-04T15:30:00` or `F6:v=1733328600` | Set current date/time. Accepts ISO 8601 datetime string `YYYY-MM-DDTHH:MM:SS` or Unix timestamp (seconds since epoch). Returns current stored time. | | `F7` — Get DateTime | `F7` | Get current date/time as ISO 8601 datetime string `YYYY-MM-DDTHH:MM:SS`. | +| `F8` — SD Card Present | `F8` | Query if SD card is present. Returns `v=0` (not present) or `v=1` (present). No params. | +| `F9` — SD Card Log File Size | `F11` | Get current log file size in bytes. Returns `v=` where bytes is the size of the active log file. Returns `v=0` if no file is open. No params. | ### Wifi System Commands (SFB) diff --git a/Shared/CommandHandlers/ConfigCommandHandler.cpp b/Shared/CommandHandlers/ConfigCommandHandler.cpp index 483bc8e..6c4c356 100644 --- a/Shared/CommandHandlers/ConfigCommandHandler.cpp +++ b/Shared/CommandHandlers/ConfigCommandHandler.cpp @@ -636,7 +636,7 @@ bool ConfigCommandHandler::handleCommand(SerialCommandManager* sender, const cha result = ConfigResult::InvalidParameter; } } - else if (strcmp(command, ConfigReloadFromSd) == 0) // ADD THIS BLOCK + else if (strcmp(command, ConfigReloadFromSd) == 0) { // C29 - Reload config from SD card if (_sdCardConfigLoader) @@ -657,7 +657,7 @@ bool ConfigCommandHandler::handleCommand(SerialCommandManager* sender, const cha return true; } } - else if (strcmp(command, ConfigExportToSd) == 0) // ADD THIS BLOCK + else if (strcmp(command, ConfigExportToSd) == 0) { // C30 - Export current config to SD card if (_sdCardConfigLoader) diff --git a/Shared/CommandHandlers/SystemCommandHandler.cpp b/Shared/CommandHandlers/SystemCommandHandler.cpp index 43123c8..af4e74e 100644 --- a/Shared/CommandHandlers/SystemCommandHandler.cpp +++ b/Shared/CommandHandlers/SystemCommandHandler.cpp @@ -18,8 +18,11 @@ SystemCommandHandler::~SystemCommandHandler() const char* const* SystemCommandHandler::supportedCommands(size_t& count) const { - static const char* cmds[] = { SystemHeartbeatCommand, SystemInitialized, SystemFreeMemory, SystemCpuUsage, - SystemBluetoothStatus, SystemWifiStatus, SystemSetDateTime, SystemGetDateTime }; + static const char* cmds[] = { + SystemHeartbeatCommand, SystemInitialized, SystemFreeMemory, SystemCpuUsage, + SystemBluetoothStatus, SystemWifiStatus, SystemSetDateTime, SystemGetDateTime, + SystemSdCardPresent, SystemSdCardLogFileSize + }; count = sizeof(cmds) / sizeof(cmds[0]); return cmds; } @@ -147,6 +150,37 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha return true; } + else if (strcmp(command, SystemSdCardPresent) == 0) + { + bool present = false; + +#if defined(ARDUINO_UNO_R4) + if (_sdCardLogger) + { + present = _sdCardLogger->isSdCardPresent(); + } +#endif + + char value = present ? '1' : '0'; + StringKeyValue param = makeParam(ValueParamName, value); + sendAckOk(sender, command, ¶m); + } + else if (strcmp(command, SystemSdCardLogFileSize) == 0) + { + uint32_t fileSize = 0; + +#if defined(ARDUINO_UNO_R4) + if (_sdCardLogger) + { + fileSize = _sdCardLogger->getCurrentLogFileSize(); + } +#endif + + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + snprintf_P(param.value, sizeof(param.value), PSTR("%lu"), (unsigned long)fileSize); + sendAckOk(sender, command, ¶m); + } else { sendAckErr(sender, command, F("Unknown system command")); @@ -156,6 +190,12 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha } #if defined(ARDUINO_UNO_R4) + +void SystemCommandHandler::setSdCardLogger(SdCardLogger* sdCardLogger) +{ + _sdCardLogger = sdCardLogger; +} + void SystemCommandHandler::setWifiController(WifiController* wifiController) { _wifiController = wifiController; diff --git a/Shared/CommandHandlers/SystemCommandHandler.h b/Shared/CommandHandlers/SystemCommandHandler.h index 5daefcd..2d573b3 100644 --- a/Shared/CommandHandlers/SystemCommandHandler.h +++ b/Shared/CommandHandlers/SystemCommandHandler.h @@ -8,11 +8,18 @@ #include "Local.h" #if defined(ARDUINO_UNO_R4) +#include "SdCardLogger.h" #include "WifiController.h" #endif class SystemCommandHandler : public SharedBaseCommandHandler { +private: +#if defined(ARDUINO_UNO_R4) + WifiController* _wifiController = nullptr; + SdCardLogger* _sdCardLogger = nullptr; +#endif + public: explicit SystemCommandHandler(BroadcastManager* broadcaster, WarningManager* warningManager); ~SystemCommandHandler(); @@ -21,12 +28,8 @@ class SystemCommandHandler : public SharedBaseCommandHandler const char* const* supportedCommands(size_t& count) const override; #if defined(ARDUINO_UNO_R4) - // Add this method void setWifiController(WifiController* wifiController); + void setSdCardLogger(SdCardLogger* sdCardLogger); #endif -private: -#if defined(ARDUINO_UNO_R4) - WifiController* _wifiController = nullptr; -#endif }; diff --git a/Shared/SystemDefinitions.h b/Shared/SystemDefinitions.h index 545455a..8845e37 100644 --- a/Shared/SystemDefinitions.h +++ b/Shared/SystemDefinitions.h @@ -17,6 +17,8 @@ constexpr char SystemBluetoothStatus[] = "F4"; constexpr char SystemWifiStatus[] = "F5"; constexpr char SystemSetDateTime[] = "F6"; constexpr char SystemGetDateTime[] = "F7"; +constexpr char SystemSdCardPresent[] = "F8"; +constexpr char SystemSdCardLogFileSize[] = "F9"; constexpr char RelayTurnAllOff[] = "R0"; constexpr char RelayTurnAllOn[] = "R1"; diff --git a/Shared/SystemFunctions.h b/Shared/SystemFunctions.h index 5c31376..ca0da87 100644 --- a/Shared/SystemFunctions.h +++ b/Shared/SystemFunctions.h @@ -87,6 +87,25 @@ class SystemFunctions { */ static bool hasElapsed(unsigned long now, unsigned long previous, unsigned long interval); + /** + * @brief Reset a serial port by flushing outgoing data and clearing incoming buffer. + * + * This can help recover from communication issues by ensuring the serial port is in a clean state. + * + * @param serial Reference to the Stream (e.g., HardwareSerial) to reset + */ + static void resetSerial(Stream& serial) + { + // Flush outgoing data + serial.flush(); + + // Clear incoming buffer + while (serial.available() > 0) + { + serial.read(); + } + } + /** * @brief Concatenate multiple strings into a provided buffer. * diff --git a/Shared/WarningManager.cpp b/Shared/WarningManager.cpp index 68180ac..b4bbc53 100644 --- a/Shared/WarningManager.cpp +++ b/Shared/WarningManager.cpp @@ -201,7 +201,11 @@ void WarningManager::sendHeartbeat() { if (_commandMgr) { - _commandMgr->sendCommand(SystemHeartbeatCommand, ""); + // Include local warnings in heartbeat + char params[32]; + snprintf_P(params, sizeof(params), PSTR("w=%s%lx"), + HexPrefix, _localWarnings); + _commandMgr->sendCommand(SystemHeartbeatCommand, params); } } diff --git a/SmartFuseBox/SDCardConfigLoader.cpp b/SmartFuseBox/SDCardConfigLoader.cpp index b8bbdde..637da52 100644 --- a/SmartFuseBox/SDCardConfigLoader.cpp +++ b/SmartFuseBox/SDCardConfigLoader.cpp @@ -11,11 +11,17 @@ SdCardConfigLoader::SdCardConfigLoader(SerialCommandManager* computerSerial, _linkSerial(linkSerial), _configController(configController), _configSyncManager(configSyncManager), + _sdCardLogger(nullptr), _csPin(csPin), _sdConfigPresent(false) { } +void SdCardConfigLoader::setSdCardLogger(SdCardLogger* sdCardLogger) +{ + _sdCardLogger = sdCardLogger; +} + bool SdCardConfigLoader::checkSdCard() { if (!_sd.begin(_csPin, SD_SCK_MHZ(50))) @@ -400,15 +406,38 @@ bool SdCardConfigLoader::loadConfigFromSd() { logInfo("Checking for SD config..."); + // Temporarily release SD card if logger is using it + bool loggerWasActive = false; + if (_sdCardLogger && _sdCardLogger->isSdCardReady()) + { + logInfo("Releasing SD card from logger..."); + _sdCardLogger->releaseSDCard(); + loggerWasActive = true; + } + if (!checkSdCard()) { logInfo("SD card not present or not accessible"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } if (!configFileExists()) { logInfo("Config file not found on SD card"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -418,6 +447,13 @@ bool SdCardConfigLoader::loadConfigFromSd() if (!configFile) { logError("Failed to open config file"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -452,28 +488,35 @@ bool SdCardConfigLoader::loadConfigFromSd() { logInfo("Saving config to EEPROM..."); ConfigResult saveResult = _configController->save(); - + if (saveResult == ConfigResult::Success) { logInfo("Config saved to EEPROM"); - + // Sync to control panel via LINK logInfo("Syncing config to control panel..."); syncConfigToLink(); - + // Disable ConfigSyncManager since SD config is authoritative if (_configSyncManager) { _configSyncManager->setEnabled(false); logInfo("ConfigSyncManager disabled (SD config active)"); } - + _sdConfigPresent = true; - + char summary[64]; snprintf(summary, sizeof(summary), "SD config loaded: %u commands applied, %u errors", successCount, errorCount); logInfo(summary); - + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + logInfo("Reacquiring SD card for logger..."); + _sdCardLogger->reacquireSDCard(); + } + return true; } else @@ -482,6 +525,12 @@ bool SdCardConfigLoader::loadConfigFromSd() } } + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -495,9 +544,25 @@ bool SdCardConfigLoader::exportConfigToSd() { logInfo("Exporting config to SD card..."); + // Temporarily release SD card if logger is using it + bool loggerWasActive = false; + if (_sdCardLogger && _sdCardLogger->isSdCardReady()) + { + logInfo("Releasing SD card from logger..."); + _sdCardLogger->releaseSDCard(); + loggerWasActive = true; + } + if (!checkSdCard()) { logError("SD card not present or not accessible"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -511,6 +576,13 @@ bool SdCardConfigLoader::exportConfigToSd() if (!configFile) { logError("Failed to create config file"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -519,6 +591,13 @@ bool SdCardConfigLoader::exportConfigToSd() { configFile.close(); logError("Config not available"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + _sdCardLogger->reacquireSDCard(); + } + return false; } @@ -708,5 +787,13 @@ bool SdCardConfigLoader::exportConfigToSd() configFile.close(); logInfo("Config exported to SD card"); + + // Reacquire SD card for logger if it was active + if (loggerWasActive && _sdCardLogger) + { + logInfo("Reacquiring SD card for logger..."); + _sdCardLogger->reacquireSDCard(); + } + return true; } diff --git a/SmartFuseBox/SDCardConfigLoader.h b/SmartFuseBox/SDCardConfigLoader.h index f7abe5c..d7d3bd0 100644 --- a/SmartFuseBox/SDCardConfigLoader.h +++ b/SmartFuseBox/SDCardConfigLoader.h @@ -5,6 +5,7 @@ #include #include "ConfigController.h" #include "ConfigSyncManager.h" +#include "SdCardLogger.h" constexpr char SD_CONFIG_FILENAME[] = "config.txt"; constexpr uint16_t SD_CONFIG_MAX_LINE_LENGTH = 128; @@ -48,6 +49,7 @@ class SdCardConfigLoader SerialCommandManager* _linkSerial; ConfigController* _configController; ConfigSyncManager* _configSyncManager; + SdCardLogger* _sdCardLogger; uint8_t _csPin; bool _sdConfigPresent; @@ -106,6 +108,12 @@ class SdCardConfigLoader ConfigSyncManager* configSyncManager, uint8_t csPin); + /** + * @brief Set the SdCardLogger reference for coordinated SD card access + * @param sdCardLogger Pointer to the SdCardLogger instance + */ + void setSdCardLogger(SdCardLogger* sdCardLogger); + /** * @brief Load configuration from SD card if present * diff --git a/SmartFuseBox/SdCardLogger.cpp b/SmartFuseBox/SdCardLogger.cpp index a0af473..a10600c 100644 --- a/SmartFuseBox/SdCardLogger.cpp +++ b/SmartFuseBox/SdCardLogger.cpp @@ -19,7 +19,10 @@ SdCardLogger::SdCardLogger(SensorCommandHandler* sensorHandler, WarningManager* _totalRecordsLogged(0), _recordsDropped(0), _sdCardErrorRaised(false), - _sdCardMissingRaised(false) + _sdCardMissingRaised(false), + _cachedTotalSize(0), + _cachedFreeSpace(0), + _initialLogFileSize(0) { memset(_currentFileName, 0, sizeof(_currentFileName)); } @@ -41,10 +44,18 @@ bool SdCardLogger::initialize() } return false; } - + _initialized = true; _sdCardPresent = true; - + + // Cache SD card size info (expensive operations done once at init) + uint32_t cardSizeBlocks = _sd.card()->sectorCount(); + _cachedTotalSize = (uint64_t)cardSizeBlocks * 512ULL; + + uint32_t freeClusterCount = _sd.freeClusterCount(); + uint32_t sectorsPerCluster = _sd.sectorsPerCluster(); + _cachedFreeSpace = (uint64_t)freeClusterCount * sectorsPerCluster * 512ULL; + return true; } @@ -285,6 +296,9 @@ bool SdCardLogger::openOrCreateFile(unsigned long now) return false; } + // Track initial file size for free space calculation + _initialLogFileSize = _currentFile.fileSize(); + // Write CSV header if new file if (!fileExists) { @@ -412,6 +426,14 @@ void SdCardLogger::checkCardPresence() _sdCardPresent = true; _initialized = true; + // Re-cache SD card size info after reinsertion + uint32_t cardSizeBlocks = _sd.card()->sectorCount(); + _cachedTotalSize = (uint64_t)cardSizeBlocks * 512ULL; + + uint32_t freeClusterCount = _sd.freeClusterCount(); + uint32_t sectorsPerCluster = _sd.sectorsPerCluster(); + _cachedFreeSpace = (uint64_t)freeClusterCount * sectorsPerCluster * 512ULL; + // Clear any SD card warnings if (_sdCardMissingRaised) { @@ -433,7 +455,7 @@ void SdCardLogger::checkCardPresence() { closeCurrentFile(); } - + _sdCardPresent = false; _initialized = false; @@ -490,3 +512,58 @@ bool SdCardLogger::isCardMissingError() // 0xFF = No card or no response return (errorCode == 0x01 || errorCode == 0x02 || errorCode == 0xFF); } + +bool SdCardLogger::isSdCardPresent() +{ + return _sdCardPresent; +} + +uint32_t SdCardLogger::getCurrentLogFileSize() const +{ + if (!_currentFile || !_currentFile.isOpen()) + { + return 0; + } + + return _currentFile.fileSize(); +} + +void SdCardLogger::releaseSDCard() +{ + if (!_initialized) + { + return; + } + + // Flush any pending writes first + flush(); + + // Close the current file + closeCurrentFile(); + + // Mark as not initialized to prevent operations during release + _initialized = false; +} + +bool SdCardLogger::reacquireSDCard() +{ + // Attempt to reinitialize the SD card + if (!initializeSdCard()) + { + return false; + } + + _initialized = true; + _sdCardPresent = true; + + // Recache SD card size info + uint32_t cardSizeBlocks = _sd.card()->sectorCount(); + _cachedTotalSize = (uint64_t)cardSizeBlocks * 512ULL; + + uint32_t freeClusterCount = _sd.freeClusterCount(); + uint32_t sectorsPerCluster = _sd.sectorsPerCluster(); + _cachedFreeSpace = (uint64_t)freeClusterCount * sectorsPerCluster * 512ULL; + + // Note: File will be reopened automatically on next update() call via openOrCreateFile() + return true; +} diff --git a/SmartFuseBox/SdCardLogger.h b/SmartFuseBox/SdCardLogger.h index ac4a043..970a02f 100644 --- a/SmartFuseBox/SdCardLogger.h +++ b/SmartFuseBox/SdCardLogger.h @@ -106,6 +106,11 @@ class SdCardLogger unsigned long _recordsDropped; bool _sdCardErrorRaised; bool _sdCardMissingRaised; + + // Cached SD card info (to avoid expensive freeClusterCount calls) + uint64_t _cachedTotalSize; + uint64_t _cachedFreeSpace; + uint32_t _initialLogFileSize; // Internal methods bool initializeSdCard(); @@ -148,7 +153,19 @@ class SdCardLogger * @return true if SD card is ready, false otherwise */ bool isSdCardReady() const { return _initialized && _sdCardPresent; } - + + /** + * @brief Check if SD card is present (may not be initialized) + * @return true if card is present, false otherwise + */ + bool isSdCardPresent(); + + /** + * @brief Get current log file size in bytes + * @return Log file size in bytes, or 0 if file not open + */ + uint32_t getCurrentLogFileSize() const; + /** * @brief Get total number of records successfully logged * @return Total records logged @@ -172,4 +189,16 @@ class SdCardLogger * Use sparingly, typically only during shutdown */ void flush(); + + /** + * @brief Temporarily release SD card access (closes file and deinitializes SD) + * Use before operations that need exclusive SD card access (e.g., config reload) + */ + void releaseSDCard(); + + /** + * @brief Re-initialize SD card after temporary release + * @return true if re-initialization successful + */ + bool reacquireSDCard(); }; diff --git a/SmartFuseBox/SmartFuseBox.ino b/SmartFuseBox/SmartFuseBox.ino index 387ae27..3626440 100644 --- a/SmartFuseBox/SmartFuseBox.ino +++ b/SmartFuseBox/SmartFuseBox.ino @@ -37,13 +37,12 @@ #include "ConfigNetworkHandler.h" #include "RelayNetworkHandler.h" #include "SoundNetworkHandler.h" -#include "WarningNetworkHandler.h" #include "SystemNetworkHandler.h" #include "SensorNetworkHandler.h" +#include "WarningNetworkHandler.h" #include "ConfigController.h" #include "ConfigSyncManager.h" -#include "RelayController.h" #include "SensorController.h" #include "SoundController.h" @@ -54,8 +53,10 @@ #include "MessageBus.h" #include "SensorDataRecord.h" #include "SdCardLogger.h" -#include "SDCardConfigLoader.h" +#if defined(CARD_CONFIG_LOADER) +#include "SDCardConfigLoader.h" +#endif #define COMPUTER_SERIAL Serial #define LINK_SERIAL Serial1 @@ -138,7 +139,10 @@ SensorNetworkHandler sensorNetworkHandler(&sensorController); // SD card logger SdCardLogger sdCardLogger(&sensorCommandHandler, &warningManager, SdCardCsPin); + +#if defined(CARD_CONFIG_LOADER) SdCardConfigLoader sdCardConfigLoader(&commandMgrComputer, &commandMgrLink, &configController, &configSyncManager, SdCardCsPin); +#endif void setup() { @@ -174,12 +178,17 @@ void setup() // Link config sync manager to handlers so they can coordinate config synchronization ackHandler.setConfigSyncManager(&configSyncManager, &configController); configHandler.setConfigSyncManager(&configSyncManager); + +#if defined(CARD_CONFIG_LOADER) configHandler.setSdCardConfigLoader(&sdCardConfigLoader); +#endif Config* config = ConfigManager::getConfigPtr(); configureWifiSupport(config); configureBluetoothSupport(config); + systemCommandHandler.setSdCardLogger(&sdCardLogger); + systemNetworkHandler.setSdCardLogger(&sdCardLogger); soundController.configUpdated(config); relayHandler.configUpdated(config); sensorManager.setup(); @@ -187,8 +196,6 @@ void setup() // Initialize SD card logger sdCardLogger.initialize(); - bool sdConfigLoaded = sdCardConfigLoader.loadConfigFromSd(); - #if defined(ARDUINO_UNO_R4) && defined(LED_MANAGER) ledManager.Initialize(); #endif @@ -202,10 +209,16 @@ void setup() } } +#if defined(CARD_CONFIG_LOADER) + // Link SD card logger to config loader for coordinated SD card access + sdCardConfigLoader.setSdCardLogger(&sdCardLogger); + + bool sdConfigLoaded = sdCardConfigLoader.loadConfigFromSd(); if (!sdConfigLoaded) { configSyncManager.requestSync(); } +#endif // indicate system initialized commandMgrComputer.sendCommand(SystemInitialized, ""); @@ -257,11 +270,13 @@ void loop() void onComputerCommandReceived(SerialCommandManager* mgr) { commandMgrComputer.sendError(mgr->getRawMessage(), F("STATCMD")); + SystemFunctions::resetSerial(COMPUTER_SERIAL); } void onLinkCommandReceived(SerialCommandManager* mgr) { commandMgrComputer.sendError(mgr->getRawMessage(), F("STATLNK")); + SystemFunctions::resetSerial(LINK_SERIAL); } void configureWifiSupport(Config* config) diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index db6d6a7..0d71793 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -175,6 +175,9 @@ Source Files + + Source Files + @@ -357,6 +360,9 @@ Header Files + + Header Files + diff --git a/SmartFuseBox/SystemNetworkHandler.cpp b/SmartFuseBox/SystemNetworkHandler.cpp index f59ce86..4b37930 100644 --- a/SmartFuseBox/SystemNetworkHandler.cpp +++ b/SmartFuseBox/SystemNetworkHandler.cpp @@ -5,7 +5,8 @@ #include "SystemFunctions.h" SystemNetworkHandler::SystemNetworkHandler(WifiController* wifiController) - : _wifiController(wifiController) + : _wifiController(wifiController), + _sdCardLogger(nullptr) { } @@ -45,7 +46,6 @@ void SystemNetworkHandler::formatStatusJson(char* buffer, size_t size) wifiEnabled = config->wifiEnabled; } - // Get runtime WiFi status if available if (_wifiController && wifiEnabled && _wifiController->isEnabled()) { rssi = _wifiController->getServer()->getSignalStrength(); @@ -54,15 +54,26 @@ void SystemNetworkHandler::formatStatusJson(char* buffer, size_t size) char dateTimeStr[DateTimeBufferLength]; DateTimeManager::formatDateTime(dateTimeStr, sizeof(dateTimeStr)); - // Enhanced JSON formatting with WiFi runtime details + bool sdPresent = false; + uint32_t logSize = 0; + + if (_sdCardLogger) + { + sdPresent = _sdCardLogger->isSdCardPresent(); + logSize = _sdCardLogger->getCurrentLogFileSize(); + } + snprintf(buffer, size, - "\"system\":{\"mem\":%d,\"cpu\":%d,\"bluetooth\":%d,\"wifi\":%d,\"rssi\":%d,\"time\":\"%s\"}", + "\"system\":{\"mem\":%d,\"cpu\":%d,\"bluetooth\":%d,\"wifi\":%d,\"rssi\":%d,\"time\":\"%s\"," + "\"sd\":{\"present\":%d,\"log\":%lu}}", SystemFunctions::freeMemory(), SystemCpuMonitor::getCpuUsage(), bluetoothEnabled, wifiEnabled, rssi, - dateTimeStr); + dateTimeStr, + sdPresent, + (unsigned long)logSize); } void SystemNetworkHandler::formatWifiStatusJson(WiFiClient* client) diff --git a/SmartFuseBox/SystemNetworkHandler.h b/SmartFuseBox/SystemNetworkHandler.h index c056fa4..0b9099c 100644 --- a/SmartFuseBox/SystemNetworkHandler.h +++ b/SmartFuseBox/SystemNetworkHandler.h @@ -3,16 +3,23 @@ #include "INetworkCommandHandler.h" #include "SystemDefinitions.h" #include "WifiController.h" +#include "SdCardLogger.h" class SystemNetworkHandler : public INetworkCommandHandler { private: WifiController* _wifiController; + SdCardLogger* _sdCardLogger; public: explicit SystemNetworkHandler(WifiController* wifiController); + void setSdCardLogger(SdCardLogger* sdCardLogger) + { + _sdCardLogger = sdCardLogger; + } + const char* getRoute() const override { return "/api/system"; } void formatWifiStatusJson(WiFiClient* client) override; diff --git a/SmartFuseBox/config.txt b/SmartFuseBox/config.txt deleted file mode 100644 index 192b7c5..0000000 --- a/SmartFuseBox/config.txt +++ /dev/null @@ -1,146 +0,0 @@ -# SmartFuseBox Configuration File -# This file is automatically loaded at boot if present on SD card -# Format: COMMAND:parameters -# Lines starting with # are comments and are ignored -# Empty lines are ignored - -# Boat Name -C3:Sea Wolf - -# Relay Names (format: index=ShortName|LongName) -# Short name: max 5 chars (used on home page) -# Long name: max 20 chars (used on buttons page) -C4:0=Nav|Navigation -C4:1=Bilge|Bilge Pump -C4:2=Light|Cabin Lights -C4:3=Pump|Water Pump -C4:4=Horn|Sound Horn -C4:5=Fan|Ventilation -C4:6=Spare|Spare 6 -C4:7=Spare|Spare 7 - -# Home Button Mappings (format: slot=relay) -# Maps home page buttons (0-3) to relays (0-7 or 255 for unmapped) -C5:0=0 -C5:1=1 -C5:2=2 -C5:3=3 - -# Button Colors (format: relay=color_index) -# Color indices: 0-7 or 255 for default -C6:0=4 -C6:1=4 -C6:2=4 -C6:3=4 -C6:4=4 -C6:5=4 -C6:6=4 -C6:7=4 - -# Vessel Type -# 0=Motor, 1=Sail, 2=Fishing, 3=Yacht -C7:v=0 - -# Sound Relay Button -# Which relay controls the horn (0-7 or 255 for unmapped) -C8:v=255 - -# Sound Start Delay (milliseconds) -C9:v=244 - -# Bluetooth Enabled (0=disabled, 1=enabled) -C10:v=0 - -# WiFi Enabled (0=disabled, 1=enabled) -C11:v=1 - -# WiFi Access Mode (0=AP, 1=Client) -C12:v=0 - -# WiFi SSID (for Client mode) -C13:SmartFuseBox - -# WiFi Password (for Client mode) -C14:sfb-776064 - -# WiFi Port (default: 80) -C15:v=80 - -# WiFi AP IP Address (for AP mode) -C17:192.168.4.1 - -# Default Relay States (format: relay=state) -# 0=off by default, 1=on by default -C18:0=0 -C18:1=0 -C18:2=0 -C18:3=0 -C18:4=0 -C18:5=0 -C18:6=0 -C18:7=0 - -# Linked Relays (format: relay1=relay2) -# When relay1 is toggled, relay2 is toggled as well -# Use 255 to unlink -C19:255=255 -C19:255=255 - -# Timezone Offset (hours from UTC, range: -12 to +14) -C20:v=0 - -# MMSI (Maritime Mobile Service Identity, 9 digits) -C21:000000000 - -# Call Sign (vessel call sign, can be empty) -C22: - -# Home Port (vessel home port, can be empty) -C23: - -# LED Colors (format: t=type;c=colorset;r=red;g=green;b=blue) -# type: 0=day, 1=night -# colorset: 0=good, 1=bad -# r,g,b: 0-255 - -# Day Mode - Good Color (blue) -C24:t=0;c=0;r=0;g=80;b=255 - -# Day Mode - Bad Color (orange) -C24:t=0;c=1;r=255;g=140;b=0 - -# Night Mode - Good Color (dark red) -C24:t=1;c=0;r=100;g=0;b=0 - -# Night Mode - Bad Color (bright orange) -C24:t=1;c=1;r=255;g=50;b=0 - -# LED Brightness (format: t=type;b=brightness) -# type: 0=day, 1=night -# brightness: 0-100 - -# Day Brightness (80%) -C25:t=0;b=80 - -# Night Brightness (20%) -C25:t=1;b=20 - -# LED Auto Day/Night Switch (0=disabled, 1=enabled) -C26:v=1 - -# LED Enable States (format: g=gps;w=warning;s=system) -# 0=disabled, 1=enabled -C27:g=1;w=1;s=1 - -# Control Panel Tones (format: t=type;h=Hz;d=duration;p=preset;r=repeat) -# type: 0=good, 1=bad -# h: tone frequency in Hz -# d: duration in milliseconds -# p: preset (0=custom, 1=submarine ping, 2=double beep, 3=rising chirp, 4=descending alert, 5=nautical bell, 0xFF=no sound) -# r: repeat interval in milliseconds (0=no repeat, only used for bad tones) - -# Good Tone (startup sound) -C28:t=0;h=1000;d=100;p=1;r=0 - -# Bad Tone (warning sound) -C28:t=1;h=400;d=500;p=5;r=30000 From 3457729f5f869309b50d0724cbcd0b6ed0d3185f Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sun, 8 Feb 2026 17:23:53 +0100 Subject: [PATCH 18/19] Change from code review --- Commands.md | 4 ++-- SmartFuseBox/SDCardConfigLoader.cpp | 2 +- SmartFuseBox/SmartFuseBox.vcxproj | 4 ++-- SmartFuseBox/SmartFuseBox.vcxproj.filters | 6 ++++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Commands.md b/Commands.md index 4af32bc..329ad28 100644 --- a/Commands.md +++ b/Commands.md @@ -12,10 +12,10 @@ These are commands used to configure the system settings and can only be sent fr | `F3` — Cpu Usage | `F3` | When received will return the current CPU usage. No params. | | `F4` — Bluetooth Enabled | `F4` | When received will return the current enabled state of bluetooth 0 off 1 on. No params. | | `F5` — Wifi Enabled | `F5` | When received will return the current enabled state of wifi 0 off 1 on. No params. | -| `F6` — Set DateTime | `F6:v=2025-12-04T15:30:00` or `F6:v=1733328600` | Set current date/time. Accepts ISO 8601 datetime string `YYYY-MM-DDTHH:MM:SS` or Unix timestamp (seconds since epoch). Returns current stored time. | +| `F6` — Set DateTime | `F6:v=1733328600` | Set current date/time. Accepts Unix timestamp (seconds since epoch). Returns current stored time. | | `F7` — Get DateTime | `F7` | Get current date/time as ISO 8601 datetime string `YYYY-MM-DDTHH:MM:SS`. | | `F8` — SD Card Present | `F8` | Query if SD card is present. Returns `v=0` (not present) or `v=1` (present). No params. | -| `F9` — SD Card Log File Size | `F11` | Get current log file size in bytes. Returns `v=` where bytes is the size of the active log file. Returns `v=0` if no file is open. No params. | +| `F9` — SD Card Log File Size | `F9` | Get current log file size in bytes. Returns `v=` where bytes is the size of the active log file. Returns `v=0` if no file is open. No params. | ### Wifi System Commands (SFB) diff --git a/SmartFuseBox/SDCardConfigLoader.cpp b/SmartFuseBox/SDCardConfigLoader.cpp index 637da52..1fa56ca 100644 --- a/SmartFuseBox/SDCardConfigLoader.cpp +++ b/SmartFuseBox/SDCardConfigLoader.cpp @@ -1,4 +1,4 @@ -#include "SdCardConfigLoader.h" +#include "SDCardConfigLoader.h" #include "ConfigManager.h" #include diff --git a/SmartFuseBox/SmartFuseBox.vcxproj b/SmartFuseBox/SmartFuseBox.vcxproj index 3fe2cff..fceefae 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj +++ b/SmartFuseBox/SmartFuseBox.vcxproj @@ -180,7 +180,7 @@ VisualMicroDebugger - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src + $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ false @@ -207,7 +207,7 @@ - $(ProjectDir)..\SmartFuseBox;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) + $(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\WiFiS3\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\dht11;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\EEPROM\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\BlockDevices;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\Storage;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\tinyusb;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino\api\deprecated-avr-comp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\cores\arduino;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\api;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\inc\instances;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\arm\CMSIS_5\CMSIS\Core\Include;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_gen;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg\bsp;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra_cfg\fsp_cfg;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_usb_basic\src\driver\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\private\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\crypto_procedures\src\sce5\plainkey\public\inc;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce\common;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\variants\UNOWIFIR4\includes\ra\fsp\src\r_sce;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\arm-none-eabi\thumb\v7e-m\fpv4-sp\hard;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\backward;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\lib\gcc\arm-none-eabi\7.2.1\include-fixed;$(ProjectDir)..\..\..\users\simon\appdata\local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\ArduinoBLE\src\utility;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SerialCommandManager\src;$(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\hardware\renesas_uno\1.5.1\libraries\SPI;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SdFat\src;$(ProjectDir)..\..\..\Users\Simon\Documents\Arduino\libraries\SensorManager\src;%(AdditionalIncludeDirectories) $(ProjectDir)..\..\..\Users\Simon\AppData\Local\arduino15\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\bin\arm-none-eabi-g++ c++17 gnu11 diff --git a/SmartFuseBox/SmartFuseBox.vcxproj.filters b/SmartFuseBox/SmartFuseBox.vcxproj.filters index 0d71793..abc04f5 100644 --- a/SmartFuseBox/SmartFuseBox.vcxproj.filters +++ b/SmartFuseBox/SmartFuseBox.vcxproj.filters @@ -178,6 +178,9 @@ Source Files + + Source Files + @@ -363,6 +366,9 @@ Header Files + + Header Files + From 735e34649cbdf1d103f3d6424dc19fe6256bc1d4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:25:38 +0100 Subject: [PATCH 19/19] Initial plan (#108) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>