From d8f4803b084873c26a051b4381474556e19a091c Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 9 May 2025 15:59:08 -0500 Subject: [PATCH 01/25] added configuration files, wip --- src/configuration/ConfigurationManager.cpp | 147 ++++++++++++++++++++ src/configuration/ConfigurationManager.h | 83 +++++++++++ src/configuration/DefaultConfiguration.json | 49 +++++++ src/configuration/IConfiguration.h | 26 ++++ test/mocks/MockConfiguration.h | 24 ++++ 5 files changed, 329 insertions(+) create mode 100644 src/configuration/ConfigurationManager.cpp create mode 100644 src/configuration/ConfigurationManager.h create mode 100644 src/configuration/DefaultConfiguration.json create mode 100644 src/configuration/IConfiguration.h create mode 100644 test/mocks/MockConfiguration.h diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp new file mode 100644 index 0000000..a9f4962 --- /dev/null +++ b/src/configuration/ConfigurationManager.cpp @@ -0,0 +1,147 @@ +/** + * @file ConfigurationManager.cpp + * @brief Implementation of the ConfigurationManager class + * + * © 2025 Regents of the University of Minnesota. All rights reserved. + */ + +#include "ConfigurationManager.h" +#include +#include + +// For Particle-based systems +#ifndef TESTING +#include "Particle.h" +#else +#include "test/mocks/Particle.h" +#endif + +ConfigurationManager::ConfigurationManager() : m_currentConfig("") { + // Constructor implementation +} + +ConfigurationManager::~ConfigurationManager() { + // Destructor implementation +} + +bool ConfigurationManager::begin(bool defaultConfig) { + if (defaultConfig) { + return loadDefaultConfiguration(); + } + return true; +} + +bool ConfigurationManager::setConfiguration(std::string config) { + // Validate the configuration format + // Here you would validate the JSON structure + + // Store the configuration + m_currentConfig = config; + + // Apply the configuration + return applyConfiguration(); +} + +std::string ConfigurationManager::getConfiguration() { + return m_currentConfig; +} + +bool ConfigurationManager::loadDefaultConfiguration() { + // Load the default configuration from the DefaultConfiguration.json file + std::ifstream configFile("configuration/DefaultConfiguration.json"); + if (configFile.is_open()) { + std::string config((std::istreambuf_iterator(configFile)), + std::istreambuf_iterator()); + configFile.close(); + + m_currentConfig = config; + return true; + } + + // If file can't be opened, use a hardcoded minimal configuration + m_currentConfig = R"({ + "config": { + "system": { + "backhaul_context": "4", + "log_period": "300", + "power_save_mode": "1", + "logging_mode": "0" + }, + "peripherals": {}, + "sensors": {} + } +})"; + + return false; +} + +bool ConfigurationManager::saveConfiguration() { + // Save the current configuration to persistent storage + // This could be a file on an SD card, EEPROM, etc. + + // Example: Save to a file + std::ofstream configFile("configuration/CurrentConfiguration.json"); + if (configFile.is_open()) { + configFile << m_currentConfig; + configFile.close(); + return true; + } + + return false; +} + +bool ConfigurationManager::applyConfiguration() { + // Parse the configuration (assuming JSON format) + // Apply settings to the appropriate components + + // Example: Log that configuration is being applied + Serial.println("Applying configuration..."); + + // TODO: Implementation would include: + // 1. Parse JSON to extract system settings + // 2. Configure system parameters like log period, power save mode, etc. + // 3. Enable/disable peripherals as specified + // 4. Configure and initialize sensors based on their settings + + // Example pseudocode: + // JsonDocument doc; + // DeserializationError error = deserializeJson(doc, m_currentConfig); + // + // if (error) { + // Serial.println("Failed to parse configuration"); + // return false; + // } + // + // // Apply system settings + // const char* logPeriod = doc["config"]["system"]["log_period"]; + // const char* powerSaveMode = doc["config"]["system"]["power_save_mode"]; + // const char* loggingMode = doc["config"]["system"]["logging_mode"]; + // + // // Configure system with these settings + // setLogPeriod(atoi(logPeriod)); + // setPowerSaveMode(atoi(powerSaveMode)); + // setLoggingMode(atoi(loggingMode)); + // + // // Enable/configure peripherals + // JsonObject peripherals = doc["config"]["peripherals"]; + // for (JsonPair peripheral : peripherals) { + // const char* id = peripheral.value()["id"]; + // bool enabled = peripheral.value()["enabled"]; + // + // // Configure the peripheral + // configurePheripheral(id, enabled); + // } + // + // // Configure sensors + // JsonObject sensors = doc["config"]["sensors"]; + // for (JsonPair sensor : sensors) { + // const char* id = sensor.value()["id"]; + // bool enabled = sensor.value()["enabled"]; + // int port = sensor.value()["port"] | 0; // Default to 0 if not specified + // + // // Configure the sensor + // configureSensor(id, enabled, port); + // } + + return true; +} \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h new file mode 100644 index 0000000..6e5f500 --- /dev/null +++ b/src/configuration/ConfigurationManager.h @@ -0,0 +1,83 @@ +/** + * @file ConfigurationManager.h + * @brief Manager for handling device configurations + * + * Provides functionality for loading, saving, and applying configurations + * + * © 2025 Regents of the University of Minnesota. All rights reserved. + */ + +#ifndef CONFIGURATION_MANAGER_H +#define CONFIGURATION_MANAGER_H + +#include +#include +#include "IConfiguration.h" + +/** + * @brief Manages configuration settings for the system + * + * Handles loading configuration from default, file or user input, + * and applies it to the appropriate components + */ +class ConfigurationManager : public IConfiguration { +public: + /** + * @brief Constructor + */ + ConfigurationManager(); + + /** + * @brief Destructor + */ + virtual ~ConfigurationManager(); + + /** + * @brief Initialize the configuration system + * + * @param defaultConfig Whether to load the default configuration + * @return true if initialization succeeded, false otherwise + */ + bool begin(bool defaultConfig = true); + + /** + * @brief Load configuration from a JSON string + * + * @param config JSON string containing configuration data + * @return true if configuration was successfully set, false otherwise + */ + bool setConfiguration(std::string config) override; + + /** + * @brief Get the current configuration as a JSON string + * + * @return std::string JSON representation of the current configuration + */ + std::string getConfiguration() override; + + /** + * @brief Load the default configuration + * + * @return true if default configuration loaded successfully, false otherwise + */ + bool loadDefaultConfiguration(); + + /** + * @brief Save the current configuration to persistent storage + * + * @return true if saved successfully, false otherwise + */ + bool saveConfiguration(); + + /** + * @brief Apply the configuration to the system + * + * @return true if configuration applied successfully, false otherwise + */ + bool applyConfiguration(); + +private: + std::string m_currentConfig; +}; + +#endif // CONFIGURATION_MANAGER_H \ No newline at end of file diff --git a/src/configuration/DefaultConfiguration.json b/src/configuration/DefaultConfiguration.json new file mode 100644 index 0000000..424d8a1 --- /dev/null +++ b/src/configuration/DefaultConfiguration.json @@ -0,0 +1,49 @@ +{ + "config": { + "system": { + "backhaul_context": "4", + "log_period": "300", + "power_save_mode": "1", + "logging_mode": "0" + }, + "peripherals": { + "filesystem": { + "id": "filesystem", + "enabled": true + }, + "aux": { + "id": "aux", + "enabled": true + }, + "i2c": { + "id": "i2c", + "enabled": true + }, + "sdi12": { + "id": "sdi12", + "enabled": true + }, + "battery": { + "id": "battery", + "enabled": true + } + }, + "sensors": { + "haar": { + "id": "haar", + "enabled": true, + "port": 0, + "version": "0x20" + }, + "et": { + "id": "et", + "enabled": true, + "port": 0 + }, + "logger": { + "id": "logger", + "enabled": true + } + } + } +} \ No newline at end of file diff --git a/src/configuration/IConfiguration.h b/src/configuration/IConfiguration.h new file mode 100644 index 0000000..e6dcf1f --- /dev/null +++ b/src/configuration/IConfiguration.h @@ -0,0 +1,26 @@ +/** + * @file IConfiguration.h + * @brief Interface for abstracting configuration functionality + * + * Defines a contract for configuring sensors for different applications + * + * © 2025 Regents of the University of Minnesota. All rights reserved. + */ + + #ifndef I_CONFIGURATION_H + #define I_CONFIGURATION_H + + #include + #include + + /** + * @brief Abstract interface for coinfiguration of sensors + */ + class IConfiguration { + public: + virtual ~IConfiguration() = default; + virtual bool setConfiguration(std::string config) = 0; + virtual std::string getConfiguration() = 0; +}; + + #endif // I_CONFIGURATION_H \ No newline at end of file diff --git a/test/mocks/MockConfiguration.h b/test/mocks/MockConfiguration.h new file mode 100644 index 0000000..bdab594 --- /dev/null +++ b/test/mocks/MockConfiguration.h @@ -0,0 +1,24 @@ +/** + * @file MockConfiguration.h + * @brief Mock implementation of the IConfiguration interface for testing + * + * © 2025 Regents of the University of Minnesota. All rights reserved. + */ + +#ifndef MOCK_CONFIGURATION_H +#define MOCK_CONFIGURATION_H + +#include +#include +#include "../../src/configuration/IConfiguration.h" + +/** + * @brief Mock implementation of the IConfiguration interface for testing + */ +class MockConfiguration : public IConfiguration { +public: + MOCK_METHOD(bool, setConfiguration, (std::string config), (override)); + MOCK_METHOD(std::string, getConfiguration, (), (override)); +}; + +#endif // MOCK_CONFIGURATION_H \ No newline at end of file From a4810fe6d9607978ccb2bc5e4a8b03ed7a9b80de Mon Sep 17 00:00:00 2001 From: zradlicz Date: Mon, 12 May 2025 12:42:54 -0500 Subject: [PATCH 02/25] wip on configuration --- src/configuration/ConfigurationManager.cpp | 588 +++++++++++++++----- src/configuration/ConfigurationManager.h | 174 +++--- src/configuration/DefaultConfiguration.json | 94 ++-- 3 files changed, 596 insertions(+), 260 deletions(-) diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index a9f4962..303b565 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -1,147 +1,453 @@ /** * @file ConfigurationManager.cpp - * @brief Implementation of the ConfigurationManager class + * @brief Implementation of ConfigurationManager class * * © 2025 Regents of the University of Minnesota. All rights reserved. */ -#include "ConfigurationManager.h" -#include -#include - -// For Particle-based systems -#ifndef TESTING -#include "Particle.h" -#else -#include "test/mocks/Particle.h" -#endif - -ConfigurationManager::ConfigurationManager() : m_currentConfig("") { - // Constructor implementation -} - -ConfigurationManager::~ConfigurationManager() { - // Destructor implementation -} - -bool ConfigurationManager::begin(bool defaultConfig) { - if (defaultConfig) { - return loadDefaultConfiguration(); - } - return true; -} - -bool ConfigurationManager::setConfiguration(std::string config) { - // Validate the configuration format - // Here you would validate the JSON structure - - // Store the configuration - m_currentConfig = config; - - // Apply the configuration - return applyConfiguration(); -} - -std::string ConfigurationManager::getConfiguration() { - return m_currentConfig; -} - -bool ConfigurationManager::loadDefaultConfiguration() { - // Load the default configuration from the DefaultConfiguration.json file - std::ifstream configFile("configuration/DefaultConfiguration.json"); - if (configFile.is_open()) { - std::string config((std::istreambuf_iterator(configFile)), - std::istreambuf_iterator()); - configFile.close(); - - m_currentConfig = config; - return true; - } - - // If file can't be opened, use a hardcoded minimal configuration - m_currentConfig = R"({ - "config": { - "system": { - "backhaul_context": "4", - "log_period": "300", - "power_save_mode": "1", - "logging_mode": "0" - }, - "peripherals": {}, - "sensors": {} - } -})"; - - return false; -} - -bool ConfigurationManager::saveConfiguration() { - // Save the current configuration to persistent storage - // This could be a file on an SD card, EEPROM, etc. - - // Example: Save to a file - std::ofstream configFile("configuration/CurrentConfiguration.json"); - if (configFile.is_open()) { - configFile << m_currentConfig; - configFile.close(); - return true; - } - - return false; -} - -bool ConfigurationManager::applyConfiguration() { - // Parse the configuration (assuming JSON format) - // Apply settings to the appropriate components - - // Example: Log that configuration is being applied - Serial.println("Applying configuration..."); - - // TODO: Implementation would include: - // 1. Parse JSON to extract system settings - // 2. Configure system parameters like log period, power save mode, etc. - // 3. Enable/disable peripherals as specified - // 4. Configure and initialize sensors based on their settings - - // Example pseudocode: - // JsonDocument doc; - // DeserializationError error = deserializeJson(doc, m_currentConfig); - // - // if (error) { - // Serial.println("Failed to parse configuration"); - // return false; - // } - // - // // Apply system settings - // const char* logPeriod = doc["config"]["system"]["log_period"]; - // const char* powerSaveMode = doc["config"]["system"]["power_save_mode"]; - // const char* loggingMode = doc["config"]["system"]["logging_mode"]; - // - // // Configure system with these settings - // setLogPeriod(atoi(logPeriod)); - // setPowerSaveMode(atoi(powerSaveMode)); - // setLoggingMode(atoi(loggingMode)); - // - // // Enable/configure peripherals - // JsonObject peripherals = doc["config"]["peripherals"]; - // for (JsonPair peripheral : peripherals) { - // const char* id = peripheral.value()["id"]; - // bool enabled = peripheral.value()["enabled"]; - // - // // Configure the peripheral - // configurePheripheral(id, enabled); - // } - // - // // Configure sensors - // JsonObject sensors = doc["config"]["sensors"]; - // for (JsonPair sensor : sensors) { - // const char* id = sensor.value()["id"]; - // bool enabled = sensor.value()["enabled"]; - // int port = sensor.value()["port"] | 0; // Default to 0 if not specified - // - // // Configure the sensor - // configureSensor(id, enabled, port); - // } - - return true; -} \ No newline at end of file + #include "ConfigurationManager.h" + #include + + // External globals needed for configuration + extern Talon* talons[]; + extern ITimeProvider& realTimeProvider; + extern SDI12Talon& sdi12; + + ConfigurationManager::ConfigurationManager() + : m_sensorCount(0) + , m_availableSensors(nullptr) + , m_availableSensorCount(0) + , m_logPeriod(300) + , m_backhaulCount(4) + , m_powerSaveMode(1) + , m_loggingMode(0) + { + // Initialize all sensors as not in use and not enabled + for (int i = 0; i < MAX_TOTAL_SENSORS; i++) { + m_sensorEnabled[i] = false; + } + + // Initialize dynamic sensors as not in use + for (int i = 0; i < MAX_DYNAMIC_SENSORS; i++) { + m_dynamicSensorInUse[i] = false; + } + } + + void ConfigurationManager::registerAvailableSensors(Sensor** availableSensors, uint8_t count) { + m_availableSensors = availableSensors; + m_availableSensorCount = count; + + // Clear the sensor pool + clearSensorPool(); + } + + bool ConfigurationManager::setConfiguration(std::string config) { + // Reset the sensor pool + clearSensorPool(); + + // Parse and apply the configuration + return parseConfiguration(config); + } + + std::string ConfigurationManager::getConfiguration() { + std::string config = "{\"config\":{"; + + // System configuration + config += "\"system\":{"; + config += "\"logPeriod\":" + std::to_string(m_logPeriod) + ","; + config += "\"backhaulCount\":" + std::to_string(m_backhaulCount) + ","; + config += "\"powerSaveMode\":" + std::to_string(m_powerSaveMode) + ","; + config += "\"loggingMode\":" + std::to_string(m_loggingMode); + config += "},"; + + // Sensors configuration + config += "\"sensors\":["; + + bool firstSensor = true; + // Add all enabled sensors to the configuration + for (uint8_t i = 0; i < m_sensorCount; i++) { + if (!firstSensor) config += ","; + firstSensor = false; + + config += "{"; + config += "\"type\":\"" + std::string(m_sensorPool[i]->getType()) + "\","; + config += "\"enabled\":true,"; + + // Only include port and talonPort if they're defined (non-zero) + if (m_sensorPool[i]->getSensorPort() > 0) { + config += "\"port\":" + std::to_string(m_sensorPool[i]->getSensorPort()) + ","; + } + + if (m_sensorPool[i]->getTalonPort() > 0) { + config += "\"talonPort\":" + std::to_string(m_sensorPool[i]->getTalonPort()) + ","; + } + + // Add version if available + if (m_sensorPool[i]->getVersion() != 0) { + char versionHex[10]; + snprintf(versionHex, sizeof(versionHex), "0x%X", m_sensorPool[i]->getVersion()); + config += "\"version\":\"" + std::string(versionHex) + "\""; + } else { + // Remove trailing comma if no version + if (config.back() == ',') { + config.pop_back(); + } + } + + config += "}"; + } + + // Also add disabled core sensors with enabled:false + for (uint8_t i = 0; i < m_availableSensorCount; i++) { + bool isEnabled = false; + for (uint8_t j = 0; j < m_sensorCount; j++) { + if (m_sensorPool[j] == m_availableSensors[i]) { + isEnabled = true; + break; + } + } + + if (!isEnabled) { + if (!firstSensor) config += ","; + firstSensor = false; + + config += "{"; + config += "\"type\":\"" + std::string(m_availableSensors[i]->getType()) + "\","; + config += "\"enabled\":false"; + config += "}"; + } + } + + config += "]"; + config += "}}"; + + return config; + } + + Sensor** ConfigurationManager::getSensorArray() { + return m_sensorPool; + } + + uint8_t ConfigurationManager::getSensorCount() const { + return m_sensorCount; + } + + void ConfigurationManager::clearSensorPool() { + // Reset to empty pool + m_sensorCount = 0; + + // Mark all pre-allocated sensors as not in use + for (int i = 0; i < MAX_DYNAMIC_SENSORS; i++) { + m_dynamicSensorInUse[i] = false; + } + } + + bool ConfigurationManager::parseConfiguration(const std::string& configStr) { + // Find the system configuration section + size_t systemStart = configStr.find("\"system\":{"); + if (systemStart != std::string::npos) { + size_t systemEnd = findMatchingBracket(configStr, systemStart + 9); + if (systemEnd > 0) { + std::string systemJson = configStr.substr(systemStart + 9, systemEnd - (systemStart + 9)); + + // Parse system settings + m_logPeriod = extractJsonIntField(systemJson, "logPeriod", 300); + m_backhaulCount = extractJsonIntField(systemJson, "backhaulCount", 4); + m_powerSaveMode = extractJsonIntField(systemJson, "powerSaveMode", 1); + m_loggingMode = extractJsonIntField(systemJson, "loggingMode", 0); + } + } + + // Find the sensors array + size_t sensorsStart = configStr.find("\"sensors\":["); + if (sensorsStart == std::string::npos) { + return false; + } + + size_t sensorsEnd = findMatchingBracket(configStr, sensorsStart + 10); + if (sensorsEnd == std::string::npos) { + return false; + } + + // Extract sensors array content + std::string sensorsJson = configStr.substr(sensorsStart + 10, sensorsEnd - (sensorsStart + 10)); + + // Count sensors to check if we exceed the limit + int sensorCount = 0; + size_t pos = 0; + while ((pos = sensorsJson.find('{', pos)) != std::string::npos) { + sensorCount++; + pos++; + } + + // Check if we have too many sensors + if (sensorCount > MAX_TOTAL_SENSORS) { + Serial.printlnf("Error: Too many sensors in configuration (%d > %d)", + sensorCount, MAX_TOTAL_SENSORS); + return false; + } + + // Parse each sensor object + pos = 0; + while (pos < sensorsJson.length()) { + size_t objStart = sensorsJson.find('{', pos); + if (objStart == std::string::npos) break; + + size_t objEnd = findMatchingBracket(sensorsJson, objStart); + if (objEnd == std::string::npos) break; + + std::string sensorObj = sensorsJson.substr(objStart + 1, objEnd - objStart - 1); + + // Extract sensor properties + std::string type = extractJsonField(sensorObj, "type"); + if (type.empty()) { + Serial.println("Error: Sensor type missing"); + pos = objEnd + 1; + continue; + } + + // Check if this sensor is enabled + bool enabled = extractJsonBoolField(sensorObj, "enabled", true); + + if (enabled) { + // Check if this is a core sensor first + if (!enableCoreSensor(type)) { + // If not a core sensor, try to configure a dynamic sensor + int port = extractJsonIntField(sensorObj, "port", 0); + int talonPort = extractJsonIntField(sensorObj, "talonPort", 0); + std::string versionStr = extractJsonField(sensorObj, "version"); + + int version = 0; + if (versionStr.substr(0, 2) == "0x") { + version = strtol(versionStr.c_str() + 2, NULL, 16); + } else if (!versionStr.empty()) { + version = atoi(versionStr.c_str()); + } + + if (!configureSensor(type, port, talonPort, version)) { + Serial.printlnf("Warning: Failed to configure sensor %s", type.c_str()); + } + } + } + + pos = objEnd + 1; + } + + return true; + } + + Sensor* ConfigurationManager::findAvailableSensorByType(const std::string& type) { + // Search through available sensors for matching type + for (uint8_t i = 0; i < m_availableSensorCount; i++) { + if (strcmp(m_availableSensors[i]->getType(), type.c_str()) == 0) { + return m_availableSensors[i]; + } + } + return nullptr; + } + + bool ConfigurationManager::enableCoreSensor(const std::string& type) { + // Find a sensor with matching type in available sensors + Sensor* sensor = findAvailableSensorByType(type); + if (sensor) { + // Check if it's already in the pool + for (uint8_t i = 0; i < m_sensorCount; i++) { + if (m_sensorPool[i] == sensor) { + // Already enabled + return true; + } + } + + // Add to pool if not already there + if (m_sensorCount < MAX_TOTAL_SENSORS) { + m_sensorPool[m_sensorCount++] = sensor; + return true; + } + } + return false; + } + + bool ConfigurationManager::configureSensor(const std::string& type, int port, int talonPort, int version) { + // If we're already at capacity, fail + if (m_sensorCount >= MAX_TOTAL_SENSORS) { + return false; + } + + // Find appropriate Talon + Talon* talon = nullptr; + if (talonPort > 0 && talonPort <= Kestrel::numTalonPorts) { + talon = talons[talonPort - 1]; + } + + // Configure the appropriate pre-allocated sensor + if (type == "Haar") { + if (!m_dynamicSensorInUse[0]) { + m_haar.setTalonPort(talonPort); + m_haar.setSensorPort(port); + m_haar.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_haar; + m_dynamicSensorInUse[0] = true; + return true; + } + } + else if (type == "Hedorah") { + if (!m_dynamicSensorInUse[1]) { + m_hedorah.setTalonPort(talonPort); + m_hedorah.setSensorPort(port); + m_hedorah.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_hedorah; + m_dynamicSensorInUse[1] = true; + return true; + } + } + else if (type == "T9602") { + if (!m_dynamicSensorInUse[2]) { + m_t9602.setTalonPort(talonPort); + m_t9602.setSensorPort(port); + m_t9602.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_t9602; + m_dynamicSensorInUse[2] = true; + return true; + } + } + else if (type == "TDR315H") { + // Find an available TDR315H instance + for (int i = 0; i < 3; i++) { + if (!m_dynamicSensorInUse[3 + i]) { + m_tdr315h[i].setTalonPort(talonPort); + m_tdr315h[i].setSensorPort(port); + m_tdr315h[i].setVersion(version); + m_sensorPool[m_sensorCount++] = &m_tdr315h[i]; + m_dynamicSensorInUse[3 + i] = true; + return true; + } + } + } + else if (type == "TEROS11") { + // Find an available TEROS11 instance + for (int i = 0; i < 2; i++) { + if (!m_dynamicSensorInUse[6 + i]) { + m_teros11[i].setTalonPort(talonPort); + m_teros11[i].setSensorPort(port); + m_teros11[i].setVersion(version); + m_sensorPool[m_sensorCount++] = &m_teros11[i]; + m_dynamicSensorInUse[6 + i] = true; + return true; + } + } + } + else if (type == "LI710") { + if (!m_dynamicSensorInUse[8]) { + m_li710.setTalonPort(talonPort); + m_li710.setSensorPort(port); + m_li710.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_li710; + m_dynamicSensorInUse[8] = true; + return true; + } + } + else if (type == "SO421") { + if (!m_dynamicSensorInUse[9]) { + m_so421.setTalonPort(talonPort); + m_so421.setSensorPort(port); + m_so421.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_so421; + m_dynamicSensorInUse[9] = true; + return true; + } + } + else if (type == "SP421") { + if (!m_dynamicSensorInUse[10]) { + m_sp421.setTalonPort(talonPort); + m_sp421.setSensorPort(port); + m_sp421.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_sp421; + m_dynamicSensorInUse[10] = true; + return true; + } + } + else if (type == "ATMOS22") { + if (!m_dynamicSensorInUse[11]) { + m_atmos22.setTalonPort(talonPort); + m_atmos22.setSensorPort(port); + m_atmos22.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_atmos22; + m_dynamicSensorInUse[11] = true; + return true; + } + } + else if (type == "BaroVue10") { + if (!m_dynamicSensorInUse[12]) { + m_barovue.setTalonPort(talonPort); + m_barovue.setSensorPort(port); + m_barovue.setVersion(version); + m_sensorPool[m_sensorCount++] = &m_barovue; + m_dynamicSensorInUse[12] = true; + return true; + } + } + // Add more sensor types as needed + + return false; // Failed to configure sensor + } + + std::string ConfigurationManager::extractJsonField(const std::string& json, const std::string& fieldName) { + std::string searchStr = "\"" + fieldName + "\":"; + size_t fieldStart = json.find(searchStr); + if (fieldStart == std::string::npos) return ""; + + fieldStart += searchStr.length(); + // Skip whitespace + while (fieldStart < json.length() && isspace(json[fieldStart])) { + fieldStart++; + } + + // Check if string value + if (json[fieldStart] == '"') { + size_t valueEnd = json.find('"', fieldStart + 1); + if (valueEnd == std::string::npos) return ""; + return json.substr(fieldStart + 1, valueEnd - fieldStart - 1); + } + + // Must be numeric or boolean value + size_t valueEnd = fieldStart; + while (valueEnd < json.length() && + (isalnum(json[valueEnd]) || json[valueEnd] == '.' || json[valueEnd] == '-')) { + valueEnd++; + } + + return json.substr(fieldStart, valueEnd - fieldStart); + } + + int ConfigurationManager::extractJsonIntField(const std::string& json, const std::string& fieldName, int defaultValue) { + std::string value = extractJsonField(json, fieldName); + if (value.empty()) return defaultValue; + return atoi(value.c_str()); + } + + bool ConfigurationManager::extractJsonBoolField(const std::string& json, const std::string& fieldName, bool defaultValue) { + std::string value = extractJsonField(json, fieldName); + if (value.empty()) return defaultValue; + return (value == "true" || value == "True" || value == "TRUE" || value == "1"); + } + + int ConfigurationManager::findMatchingBracket(const std::string& str, int openPos) { + char open = str[openPos]; + char close; + + if (open == '{') close = '}'; + else if (open == '[') close = ']'; + else if (open == '(') close = ')'; + else return -1; + + int depth = 1; + for (size_t i = openPos + 1; i < str.length(); i++) { + if (str[i] == open) depth++; + else if (str[i] == close) { + depth--; + if (depth == 0) return i; + } + } + + return -1; // No matching bracket found + } \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 6e5f500..023dd76 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -1,83 +1,101 @@ /** * @file ConfigurationManager.h - * @brief Manager for handling device configurations + * @brief Configuration manager for sensor deployment * - * Provides functionality for loading, saving, and applying configurations - * - * © 2025 Regents of the University of Minnesota. All rights reserved. + * © 2025 Regents of the University of Minnesota. All rights reserved. */ -#ifndef CONFIGURATION_MANAGER_H -#define CONFIGURATION_MANAGER_H - -#include -#include -#include "IConfiguration.h" - -/** - * @brief Manages configuration settings for the system - * - * Handles loading configuration from default, file or user input, - * and applies it to the appropriate components - */ -class ConfigurationManager : public IConfiguration { -public: - /** - * @brief Constructor - */ - ConfigurationManager(); - - /** - * @brief Destructor - */ - virtual ~ConfigurationManager(); - - /** - * @brief Initialize the configuration system - * - * @param defaultConfig Whether to load the default configuration - * @return true if initialization succeeded, false otherwise - */ - bool begin(bool defaultConfig = true); - - /** - * @brief Load configuration from a JSON string - * - * @param config JSON string containing configuration data - * @return true if configuration was successfully set, false otherwise - */ - bool setConfiguration(std::string config) override; - - /** - * @brief Get the current configuration as a JSON string - * - * @return std::string JSON representation of the current configuration - */ - std::string getConfiguration() override; - - /** - * @brief Load the default configuration - * - * @return true if default configuration loaded successfully, false otherwise - */ - bool loadDefaultConfiguration(); - - /** - * @brief Save the current configuration to persistent storage - * - * @return true if saved successfully, false otherwise - */ - bool saveConfiguration(); - - /** - * @brief Apply the configuration to the system - * - * @return true if configuration applied successfully, false otherwise - */ - bool applyConfiguration(); - -private: - std::string m_currentConfig; -}; - -#endif // CONFIGURATION_MANAGER_H \ No newline at end of file + #ifndef CONFIGURATION_MANAGER_H + #define CONFIGURATION_MANAGER_H + + #include "IConfiguration.h" + #include + #include + // These are for pre-allocation of sensors + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + class ConfigurationManager : public IConfiguration { + public: + // Constants for fixed allocation + static const uint8_t MAX_TOTAL_SENSORS = 20; // Maximum number of sensors (core + dynamic) + static const uint8_t MAX_DYNAMIC_SENSORS = 14; // Maximum number of dynamic sensors + + ConfigurationManager(); + ~ConfigurationManager() = default; // No dynamic allocation, no need for custom destructor + + // IConfiguration implementation + bool setConfiguration(std::string config) override; + std::string getConfiguration() override; + + // Sensor pool access + Sensor** getSensorArray(); + uint8_t getSensorCount() const; + + // Method to register available sensors (called once at startup) + void registerAvailableSensors(Sensor** availableSensors, uint8_t count); + + // System configuration getters + unsigned long getLogPeriod() const { return m_logPeriod; } + int getBackhaulCount() const { return m_backhaulCount; } + int getPowerSaveMode() const { return m_powerSaveMode; } + int getLoggingMode() const { return m_loggingMode; } + + private: + // Fixed sensor array for all sensors (core + dynamic) + Sensor* m_sensorPool[MAX_TOTAL_SENSORS]; + uint8_t m_sensorCount; + + // Array to store pointers to available sensors (both core and optional) + Sensor** m_availableSensors; + uint8_t m_availableSensorCount; + + // Array to track which sensors are enabled + bool m_sensorEnabled[MAX_TOTAL_SENSORS]; + + // Pre-allocated sensors for dynamic configuration + // These sensors are declared here but initialized on demand + Haar m_haar; + Hedorah m_hedorah; + T9602 m_t9602; + TDR315H m_tdr315h[3]; // Allow up to 3 soil sensors + TEROS11 m_teros11[2]; // Allow up to 2 TEROS11 sensors + LI710 m_li710; + SO421 m_so421; + SP421 m_sp421; + ATMOS22 m_atmos22; + BaroVue10 m_barovue; + // Add other sensor types as needed + + // Flags to track which pre-allocated sensors are in use + bool m_dynamicSensorInUse[MAX_DYNAMIC_SENSORS]; + + // System configuration + unsigned long m_logPeriod; + int m_backhaulCount; + int m_powerSaveMode; + int m_loggingMode; + + // Internal methods + void clearSensorPool(); + bool parseConfiguration(const std::string& config); + bool configureSensor(const std::string& type, int port, int talonPort, int version); + bool enableCoreSensor(const std::string& type); + Sensor* findAvailableSensorByType(const std::string& type); + + // JSON parsing helpers + std::string extractJsonField(const std::string& json, const std::string& fieldName); + int extractJsonIntField(const std::string& json, const std::string& fieldName, int defaultValue); + bool extractJsonBoolField(const std::string& json, const std::string& fieldName, bool defaultValue); + int findMatchingBracket(const std::string& str, int openPos); + }; + + #endif // CONFIGURATION_MANAGER_H \ No newline at end of file diff --git a/src/configuration/DefaultConfiguration.json b/src/configuration/DefaultConfiguration.json index 424d8a1..a095db0 100644 --- a/src/configuration/DefaultConfiguration.json +++ b/src/configuration/DefaultConfiguration.json @@ -1,49 +1,61 @@ -{ +/** + * @file DefaultConfiguration.json + * @brief Default configuration for the Kestrel system + * + * © 2025 Regents of the University of Minnesota. All rights reserved. + */ + { "config": { "system": { - "backhaul_context": "4", - "log_period": "300", - "power_save_mode": "1", - "logging_mode": "0" + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 0 }, - "peripherals": { - "filesystem": { - "id": "filesystem", - "enabled": true - }, - "aux": { - "id": "aux", - "enabled": true - }, - "i2c": { - "id": "i2c", - "enabled": true - }, - "sdi12": { - "id": "sdi12", - "enabled": true - }, - "battery": { - "id": "battery", - "enabled": true - } - }, - "sensors": { - "haar": { - "id": "haar", - "enabled": true, - "port": 0, - "version": "0x20" - }, - "et": { - "id": "et", - "enabled": true, - "port": 0 + "sensors": [ + { + "type": "FileSystem", + "enabled": true + }, + { + "type": "Aux", + "enabled": true + }, + { + "type": "I2C", + "enabled": true + }, + { + "type": "SDI12", + "enabled": true + }, + { + "type": "Battery", + "enabled": true }, - "logger": { - "id": "logger", + { + "type": "Logger", "enabled": true + }, + { + "type": "Haar", + "enabled": true, + "port": 1, + "talonPort": 1, + "version": "0x20" + }, + { + "type": "LI710", + "enabled": true, + "port": 1, + "talonPort": 3 + }, + { + "type": "TDR315H", + "enabled": true, + "port": 2, + "talonPort": 3 } - } + ] } } \ No newline at end of file From dd9c0ef1ee202457f38fed8984e2e8f1490bc7fa Mon Sep 17 00:00:00 2001 From: zradlicz Date: Mon, 12 May 2025 15:10:09 -0500 Subject: [PATCH 03/25] added read from sd funciton and updated file handler submodule --- lib/Driver_-_Kestrel-FileHandler | 2 +- src/FlightControl_Demo.cpp | 99 +++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index a284d03..66efa09 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit a284d0348dbde5c974b2f826df0a3582381aca67 +Subproject commit 66efa098a0c0f5e70622233d8272d214120afc11 diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 00cd96c..b863b9c 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -33,6 +33,7 @@ int takeSample(String dummy); int commandExe(String command); int systemRestart(String resetType); int configurePowerSave(int desiredPowerSaveMode); +int updateConfiguration(String configJson); #define WAIT_GPS false #define USE_CELL //System attempts to connect to cell @@ -77,6 +78,8 @@ int configurePowerSave(int desiredPowerSaveMode); #include "hardware/AccelerometerMXC6655.h" #include "hardware/AccelerometerBMA456.h" +#include "configuration/ConfigurationManager.h" + const String firmwareVersion = "2.9.11"; const String schemaVersion = "2.2.9"; @@ -155,8 +158,8 @@ namespace LogModes { //PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product PRODUCT_VERSION(34) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number -const int backhaulCount = 4; //Number of log events before backhaul is performed -const unsigned long logPeriod = 300; //Number of seconds to wait between logging events +int backhaulCount = 4; //Number of log events before backhaul is performed +unsigned long logPeriod = 300; //Number of seconds to wait between logging events int desiredPowerSaveMode = PowerSaveModes::LOW_POWER; //Specify the power save mode you wish to use: PERFORMANCE, BALANCED, LOW_POWER, ULTRA_LOW_POWER int loggingMode = LogModes::STANDARD; //Specify the type of logging mode you wish to use: STANDARD, PERFORMANCE, BALANCED, NO_LOCAL @@ -193,6 +196,20 @@ Sensor* const sensors[numSensors] = { // &gas, // &apogeeO2, }; + +// Define available sensors array (all sensors that could be enabled/disabled) +Sensor* availableSensors[] = { + &fileSys, + &aux, + &i2c, + &sdi12, + &battery, + &logger +}; +const uint8_t availableSensorCount = sizeof(availableSensors) / sizeof(availableSensors[0]); + +// Create configuration manager +ConfigurationManager configManager; /////////////////////////// END USER CONFIG ///////////////////////////////// namespace PinsIO { //For Kestrel v1.1 @@ -255,6 +272,7 @@ void setup() { // talons[aux.getTalonPort()] = &aux; //Place talon objects at coresponding positions in array // talons[aux1.getTalonPort()] = &aux1; time_t startTime = millis(); + Particle.function("updateConfig", updateConfiguration); Particle.function("nodeID", setNodeID); Particle.function("findSensors", detectSensors); Particle.function("findTalons", detectTalons); @@ -312,7 +330,54 @@ void setup() { // logger.enableData(i, false); //Turn off all data by default // } + // Initialize configuration system + Serial.println("Initializing configuration system..."); + + // Register available sensors with configuration manager + configManager.registerAvailableSensors(availableSensors, availableSensorCount); + + detectTalons(); + + // Load configuration from SD card if possible + bool configLoaded = false; + if (!hasCriticalError) { + std::string configStr = fileSys.readFromSD("config.json").c_str(); + if (!configStr.empty()) { + Serial.println("Loading configuration from SD card..."); + configLoaded = configManager.setConfiguration(configStr); + } + } + + // If no config loaded from SD, use default + if (!configLoaded) { + Serial.println("Loading default configuration..."); + std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0},\"sensors\":["; + + // Add all available sensors as enabled by default + for (uint8_t i = 0; i < availableSensorCount; i++) { + if (i > 0) defaultConfig += ","; + defaultConfig += "{\"type\":\"" + std::string(availableSensors[i]->getType()) + "\",\"enabled\":true}"; + } + + defaultConfig += "]}}"; + configManager.setConfiguration(defaultConfig); + + // Save default config to SD card + if (!hasCriticalError) { + fileSys.writeToSD(defaultConfig.c_str(), "config.json"); + } + } + + // Set global variables from configuration + logPeriod = configManager.getLogPeriod(); + backhaulCount = configManager.getBackhaulCount(); + desiredPowerSaveMode = configManager.getPowerSaveMode(); + loggingMode = configManager.getLoggingMode(); + + // Apply power save mode + configurePowerSave(desiredPowerSaveMode); + detectSensors(); // I2C_OnBoardEn(false); @@ -1322,6 +1387,36 @@ int setNodeID(String nodeID) } } +int updateConfiguration(String configJson) { + Serial.println("Updating configuration..."); + Serial.println(configJson); + + // Convert String to std::string + std::string configStr = configJson.c_str(); + + if (configManager.setConfiguration(configStr)) { + // Update global variables + logPeriod = configManager.getLogPeriod(); + backhaulCount = configManager.getBackhaulCount(); + desiredPowerSaveMode = configManager.getPowerSaveMode(); + loggingMode = configManager.getLoggingMode(); + + // Apply power save mode + configurePowerSave(desiredPowerSaveMode); + + // Save configuration to SD card + fileSys.writeToSD(configStr.c_str(), "config.json"); + + // Re-initialize sensors if needed + // initSensors(); + + return 1; // Success + } + + Serial.println("Failed to update configuration"); + return -1; // Failure +} + int takeSample(String dummy) { logger.wake(); //Wake logger in case it was sleeping From 504a7304bec51708d7a34549cfea60329f066f41 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Tue, 13 May 2025 10:17:44 -0500 Subject: [PATCH 04/25] wip config --- src/FlightControl_Demo.cpp | 35 ++++++++++++++-- src/configuration/ConfigurationManager.cpp | 46 +++++++++++++++++++++- src/configuration/ConfigurationManager.h | 3 ++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index b863b9c..fe852a9 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -1391,6 +1391,12 @@ int updateConfiguration(String configJson) { Serial.println("Updating configuration..."); Serial.println(configJson); + // Validate JSON format + if (configJson.indexOf("\"config\"") == -1) { + Serial.println("Error: Invalid configuration format. Missing 'config' element."); + return -2; // Invalid format + } + // Convert String to std::string std::string configStr = configJson.c_str(); @@ -1405,11 +1411,34 @@ int updateConfiguration(String configJson) { configurePowerSave(desiredPowerSaveMode); // Save configuration to SD card - fileSys.writeToSD(configStr.c_str(), "config.json"); + bool saveSuccess = fileSys.writeToSD(configStr.c_str(), "config.json"); + if (!saveSuccess) { + Serial.println("Warning: Failed to save configuration to SD card"); + } + + // Reset sensors for reconfiguration + Serial.println("Reconfiguring sensors based on new configuration..."); + + // First power down all sensors to ensure clean state + sleepSensors(); + + // Reset all sensor ports to ensure they'll be re-detected + for (int i = 0; i < numSensors; i++) { + if (sensors[i]->sensorInterface != BusType::CORE) { + sensors[i]->setTalonPort(0); + sensors[i]->setSensorPort(0); + } + } + + // Re-detect talons and sensors + detectTalons(); + detectSensors(); - // Re-initialize sensors if needed - // initSensors(); + // Wake sensors and re-initialize them + wakeSensors(); + initSensors(); + Serial.println("Configuration update complete"); return 1; // Success } diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index 303b565..6f9ec63 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -42,8 +42,8 @@ } bool ConfigurationManager::setConfiguration(std::string config) { - // Reset the sensor pool - clearSensorPool(); + // Fully reset all sensors + resetSensors(); // Parse and apply the configuration return parseConfiguration(config); @@ -142,6 +142,48 @@ } } + void ConfigurationManager::resetSensors() { + // Clear the sensor pool + clearSensorPool(); + + // Reset all pre-allocated sensors (completely safe as all these objects are statically allocated) + m_haar.setTalonPort(0); + m_haar.setSensorPort(0); + + m_hedorah.setTalonPort(0); + m_hedorah.setSensorPort(0); + + m_t9602.setTalonPort(0); + m_t9602.setSensorPort(0); + + // Reset TDR315H sensors + for (int i = 0; i < 3; i++) { + m_tdr315h[i].setTalonPort(0); + m_tdr315h[i].setSensorPort(0); + } + + // Reset TEROS11 sensors + for (int i = 0; i < 2; i++) { + m_teros11[i].setTalonPort(0); + m_teros11[i].setSensorPort(0); + } + + m_li710.setTalonPort(0); + m_li710.setSensorPort(0); + + m_so421.setTalonPort(0); + m_so421.setSensorPort(0); + + m_sp421.setTalonPort(0); + m_sp421.setSensorPort(0); + + m_atmos22.setTalonPort(0); + m_atmos22.setSensorPort(0); + + m_barovue.setTalonPort(0); + m_barovue.setSensorPort(0); + } + bool ConfigurationManager::parseConfiguration(const std::string& configStr) { // Find the system configuration section size_t systemStart = configStr.find("\"system\":{"); diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 023dd76..e659231 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -43,6 +43,9 @@ // Method to register available sensors (called once at startup) void registerAvailableSensors(Sensor** availableSensors, uint8_t count); + // Method to reset all sensors (clears their allocation) + void resetSensors(); + // System configuration getters unsigned long getLogPeriod() const { return m_logPeriod; } int getBackhaulCount() const { return m_backhaulCount; } From 26d811fa83e4ae37e3051f7cc7cfd8a40bd0a7f4 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 14 May 2025 12:16:45 -0500 Subject: [PATCH 05/25] work in progress on config, may be scrapping --- src/FlightControl_Demo.cpp | 39 ++++++- src/configuration/ConfigurationManager.cpp | 109 ++++++++++++++------ src/configuration/ConfigurationManager.h | 1 - src/configuration/DefaultConfiguration.json | 61 ----------- 4 files changed, 116 insertions(+), 94 deletions(-) delete mode 100644 src/configuration/DefaultConfiguration.json diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index fe852a9..1f40919 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -34,6 +34,17 @@ int commandExe(String command); int systemRestart(String resetType); int configurePowerSave(int desiredPowerSaveMode); int updateConfiguration(String configJson); +void syncCoreConfiguredSensors(void); + +// Forward declare the types of core sensors (for configuration purposes) +const char* const CoreSensorTypes[] = { + "FileSystem", + "Aux", + "I2C", + "SDI12", + "Battery", + "Logger" +}; #define WAIT_GPS false #define USE_CELL //System attempts to connect to cell @@ -335,6 +346,9 @@ void setup() { // Register available sensors with configuration manager configManager.registerAvailableSensors(availableSensors, availableSensorCount); + + // Make sure core sensors are properly synchronized with configuration + syncCoreConfiguredSensors(); detectTalons(); @@ -354,10 +368,12 @@ void setup() { Serial.println("Loading default configuration..."); std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0},\"sensors\":["; - // Add all available sensors as enabled by default - for (uint8_t i = 0; i < availableSensorCount; i++) { + const int numCoreTypes = sizeof(CoreSensorTypes) / sizeof(CoreSensorTypes[0]); + + // Add all core sensors as enabled by default + for (uint8_t i = 0; i < numCoreTypes; i++) { if (i > 0) defaultConfig += ","; - defaultConfig += "{\"type\":\"" + std::string(availableSensors[i]->getType()) + "\",\"enabled\":true}"; + defaultConfig += "{\"type\":\"" + std::string(CoreSensorTypes[i]) + "\",\"enabled\":true}"; } defaultConfig += "]}}"; @@ -1545,4 +1561,21 @@ int configurePowerSave(int desiredPowerSaveMode) talonsToTest[t]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all talons } return 0; //DEBUG! +} + +// Utility function to sync core sensors between ConfigurationManager and the main sensors array +void syncCoreConfiguredSensors() { + // This function ensures the core sensors in the configuration manager + // have the same enabled state as the sensors in the main array + + const int numCoreTypes = sizeof(CoreSensorTypes) / sizeof(CoreSensorTypes[0]); + + // Register all core sensors with the configuration manager + if (configManager.getSensorCount() == 0) { + for (int i = 0; i < numCoreTypes; i++) { + configManager.enableCoreSensor(std::string(CoreSensorTypes[i])); + } + } + + Serial.println("Core sensors synchronized with configuration"); } \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index 6f9ec63..fd9089f 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -7,6 +7,7 @@ #include "ConfigurationManager.h" #include + #include // Include for numTalonPorts // External globals needed for configuration extern Talon* talons[]; @@ -21,6 +22,17 @@ , m_backhaulCount(4) , m_powerSaveMode(1) , m_loggingMode(0) + // Initialize sensor objects with proper construction parameters + , m_haar(0, 0, 0x00) + , m_hedorah(0, 0, 0x00) + , m_t9602(0, 0, 0x00) + , m_tdr315h{sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00} // Initialize 3 TDR315H sensors + , m_teros11{sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00} // Initialize 2 TEROS11 sensors + , m_li710(realTimeProvider, sdi12, 0, 0) + , m_so421(sdi12, 0, 0) + , m_sp421(sdi12, 0, 0) + , m_atmos22(sdi12, 0, 0) + , m_barovue(sdi12, 0, 0x00) { // Initialize all sensors as not in use and not enabled for (int i = 0; i < MAX_TOTAL_SENSORS; i++) { @@ -63,6 +75,31 @@ // Sensors configuration config += "\"sensors\":["; + // Helper function to determine sensor type based on pointer comparison + auto getSensorType = [this](Sensor* sensor) -> std::string { + // Check core sensors first based on the order in available sensors + if (m_availableSensorCount > 0 && sensor == m_availableSensors[0]) return "FileSystem"; + if (m_availableSensorCount > 1 && sensor == m_availableSensors[1]) return "Aux"; + if (m_availableSensorCount > 2 && sensor == m_availableSensors[2]) return "I2C"; + if (m_availableSensorCount > 3 && sensor == m_availableSensors[3]) return "SDI12"; + if (m_availableSensorCount > 4 && sensor == m_availableSensors[4]) return "Battery"; + if (m_availableSensorCount > 5 && sensor == m_availableSensors[5]) return "Logger"; + + // Check dynamic sensors + if (sensor == &m_haar) return "Haar"; + if (sensor == &m_hedorah) return "Hedorah"; + if (sensor == &m_t9602) return "T9602"; + for (int i = 0; i < 3; i++) if (sensor == &m_tdr315h[i]) return "TDR315H"; + for (int i = 0; i < 2; i++) if (sensor == &m_teros11[i]) return "TEROS11"; + if (sensor == &m_li710) return "LI710"; + if (sensor == &m_so421) return "SO421"; + if (sensor == &m_sp421) return "SP421"; + if (sensor == &m_atmos22) return "ATMOS22"; + if (sensor == &m_barovue) return "BaroVue10"; + + return "Unknown"; // Fallback + }; + bool firstSensor = true; // Add all enabled sensors to the configuration for (uint8_t i = 0; i < m_sensorCount; i++) { @@ -70,7 +107,7 @@ firstSensor = false; config += "{"; - config += "\"type\":\"" + std::string(m_sensorPool[i]->getType()) + "\","; + config += "\"type\":\"" + getSensorType(m_sensorPool[i]) + "\","; config += "\"enabled\":true,"; // Only include port and talonPort if they're defined (non-zero) @@ -82,16 +119,12 @@ config += "\"talonPort\":" + std::to_string(m_sensorPool[i]->getTalonPort()) + ","; } - // Add version if available - if (m_sensorPool[i]->getVersion() != 0) { - char versionHex[10]; - snprintf(versionHex, sizeof(versionHex), "0x%X", m_sensorPool[i]->getVersion()); - config += "\"version\":\"" + std::string(versionHex) + "\""; - } else { - // Remove trailing comma if no version - if (config.back() == ',') { - config.pop_back(); - } + // We can't determine version anymore without getVersion, + // so we'll remove this part + + // Remove trailing comma if present + if (config.back() == ',') { + config.pop_back(); } config += "}"; @@ -112,7 +145,20 @@ firstSensor = false; config += "{"; - config += "\"type\":\"" + std::string(m_availableSensors[i]->getType()) + "\","; + + // Get type based on index in availableSensors + std::string type; + switch (i) { + case 0: type = "FileSystem"; break; + case 1: type = "Aux"; break; + case 2: type = "I2C"; break; + case 3: type = "SDI12"; break; + case 4: type = "Battery"; break; + case 5: type = "Logger"; break; + default: type = "Unknown"; break; + } + + config += "\"type\":\"" + type + "\","; config += "\"enabled\":false"; config += "}"; } @@ -279,13 +325,18 @@ } Sensor* ConfigurationManager::findAvailableSensorByType(const std::string& type) { - // Search through available sensors for matching type - for (uint8_t i = 0; i < m_availableSensorCount; i++) { - if (strcmp(m_availableSensors[i]->getType(), type.c_str()) == 0) { - return m_availableSensors[i]; - } - } - return nullptr; + // Map sensor types to indices in the available sensors array + // This is a workaround since the Sensor class doesn't have a getType() method + + // Core sensor types mapping to indices in availableSensors + if (type == "FileSystem" && m_availableSensorCount > 0) return m_availableSensors[0]; + if (type == "Aux" && m_availableSensorCount > 1) return m_availableSensors[1]; + if (type == "I2C" && m_availableSensorCount > 2) return m_availableSensors[2]; + if (type == "SDI12" && m_availableSensorCount > 3) return m_availableSensors[3]; + if (type == "Battery" && m_availableSensorCount > 4) return m_availableSensors[4]; + if (type == "Logger" && m_availableSensorCount > 5) return m_availableSensors[5]; + + return nullptr; // Type not found } bool ConfigurationManager::enableCoreSensor(const std::string& type) { @@ -326,7 +377,7 @@ if (!m_dynamicSensorInUse[0]) { m_haar.setTalonPort(talonPort); m_haar.setSensorPort(port); - m_haar.setVersion(version); + // Version is set during sensor constructor, not supported via setVersion m_sensorPool[m_sensorCount++] = &m_haar; m_dynamicSensorInUse[0] = true; return true; @@ -336,7 +387,7 @@ if (!m_dynamicSensorInUse[1]) { m_hedorah.setTalonPort(talonPort); m_hedorah.setSensorPort(port); - m_hedorah.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_hedorah; m_dynamicSensorInUse[1] = true; return true; @@ -346,7 +397,7 @@ if (!m_dynamicSensorInUse[2]) { m_t9602.setTalonPort(talonPort); m_t9602.setSensorPort(port); - m_t9602.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_t9602; m_dynamicSensorInUse[2] = true; return true; @@ -358,7 +409,7 @@ if (!m_dynamicSensorInUse[3 + i]) { m_tdr315h[i].setTalonPort(talonPort); m_tdr315h[i].setSensorPort(port); - m_tdr315h[i].setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_tdr315h[i]; m_dynamicSensorInUse[3 + i] = true; return true; @@ -371,7 +422,7 @@ if (!m_dynamicSensorInUse[6 + i]) { m_teros11[i].setTalonPort(talonPort); m_teros11[i].setSensorPort(port); - m_teros11[i].setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_teros11[i]; m_dynamicSensorInUse[6 + i] = true; return true; @@ -382,7 +433,7 @@ if (!m_dynamicSensorInUse[8]) { m_li710.setTalonPort(talonPort); m_li710.setSensorPort(port); - m_li710.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_li710; m_dynamicSensorInUse[8] = true; return true; @@ -392,7 +443,7 @@ if (!m_dynamicSensorInUse[9]) { m_so421.setTalonPort(talonPort); m_so421.setSensorPort(port); - m_so421.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_so421; m_dynamicSensorInUse[9] = true; return true; @@ -402,7 +453,7 @@ if (!m_dynamicSensorInUse[10]) { m_sp421.setTalonPort(talonPort); m_sp421.setSensorPort(port); - m_sp421.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_sp421; m_dynamicSensorInUse[10] = true; return true; @@ -412,7 +463,7 @@ if (!m_dynamicSensorInUse[11]) { m_atmos22.setTalonPort(talonPort); m_atmos22.setSensorPort(port); - m_atmos22.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_atmos22; m_dynamicSensorInUse[11] = true; return true; @@ -422,7 +473,7 @@ if (!m_dynamicSensorInUse[12]) { m_barovue.setTalonPort(talonPort); m_barovue.setSensorPort(port); - m_barovue.setVersion(version); + // Version is set during sensor constructor m_sensorPool[m_sensorCount++] = &m_barovue; m_dynamicSensorInUse[12] = true; return true; diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index e659231..56848d6 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -91,7 +91,6 @@ void clearSensorPool(); bool parseConfiguration(const std::string& config); bool configureSensor(const std::string& type, int port, int talonPort, int version); - bool enableCoreSensor(const std::string& type); Sensor* findAvailableSensorByType(const std::string& type); // JSON parsing helpers diff --git a/src/configuration/DefaultConfiguration.json b/src/configuration/DefaultConfiguration.json deleted file mode 100644 index a095db0..0000000 --- a/src/configuration/DefaultConfiguration.json +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file DefaultConfiguration.json - * @brief Default configuration for the Kestrel system - * - * © 2025 Regents of the University of Minnesota. All rights reserved. - */ - { - "config": { - "system": { - "logPeriod": 300, - "backhaulCount": 4, - "powerSaveMode": 1, - "loggingMode": 0 - }, - "sensors": [ - { - "type": "FileSystem", - "enabled": true - }, - { - "type": "Aux", - "enabled": true - }, - { - "type": "I2C", - "enabled": true - }, - { - "type": "SDI12", - "enabled": true - }, - { - "type": "Battery", - "enabled": true - }, - { - "type": "Logger", - "enabled": true - }, - { - "type": "Haar", - "enabled": true, - "port": 1, - "talonPort": 1, - "version": "0x20" - }, - { - "type": "LI710", - "enabled": true, - "port": 1, - "talonPort": 3 - }, - { - "type": "TDR315H", - "enabled": true, - "port": 2, - "talonPort": 3 - } - ] - } -} \ No newline at end of file From 1c94fa07d1d86d7e32a50b1256d91f75d5c3b3bf Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 14 May 2025 12:17:21 -0500 Subject: [PATCH 06/25] added files --- docs/RemoteConfiguration.md | 120 ++++++++++++++++++ test/sample_configurations/full_config.json | 86 +++++++++++++ .../sample_configurations/minimal_config.json | 43 +++++++ .../standard_config.json | 55 ++++++++ 4 files changed, 304 insertions(+) create mode 100644 docs/RemoteConfiguration.md create mode 100644 test/sample_configurations/full_config.json create mode 100644 test/sample_configurations/minimal_config.json create mode 100644 test/sample_configurations/standard_config.json diff --git a/docs/RemoteConfiguration.md b/docs/RemoteConfiguration.md new file mode 100644 index 0000000..8f06b01 --- /dev/null +++ b/docs/RemoteConfiguration.md @@ -0,0 +1,120 @@ +# Remote Configuration Guide + +The FlightControl-Demo firmware supports remote configuration updates through Particle functions. This allows you to change the system configuration and enabled sensors without requiring a firmware update. + +## Configuration Overview + +The firmware can be configured using a JSON configuration format. The configuration includes: + +1. **System settings**: Controls logging periods, backhaul frequency, power save modes, etc. +2. **Sensors list**: Defines which sensors are enabled and their connection details + +## JSON Format + +The configuration uses the following format: + +```json +{ + "config": { + "system": { + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 0 + }, + "sensors": [ + { + "type": "FileSystem", + "enabled": true + }, + { + "type": "Haar", + "enabled": true, + "port": 1, + "talonPort": 1, + "version": "0x20" + } + ] + } +} +``` + +### System Parameters + +- `logPeriod`: Time in seconds between logging events (default: 300) +- `backhaulCount`: Number of log events before a backhaul is performed (default: 4) +- `powerSaveMode`: Power saving mode (0=Performance, 1=Low Power, 2=Ultra Low Power) +- `loggingMode`: Logging mode (0=Standard, 1=Performance, 2=Balanced, 3=No Local) + +### Sensor Parameters + +- `type`: The sensor type identifier (required) +- `enabled`: Whether the sensor is enabled (true/false) +- `port`: The sensor port on the Talon (only for non-core sensors) +- `talonPort`: The Talon port on the Kestrel (only for non-core sensors) +- `version`: The hardware version, in decimal or hex (e.g., "0x20") + +## Core Sensors + +The following sensors are considered "core" sensors and are always available: + +- FileSystem +- Aux +- I2C +- SDI12 +- Battery +- Logger + +## Dynamic Sensors + +The following sensors can be configured dynamically: + +- Haar +- Hedorah +- T9602 +- TDR315H (up to 3 instances) +- TEROS11 (up to 2 instances) +- LI710 +- SO421 +- SP421 +- ATMOS22 +- BaroVue10 + +## Updating Configuration Remotely + +To update the configuration remotely, call the Particle function `updateConfig` with a valid JSON configuration string. The function can be called using the Particle CLI or the Particle Cloud API. + +### Using Particle CLI + +```bash +particle call updateConfig +``` + +### Return Codes + +- `1`: Success +- `-1`: Failed to update configuration +- `-2`: Invalid JSON format + +## Configuration Files + +Sample configuration files can be found in the `test/sample_configurations/` directory: + +- `standard_config.json`: The default configuration +- `minimal_config.json`: A minimal configuration with fewer sensors +- `full_config.json`: A configuration with many sensors + +## Reset Process + +When a new configuration is applied: + +1. The system settings are updated +2. All non-core sensors are reset +3. Talons and sensors are re-detected +4. Sensors are re-initialized + +This ensures that the device applies the new configuration completely. + +## Persistent Storage + +The current configuration is stored on the SD card as `config.json`. If the device reboots, it will load this configuration during startup. If no configuration file exists or there's an error loading it, the device will use the default configuration. \ No newline at end of file diff --git a/test/sample_configurations/full_config.json b/test/sample_configurations/full_config.json new file mode 100644 index 0000000..0be9f5d --- /dev/null +++ b/test/sample_configurations/full_config.json @@ -0,0 +1,86 @@ +{ + "config": { + "system": { + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 2 + }, + "sensors": [ + { + "type": "FileSystem", + "enabled": true + }, + { + "type": "Aux", + "enabled": true + }, + { + "type": "I2C", + "enabled": true + }, + { + "type": "SDI12", + "enabled": true + }, + { + "type": "Battery", + "enabled": true + }, + { + "type": "Logger", + "enabled": true + }, + { + "type": "Haar", + "enabled": true, + "port": 1, + "talonPort": 1, + "version": "0x20" + }, + { + "type": "Hedorah", + "enabled": true, + "port": 2, + "talonPort": 1, + "version": "0x10" + }, + { + "type": "LI710", + "enabled": true, + "port": 1, + "talonPort": 3 + }, + { + "type": "TDR315H", + "enabled": true, + "port": 2, + "talonPort": 3 + }, + { + "type": "TDR315H", + "enabled": true, + "port": 3, + "talonPort": 3 + }, + { + "type": "SO421", + "enabled": true, + "port": 1, + "talonPort": 4 + }, + { + "type": "SP421", + "enabled": true, + "port": 2, + "talonPort": 4 + }, + { + "type": "ATMOS22", + "enabled": true, + "port": 3, + "talonPort": 4 + } + ] + } +} \ No newline at end of file diff --git a/test/sample_configurations/minimal_config.json b/test/sample_configurations/minimal_config.json new file mode 100644 index 0000000..4d212ae --- /dev/null +++ b/test/sample_configurations/minimal_config.json @@ -0,0 +1,43 @@ +{ + "config": { + "system": { + "logPeriod": 600, + "backhaulCount": 2, + "powerSaveMode": 2, + "loggingMode": 1 + }, + "sensors": [ + { + "type": "FileSystem", + "enabled": true + }, + { + "type": "Aux", + "enabled": true + }, + { + "type": "I2C", + "enabled": true + }, + { + "type": "SDI12", + "enabled": true + }, + { + "type": "Battery", + "enabled": true + }, + { + "type": "Logger", + "enabled": true + }, + { + "type": "Haar", + "enabled": true, + "port": 1, + "talonPort": 1, + "version": "0x20" + } + ] + } +} \ No newline at end of file diff --git a/test/sample_configurations/standard_config.json b/test/sample_configurations/standard_config.json new file mode 100644 index 0000000..730b33a --- /dev/null +++ b/test/sample_configurations/standard_config.json @@ -0,0 +1,55 @@ +{ + "config": { + "system": { + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 0 + }, + "sensors": [ + { + "type": "FileSystem", + "enabled": true + }, + { + "type": "Aux", + "enabled": true + }, + { + "type": "I2C", + "enabled": true + }, + { + "type": "SDI12", + "enabled": true + }, + { + "type": "Battery", + "enabled": true + }, + { + "type": "Logger", + "enabled": true + }, + { + "type": "Haar", + "enabled": true, + "port": 1, + "talonPort": 1, + "version": "0x20" + }, + { + "type": "LI710", + "enabled": true, + "port": 1, + "talonPort": 3 + }, + { + "type": "TDR315H", + "enabled": true, + "port": 2, + "talonPort": 3 + } + ] + } +} \ No newline at end of file From ea9bdb0ec7968a849af7efc9fb9424d03abcd839 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 14 May 2025 15:34:12 -0500 Subject: [PATCH 07/25] writing config.json to sdcard --- lib/Driver_-_Kestrel-FileHandler | 2 +- src/FlightControl_Demo.cpp | 152 +++---- src/configuration/ConfigurationManager.cpp | 443 +-------------------- src/configuration/ConfigurationManager.h | 59 --- 4 files changed, 54 insertions(+), 602 deletions(-) diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index 66efa09..94f904b 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit 66efa098a0c0f5e70622233d8272d214120afc11 +Subproject commit 94f904b1e7c5c5c7b62db35bde5f327269500808 diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 1f40919..15ddd1c 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -34,7 +34,7 @@ int commandExe(String command); int systemRestart(String resetType); int configurePowerSave(int desiredPowerSaveMode); int updateConfiguration(String configJson); -void syncCoreConfiguredSensors(void); +int getConfiguration(String dummy); // Forward declare the types of core sensors (for configuration purposes) const char* const CoreSensorTypes[] = { @@ -165,14 +165,23 @@ namespace LogModes { constexpr uint8_t NO_LOCAL = 3; //Same as standard log, but no attempt to log to SD card }; -/////////////////////////// BEGIN USER CONFIG //////////////////////// -//PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product -PRODUCT_VERSION(34) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number +PRODUCT_VERSION(34) -int backhaulCount = 4; //Number of log events before backhaul is performed -unsigned long logPeriod = 300; //Number of seconds to wait between logging events -int desiredPowerSaveMode = PowerSaveModes::LOW_POWER; //Specify the power save mode you wish to use: PERFORMANCE, BALANCED, LOW_POWER, ULTRA_LOW_POWER -int loggingMode = LogModes::STANDARD; //Specify the type of logging mode you wish to use: STANDARD, PERFORMANCE, BALANCED, NO_LOCAL +//global variables affected by configuration manager +int backhaulCount; +unsigned long logPeriod; +int desiredPowerSaveMode; +int loggingMode; + +ConfigurationManager configManager; +// /////////////////////////// BEGIN USER CONFIG //////////////////////// +// //PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product +// PRODUCT_VERSION(34) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number + +// int backhaulCount = 4; //Number of log events before backhaul is performed +// unsigned long logPeriod = 300; //Number of seconds to wait between logging events +// int desiredPowerSaveMode = PowerSaveModes::LOW_POWER; //Specify the power save mode you wish to use: PERFORMANCE, BALANCED, LOW_POWER, ULTRA_LOW_POWER +// int loggingMode = LogModes::STANDARD; //Specify the type of logging mode you wish to use: STANDARD, PERFORMANCE, BALANCED, NO_LOCAL Haar haar(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 // Haar haar1(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 @@ -188,7 +197,7 @@ Hedorah gas(0, 0, 0x10); //Instantiate CO2 sensor with default ports and v1.0 ha LI710 et(realTimeProvider, realSdi12, 0, 0); //Instantiate ET sensor with default ports and unknown version, pass over SDI12 Talon interface BaroVue10 campPressure(sdi12, 0, 0x00); // Instantiate Barovue10 with default ports and v0.0 hardware -const uint8_t numSensors = 8; //Number must match the number of objects defined in `sensors` array +const uint8_t numSensors = 9; //Number must match the number of objects defined in `sensors` array Sensor* const sensors[numSensors] = { &fileSys, @@ -197,31 +206,17 @@ Sensor* const sensors[numSensors] = { &sdi12, &battery, &logger, //Add sensors after this line - &et, - &haar - // &soil1, + //&et, + //&haar, + &soil1, // &apogeeSolar, - // &soil2, - // &soil3, + &soil2, + &soil3, // &gas, // &apogeeO2, }; - -// Define available sensors array (all sensors that could be enabled/disabled) -Sensor* availableSensors[] = { - &fileSys, - &aux, - &i2c, - &sdi12, - &battery, - &logger -}; -const uint8_t availableSensorCount = sizeof(availableSensors) / sizeof(availableSensors[0]); - -// Create configuration manager -ConfigurationManager configManager; -/////////////////////////// END USER CONFIG ///////////////////////////////// +// /////////////////////////// END USER CONFIG ///////////////////////////////// namespace PinsIO { //For Kestrel v1.1 constexpr uint16_t VUSB = 5; @@ -273,7 +268,6 @@ String metadata = ""; String data = ""; void setup() { - configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) System.enableFeature(FEATURE_RESET_INFO); //Allows for Particle to see reason for last reset using System.resetReason(); if(System.resetReason() != RESET_REASON_POWER_DOWN) { //DEBUG! Set safe mode @@ -284,14 +278,14 @@ void setup() { // talons[aux1.getTalonPort()] = &aux1; time_t startTime = millis(); Particle.function("updateConfig", updateConfiguration); + Particle.function("getConfig", getConfiguration); Particle.function("nodeID", setNodeID); Particle.function("findSensors", detectSensors); Particle.function("findTalons", detectTalons); Particle.function("systemRestart", systemRestart); Particle.function("takeSample", takeSample); Particle.function("commandExe", commandExe); - Serial.begin(1000000); - waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds + Serial.print("RESET CAUSE: "); //DEBUG! Serial.println(System.resetReason()); //DEBUG! bool hasCriticalError = false; @@ -341,48 +335,28 @@ void setup() { // logger.enableData(i, false); //Turn off all data by default // } - // Initialize configuration system - Serial.println("Initializing configuration system..."); - - // Register available sensors with configuration manager - configManager.registerAvailableSensors(availableSensors, availableSensorCount); - - // Make sure core sensors are properly synchronized with configuration - syncCoreConfiguredSensors(); + Serial.begin(1000000); + waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds - - detectTalons(); - // Load configuration from SD card if possible bool configLoaded = false; - if (!hasCriticalError) { - std::string configStr = fileSys.readFromSD("config.json").c_str(); - if (!configStr.empty()) { - Serial.println("Loading configuration from SD card..."); - configLoaded = configManager.setConfiguration(configStr); - } - } + + std::string configStr = fileSys.readFromSD("config.json").c_str(); + if (!configStr.empty()) { + Serial.println("Loading configuration from SD card..."); + configLoaded = configManager.setConfiguration(configStr); + Serial.println(configStr.c_str()); + } // If no config loaded from SD, use default if (!configLoaded) { Serial.println("Loading default configuration..."); std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0},\"sensors\":["; - - const int numCoreTypes = sizeof(CoreSensorTypes) / sizeof(CoreSensorTypes[0]); - - // Add all core sensors as enabled by default - for (uint8_t i = 0; i < numCoreTypes; i++) { - if (i > 0) defaultConfig += ","; - defaultConfig += "{\"type\":\"" + std::string(CoreSensorTypes[i]) + "\",\"enabled\":true}"; - } - - defaultConfig += "]}}"; configManager.setConfiguration(defaultConfig); // Save default config to SD card - if (!hasCriticalError) { - fileSys.writeToSD(defaultConfig.c_str(), "config.json"); - } + Serial.println("Saving default configuration to SD card..."); + fileSys.writeToSD(defaultConfig.c_str(), "config.json"); } // Set global variables from configuration @@ -392,8 +366,9 @@ void setup() { loggingMode = configManager.getLoggingMode(); // Apply power save mode - configurePowerSave(desiredPowerSaveMode); + configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) + detectTalons(); detectSensors(); // I2C_OnBoardEn(false); @@ -1430,30 +1405,9 @@ int updateConfiguration(String configJson) { bool saveSuccess = fileSys.writeToSD(configStr.c_str(), "config.json"); if (!saveSuccess) { Serial.println("Warning: Failed to save configuration to SD card"); + //add error for failed configuration save } - // Reset sensors for reconfiguration - Serial.println("Reconfiguring sensors based on new configuration..."); - - // First power down all sensors to ensure clean state - sleepSensors(); - - // Reset all sensor ports to ensure they'll be re-detected - for (int i = 0; i < numSensors; i++) { - if (sensors[i]->sensorInterface != BusType::CORE) { - sensors[i]->setTalonPort(0); - sensors[i]->setSensorPort(0); - } - } - - // Re-detect talons and sensors - detectTalons(); - detectSensors(); - - // Wake sensors and re-initialize them - wakeSensors(); - initSensors(); - Serial.println("Configuration update complete"); return 1; // Success } @@ -1462,6 +1416,15 @@ int updateConfiguration(String configJson) { return -1; // Failure } +int getConfiguration(String dummy) { + std::string configStr = configManager.getConfiguration(); + if (configStr.length() > 0) { + return 1; + } else { + return -1; + } +} + int takeSample(String dummy) { logger.wake(); //Wake logger in case it was sleeping @@ -1561,21 +1524,4 @@ int configurePowerSave(int desiredPowerSaveMode) talonsToTest[t]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all talons } return 0; //DEBUG! -} - -// Utility function to sync core sensors between ConfigurationManager and the main sensors array -void syncCoreConfiguredSensors() { - // This function ensures the core sensors in the configuration manager - // have the same enabled state as the sensors in the main array - - const int numCoreTypes = sizeof(CoreSensorTypes) / sizeof(CoreSensorTypes[0]); - - // Register all core sensors with the configuration manager - if (configManager.getSensorCount() == 0) { - for (int i = 0; i < numCoreTypes; i++) { - configManager.enableCoreSensor(std::string(CoreSensorTypes[i])); - } - } - - Serial.println("Core sensors synchronized with configuration"); } \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index fd9089f..cfa435a 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -6,57 +6,11 @@ */ #include "ConfigurationManager.h" - #include - #include // Include for numTalonPorts - - // External globals needed for configuration - extern Talon* talons[]; - extern ITimeProvider& realTimeProvider; - extern SDI12Talon& sdi12; - - ConfigurationManager::ConfigurationManager() - : m_sensorCount(0) - , m_availableSensors(nullptr) - , m_availableSensorCount(0) - , m_logPeriod(300) - , m_backhaulCount(4) - , m_powerSaveMode(1) - , m_loggingMode(0) - // Initialize sensor objects with proper construction parameters - , m_haar(0, 0, 0x00) - , m_hedorah(0, 0, 0x00) - , m_t9602(0, 0, 0x00) - , m_tdr315h{sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00} // Initialize 3 TDR315H sensors - , m_teros11{sdi12, 0, 0, 0x00, sdi12, 0, 0, 0x00} // Initialize 2 TEROS11 sensors - , m_li710(realTimeProvider, sdi12, 0, 0) - , m_so421(sdi12, 0, 0) - , m_sp421(sdi12, 0, 0) - , m_atmos22(sdi12, 0, 0) - , m_barovue(sdi12, 0, 0x00) - { - // Initialize all sensors as not in use and not enabled - for (int i = 0; i < MAX_TOTAL_SENSORS; i++) { - m_sensorEnabled[i] = false; - } - - // Initialize dynamic sensors as not in use - for (int i = 0; i < MAX_DYNAMIC_SENSORS; i++) { - m_dynamicSensorInUse[i] = false; - } - } - - void ConfigurationManager::registerAvailableSensors(Sensor** availableSensors, uint8_t count) { - m_availableSensors = availableSensors; - m_availableSensorCount = count; - - // Clear the sensor pool - clearSensorPool(); - } + + ConfigurationManager::ConfigurationManager() {}; + bool ConfigurationManager::setConfiguration(std::string config) { - // Fully reset all sensors - resetSensors(); - // Parse and apply the configuration return parseConfiguration(config); } @@ -70,166 +24,12 @@ config += "\"backhaulCount\":" + std::to_string(m_backhaulCount) + ","; config += "\"powerSaveMode\":" + std::to_string(m_powerSaveMode) + ","; config += "\"loggingMode\":" + std::to_string(m_loggingMode); - config += "},"; - - // Sensors configuration - config += "\"sensors\":["; - - // Helper function to determine sensor type based on pointer comparison - auto getSensorType = [this](Sensor* sensor) -> std::string { - // Check core sensors first based on the order in available sensors - if (m_availableSensorCount > 0 && sensor == m_availableSensors[0]) return "FileSystem"; - if (m_availableSensorCount > 1 && sensor == m_availableSensors[1]) return "Aux"; - if (m_availableSensorCount > 2 && sensor == m_availableSensors[2]) return "I2C"; - if (m_availableSensorCount > 3 && sensor == m_availableSensors[3]) return "SDI12"; - if (m_availableSensorCount > 4 && sensor == m_availableSensors[4]) return "Battery"; - if (m_availableSensorCount > 5 && sensor == m_availableSensors[5]) return "Logger"; - - // Check dynamic sensors - if (sensor == &m_haar) return "Haar"; - if (sensor == &m_hedorah) return "Hedorah"; - if (sensor == &m_t9602) return "T9602"; - for (int i = 0; i < 3; i++) if (sensor == &m_tdr315h[i]) return "TDR315H"; - for (int i = 0; i < 2; i++) if (sensor == &m_teros11[i]) return "TEROS11"; - if (sensor == &m_li710) return "LI710"; - if (sensor == &m_so421) return "SO421"; - if (sensor == &m_sp421) return "SP421"; - if (sensor == &m_atmos22) return "ATMOS22"; - if (sensor == &m_barovue) return "BaroVue10"; - - return "Unknown"; // Fallback - }; - - bool firstSensor = true; - // Add all enabled sensors to the configuration - for (uint8_t i = 0; i < m_sensorCount; i++) { - if (!firstSensor) config += ","; - firstSensor = false; - - config += "{"; - config += "\"type\":\"" + getSensorType(m_sensorPool[i]) + "\","; - config += "\"enabled\":true,"; - - // Only include port and talonPort if they're defined (non-zero) - if (m_sensorPool[i]->getSensorPort() > 0) { - config += "\"port\":" + std::to_string(m_sensorPool[i]->getSensorPort()) + ","; - } - - if (m_sensorPool[i]->getTalonPort() > 0) { - config += "\"talonPort\":" + std::to_string(m_sensorPool[i]->getTalonPort()) + ","; - } - - // We can't determine version anymore without getVersion, - // so we'll remove this part - - // Remove trailing comma if present - if (config.back() == ',') { - config.pop_back(); - } - - config += "}"; - } - - // Also add disabled core sensors with enabled:false - for (uint8_t i = 0; i < m_availableSensorCount; i++) { - bool isEnabled = false; - for (uint8_t j = 0; j < m_sensorCount; j++) { - if (m_sensorPool[j] == m_availableSensors[i]) { - isEnabled = true; - break; - } - } - - if (!isEnabled) { - if (!firstSensor) config += ","; - firstSensor = false; - - config += "{"; - - // Get type based on index in availableSensors - std::string type; - switch (i) { - case 0: type = "FileSystem"; break; - case 1: type = "Aux"; break; - case 2: type = "I2C"; break; - case 3: type = "SDI12"; break; - case 4: type = "Battery"; break; - case 5: type = "Logger"; break; - default: type = "Unknown"; break; - } - - config += "\"type\":\"" + type + "\","; - config += "\"enabled\":false"; - config += "}"; - } - } - - config += "]"; config += "}}"; return config; } - Sensor** ConfigurationManager::getSensorArray() { - return m_sensorPool; - } - - uint8_t ConfigurationManager::getSensorCount() const { - return m_sensorCount; - } - - void ConfigurationManager::clearSensorPool() { - // Reset to empty pool - m_sensorCount = 0; - - // Mark all pre-allocated sensors as not in use - for (int i = 0; i < MAX_DYNAMIC_SENSORS; i++) { - m_dynamicSensorInUse[i] = false; - } - } - - void ConfigurationManager::resetSensors() { - // Clear the sensor pool - clearSensorPool(); - - // Reset all pre-allocated sensors (completely safe as all these objects are statically allocated) - m_haar.setTalonPort(0); - m_haar.setSensorPort(0); - - m_hedorah.setTalonPort(0); - m_hedorah.setSensorPort(0); - - m_t9602.setTalonPort(0); - m_t9602.setSensorPort(0); - - // Reset TDR315H sensors - for (int i = 0; i < 3; i++) { - m_tdr315h[i].setTalonPort(0); - m_tdr315h[i].setSensorPort(0); - } - - // Reset TEROS11 sensors - for (int i = 0; i < 2; i++) { - m_teros11[i].setTalonPort(0); - m_teros11[i].setSensorPort(0); - } - - m_li710.setTalonPort(0); - m_li710.setSensorPort(0); - - m_so421.setTalonPort(0); - m_so421.setSensorPort(0); - - m_sp421.setTalonPort(0); - m_sp421.setSensorPort(0); - - m_atmos22.setTalonPort(0); - m_atmos22.setSensorPort(0); - - m_barovue.setTalonPort(0); - m_barovue.setSensorPort(0); - } - + bool ConfigurationManager::parseConfiguration(const std::string& configStr) { // Find the system configuration section size_t systemStart = configStr.find("\"system\":{"); @@ -246,244 +46,9 @@ } } - // Find the sensors array - size_t sensorsStart = configStr.find("\"sensors\":["); - if (sensorsStart == std::string::npos) { - return false; - } - - size_t sensorsEnd = findMatchingBracket(configStr, sensorsStart + 10); - if (sensorsEnd == std::string::npos) { - return false; - } - - // Extract sensors array content - std::string sensorsJson = configStr.substr(sensorsStart + 10, sensorsEnd - (sensorsStart + 10)); - - // Count sensors to check if we exceed the limit - int sensorCount = 0; - size_t pos = 0; - while ((pos = sensorsJson.find('{', pos)) != std::string::npos) { - sensorCount++; - pos++; - } - - // Check if we have too many sensors - if (sensorCount > MAX_TOTAL_SENSORS) { - Serial.printlnf("Error: Too many sensors in configuration (%d > %d)", - sensorCount, MAX_TOTAL_SENSORS); - return false; - } - - // Parse each sensor object - pos = 0; - while (pos < sensorsJson.length()) { - size_t objStart = sensorsJson.find('{', pos); - if (objStart == std::string::npos) break; - - size_t objEnd = findMatchingBracket(sensorsJson, objStart); - if (objEnd == std::string::npos) break; - - std::string sensorObj = sensorsJson.substr(objStart + 1, objEnd - objStart - 1); - - // Extract sensor properties - std::string type = extractJsonField(sensorObj, "type"); - if (type.empty()) { - Serial.println("Error: Sensor type missing"); - pos = objEnd + 1; - continue; - } - - // Check if this sensor is enabled - bool enabled = extractJsonBoolField(sensorObj, "enabled", true); - - if (enabled) { - // Check if this is a core sensor first - if (!enableCoreSensor(type)) { - // If not a core sensor, try to configure a dynamic sensor - int port = extractJsonIntField(sensorObj, "port", 0); - int talonPort = extractJsonIntField(sensorObj, "talonPort", 0); - std::string versionStr = extractJsonField(sensorObj, "version"); - - int version = 0; - if (versionStr.substr(0, 2) == "0x") { - version = strtol(versionStr.c_str() + 2, NULL, 16); - } else if (!versionStr.empty()) { - version = atoi(versionStr.c_str()); - } - - if (!configureSensor(type, port, talonPort, version)) { - Serial.printlnf("Warning: Failed to configure sensor %s", type.c_str()); - } - } - } - - pos = objEnd + 1; - } - return true; } - Sensor* ConfigurationManager::findAvailableSensorByType(const std::string& type) { - // Map sensor types to indices in the available sensors array - // This is a workaround since the Sensor class doesn't have a getType() method - - // Core sensor types mapping to indices in availableSensors - if (type == "FileSystem" && m_availableSensorCount > 0) return m_availableSensors[0]; - if (type == "Aux" && m_availableSensorCount > 1) return m_availableSensors[1]; - if (type == "I2C" && m_availableSensorCount > 2) return m_availableSensors[2]; - if (type == "SDI12" && m_availableSensorCount > 3) return m_availableSensors[3]; - if (type == "Battery" && m_availableSensorCount > 4) return m_availableSensors[4]; - if (type == "Logger" && m_availableSensorCount > 5) return m_availableSensors[5]; - - return nullptr; // Type not found - } - - bool ConfigurationManager::enableCoreSensor(const std::string& type) { - // Find a sensor with matching type in available sensors - Sensor* sensor = findAvailableSensorByType(type); - if (sensor) { - // Check if it's already in the pool - for (uint8_t i = 0; i < m_sensorCount; i++) { - if (m_sensorPool[i] == sensor) { - // Already enabled - return true; - } - } - - // Add to pool if not already there - if (m_sensorCount < MAX_TOTAL_SENSORS) { - m_sensorPool[m_sensorCount++] = sensor; - return true; - } - } - return false; - } - - bool ConfigurationManager::configureSensor(const std::string& type, int port, int talonPort, int version) { - // If we're already at capacity, fail - if (m_sensorCount >= MAX_TOTAL_SENSORS) { - return false; - } - - // Find appropriate Talon - Talon* talon = nullptr; - if (talonPort > 0 && talonPort <= Kestrel::numTalonPorts) { - talon = talons[talonPort - 1]; - } - - // Configure the appropriate pre-allocated sensor - if (type == "Haar") { - if (!m_dynamicSensorInUse[0]) { - m_haar.setTalonPort(talonPort); - m_haar.setSensorPort(port); - // Version is set during sensor constructor, not supported via setVersion - m_sensorPool[m_sensorCount++] = &m_haar; - m_dynamicSensorInUse[0] = true; - return true; - } - } - else if (type == "Hedorah") { - if (!m_dynamicSensorInUse[1]) { - m_hedorah.setTalonPort(talonPort); - m_hedorah.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_hedorah; - m_dynamicSensorInUse[1] = true; - return true; - } - } - else if (type == "T9602") { - if (!m_dynamicSensorInUse[2]) { - m_t9602.setTalonPort(talonPort); - m_t9602.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_t9602; - m_dynamicSensorInUse[2] = true; - return true; - } - } - else if (type == "TDR315H") { - // Find an available TDR315H instance - for (int i = 0; i < 3; i++) { - if (!m_dynamicSensorInUse[3 + i]) { - m_tdr315h[i].setTalonPort(talonPort); - m_tdr315h[i].setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_tdr315h[i]; - m_dynamicSensorInUse[3 + i] = true; - return true; - } - } - } - else if (type == "TEROS11") { - // Find an available TEROS11 instance - for (int i = 0; i < 2; i++) { - if (!m_dynamicSensorInUse[6 + i]) { - m_teros11[i].setTalonPort(talonPort); - m_teros11[i].setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_teros11[i]; - m_dynamicSensorInUse[6 + i] = true; - return true; - } - } - } - else if (type == "LI710") { - if (!m_dynamicSensorInUse[8]) { - m_li710.setTalonPort(talonPort); - m_li710.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_li710; - m_dynamicSensorInUse[8] = true; - return true; - } - } - else if (type == "SO421") { - if (!m_dynamicSensorInUse[9]) { - m_so421.setTalonPort(talonPort); - m_so421.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_so421; - m_dynamicSensorInUse[9] = true; - return true; - } - } - else if (type == "SP421") { - if (!m_dynamicSensorInUse[10]) { - m_sp421.setTalonPort(talonPort); - m_sp421.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_sp421; - m_dynamicSensorInUse[10] = true; - return true; - } - } - else if (type == "ATMOS22") { - if (!m_dynamicSensorInUse[11]) { - m_atmos22.setTalonPort(talonPort); - m_atmos22.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_atmos22; - m_dynamicSensorInUse[11] = true; - return true; - } - } - else if (type == "BaroVue10") { - if (!m_dynamicSensorInUse[12]) { - m_barovue.setTalonPort(talonPort); - m_barovue.setSensorPort(port); - // Version is set during sensor constructor - m_sensorPool[m_sensorCount++] = &m_barovue; - m_dynamicSensorInUse[12] = true; - return true; - } - } - // Add more sensor types as needed - - return false; // Failed to configure sensor - } - std::string ConfigurationManager::extractJsonField(const std::string& json, const std::string& fieldName) { std::string searchStr = "\"" + fieldName + "\":"; size_t fieldStart = json.find(searchStr); diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 56848d6..c939449 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -9,26 +9,8 @@ #define CONFIGURATION_MANAGER_H #include "IConfiguration.h" - #include - #include - // These are for pre-allocation of sensors - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - class ConfigurationManager : public IConfiguration { public: - // Constants for fixed allocation - static const uint8_t MAX_TOTAL_SENSORS = 20; // Maximum number of sensors (core + dynamic) - static const uint8_t MAX_DYNAMIC_SENSORS = 14; // Maximum number of dynamic sensors - ConfigurationManager(); ~ConfigurationManager() = default; // No dynamic allocation, no need for custom destructor @@ -36,16 +18,6 @@ bool setConfiguration(std::string config) override; std::string getConfiguration() override; - // Sensor pool access - Sensor** getSensorArray(); - uint8_t getSensorCount() const; - - // Method to register available sensors (called once at startup) - void registerAvailableSensors(Sensor** availableSensors, uint8_t count); - - // Method to reset all sensors (clears their allocation) - void resetSensors(); - // System configuration getters unsigned long getLogPeriod() const { return m_logPeriod; } int getBackhaulCount() const { return m_backhaulCount; } @@ -53,34 +25,6 @@ int getLoggingMode() const { return m_loggingMode; } private: - // Fixed sensor array for all sensors (core + dynamic) - Sensor* m_sensorPool[MAX_TOTAL_SENSORS]; - uint8_t m_sensorCount; - - // Array to store pointers to available sensors (both core and optional) - Sensor** m_availableSensors; - uint8_t m_availableSensorCount; - - // Array to track which sensors are enabled - bool m_sensorEnabled[MAX_TOTAL_SENSORS]; - - // Pre-allocated sensors for dynamic configuration - // These sensors are declared here but initialized on demand - Haar m_haar; - Hedorah m_hedorah; - T9602 m_t9602; - TDR315H m_tdr315h[3]; // Allow up to 3 soil sensors - TEROS11 m_teros11[2]; // Allow up to 2 TEROS11 sensors - LI710 m_li710; - SO421 m_so421; - SP421 m_sp421; - ATMOS22 m_atmos22; - BaroVue10 m_barovue; - // Add other sensor types as needed - - // Flags to track which pre-allocated sensors are in use - bool m_dynamicSensorInUse[MAX_DYNAMIC_SENSORS]; - // System configuration unsigned long m_logPeriod; int m_backhaulCount; @@ -88,10 +32,7 @@ int m_loggingMode; // Internal methods - void clearSensorPool(); bool parseConfiguration(const std::string& config); - bool configureSensor(const std::string& type, int port, int talonPort, int version); - Sensor* findAvailableSensorByType(const std::string& type); // JSON parsing helpers std::string extractJsonField(const std::string& json, const std::string& fieldName); From 30d7a96b281a7e879ccf1a24eebcc5c899834ee4 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 14 May 2025 17:12:23 -0500 Subject: [PATCH 08/25] wip, working config file handling --- lib/Driver_-_Kestrel-FileHandler | 2 +- src/FlightControl_Demo.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index 94f904b..88c9252 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit 94f904b1e7c5c5c7b62db35bde5f327269500808 +Subproject commit 88c925252ddd79a923e30db4611c610d26129f9b diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 15ddd1c..dd74543 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -351,11 +351,13 @@ void setup() { // If no config loaded from SD, use default if (!configLoaded) { Serial.println("Loading default configuration..."); - std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0},\"sensors\":["; + //{"config":{"system":{"logPeriod":300,"backhaulCount":4,"powerSaveMode":1,"loggingMode":0}}} + std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0}}}"; configManager.setConfiguration(defaultConfig); // Save default config to SD card Serial.println("Saving default configuration to SD card..."); + fileSys.clearFileFromSD("config.json"); fileSys.writeToSD(defaultConfig.c_str(), "config.json"); } @@ -1402,6 +1404,8 @@ int updateConfiguration(String configJson) { configurePowerSave(desiredPowerSaveMode); // Save configuration to SD card + //need to clear the file before writing new config to it + fileSys.clearFileFromSD("config.json"); bool saveSuccess = fileSys.writeToSD(configStr.c_str(), "config.json"); if (!saveSuccess) { Serial.println("Warning: Failed to save configuration to SD card"); From 5feef117f3053096888db3e436129fb5a12bdb94 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Thu, 15 May 2025 17:33:04 -0500 Subject: [PATCH 09/25] sensor counts and file handling working better --- src/FlightControl_Demo.cpp | 73 ++++++++++++++++++---- src/configuration/ConfigurationManager.cpp | 48 ++++++++++++++ src/configuration/ConfigurationManager.h | 18 +++++- src/configuration/IConfiguration.h | 2 + 4 files changed, 128 insertions(+), 13 deletions(-) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index dd74543..4c7fee8 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -34,7 +34,8 @@ int commandExe(String command); int systemRestart(String resetType); int configurePowerSave(int desiredPowerSaveMode); int updateConfiguration(String configJson); -int getConfiguration(String dummy); +int getSystemConfiguration(String dummy); +int getSensorConfiguration(String dummy); // Forward declare the types of core sensors (for configuration purposes) const char* const CoreSensorTypes[] = { @@ -173,6 +174,9 @@ unsigned long logPeriod; int desiredPowerSaveMode; int loggingMode; +int systemConfigUid = 0; //Used to track the UID of the configuration file +int sensorConfigUid = 0; //Used to track the UID of the sensor configuration file + ConfigurationManager configManager; // /////////////////////////// BEGIN USER CONFIG //////////////////////// // //PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product @@ -278,7 +282,8 @@ void setup() { // talons[aux1.getTalonPort()] = &aux1; time_t startTime = millis(); Particle.function("updateConfig", updateConfiguration); - Particle.function("getConfig", getConfiguration); + Particle.function("getSystemConfig", getSystemConfiguration); + Particle.function("getSensorConfig", getSensorConfiguration); Particle.function("nodeID", setNodeID); Particle.function("findSensors", detectSensors); Particle.function("findTalons", detectTalons); @@ -351,9 +356,52 @@ void setup() { // If no config loaded from SD, use default if (!configLoaded) { Serial.println("Loading default configuration..."); - //{"config":{"system":{"logPeriod":300,"backhaulCount":4,"powerSaveMode":1,"loggingMode":0}}} - std::string defaultConfig = "{\"config\":{\"system\":{\"logPeriod\":300,\"backhaulCount\":4,\"powerSaveMode\":1,\"loggingMode\":0}}}"; - configManager.setConfiguration(defaultConfig); + // { + // "config": { + // "system": { + // "logPeriod": 300, + // "backhaulCount": 4, + // "powerSaveMode": 1, + // "loggingMode": 0, + // "numAuxTalons": 1, + // "numI2CTalons": 1, + // "numSDI12Talons": 1 + // }, + // "sensors": { + // "numET": 0, + // "numHaar": 0, + // "numSoil": 3, + // "numApogecSolar": 0, + // "numCO2": 0, + // "numO2": 0, + // "numPressure": 0 + // } + // } + //} + std::string defaultConfig = "{" + "\"config\":{" + "\"system\":{" + "\"logPeriod\":300," + "\"backhaulCount\":4," + "\"powerSaveMode\":1," + "\"loggingMode\":0," + "\"numAuxTalons\":1," + "\"numI2CTalons\":1," + "\"numSDI12Talons\":1" + "}," + "\"sensors\":{" + "\"numET\":0," + "\"numHaar\":0," + "\"numSoil\":3," + "\"numApogecSolar\":0," + "\"numCO2\":0," + "\"numO2\":0," + "\"numPressure\":0" + "}" + "}" + "}"; + configManager.setConfiguration(defaultConfig); + // Save default config to SD card Serial.println("Saving default configuration to SD card..."); @@ -1420,13 +1468,14 @@ int updateConfiguration(String configJson) { return -1; // Failure } -int getConfiguration(String dummy) { - std::string configStr = configManager.getConfiguration(); - if (configStr.length() > 0) { - return 1; - } else { - return -1; - } +int getSystemConfiguration(String dummy) { + int uid = configManager.updateSystemConfigurationUid(); + return uid; +} + +int getSensorConfiguration(String dummy) { + int uid = configManager.updateSensorConfigurationUid(); + return uid; } int takeSample(String dummy) diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index cfa435a..e24518e 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -28,6 +28,30 @@ return config; } + + int ConfigurationManager::updateSystemConfigurationUid() { + int tempUid = m_logPeriod << 16; + tempUid |= m_backhaulCount << 12; + tempUid |= m_powerSaveMode << 10; + tempUid |= m_loggingMode << 8; + tempUid |= m_numAuxTalons << 6; + tempUid |= m_numI2CTalons << 4; + tempUid |= m_numSDI12Talons << 2; + m_SystemConfigUid = tempUid; + return m_SystemConfigUid; + } + + int ConfigurationManager::updateSensorConfigurationUid() { + int tempUid = m_numET << 28; + tempUid |= m_numHaar << 24; + tempUid |= m_numSoil << 20; + tempUid |= m_numApogeeSolar << 16; + tempUid |= m_numCO2 << 12; + tempUid |= m_numO2 << 8; + tempUid |= m_numPressure << 4; + m_SensorConfigUid = tempUid; + return m_SensorConfigUid; + } bool ConfigurationManager::parseConfiguration(const std::string& configStr) { @@ -43,6 +67,30 @@ m_backhaulCount = extractJsonIntField(systemJson, "backhaulCount", 4); m_powerSaveMode = extractJsonIntField(systemJson, "powerSaveMode", 1); m_loggingMode = extractJsonIntField(systemJson, "loggingMode", 0); + m_numAuxTalons = extractJsonIntField(systemJson, "numAuxTalons", 1); + m_numI2CTalons = extractJsonIntField(systemJson, "numI2CTalons", 1); + m_numSDI12Talons = extractJsonIntField(systemJson, "numSDI12Talons", 1); + + updateSystemConfigurationUid(); + } + } + // Find the sensor configuration section + size_t sensorsStart = configStr.find("\"sensors\":{"); + if (sensorsStart != std::string::npos) { + size_t sensorsEnd = findMatchingBracket(configStr, sensorsStart + 10); + if (sensorsEnd > 0) { + std::string sensorsJson = configStr.substr(sensorsStart + 10, sensorsEnd - (sensorsStart + 10)); + + // Parse sensor settings + m_numET = extractJsonIntField(sensorsJson, "numET", 0); + m_numHaar = extractJsonIntField(sensorsJson, "numHaar", 0); + m_numSoil = extractJsonIntField(sensorsJson, "numSoil", 3); + m_numApogeeSolar = extractJsonIntField(sensorsJson, "numApogeeSolar", 0); + m_numCO2 = extractJsonIntField(sensorsJson, "numCO2", 0); + m_numO2 = extractJsonIntField(sensorsJson, "numO2", 0); + m_numPressure = extractJsonIntField(sensorsJson, "numPressure", 0); + + updateSensorConfigurationUid(); } } diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index c939449..460a931 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -17,6 +17,8 @@ // IConfiguration implementation bool setConfiguration(std::string config) override; std::string getConfiguration() override; + int updateSystemConfigurationUid() override; + int updateSensorConfigurationUid() override; // System configuration getters unsigned long getLogPeriod() const { return m_logPeriod; } @@ -30,7 +32,21 @@ int m_backhaulCount; int m_powerSaveMode; int m_loggingMode; - + int m_numAuxTalons; + int m_numI2CTalons; + int m_numSDI12Talons; + // Sensor configuration + int m_numET; + int m_numHaar; + int m_numSoil; + int m_numApogeeSolar; + int m_numCO2; + int m_numO2; + int m_numPressure; + + int m_SystemConfigUid; // Used to track the UID of the system configuration + int m_SensorConfigUid; // Used to track the UID of the sensor configuration + // Internal methods bool parseConfiguration(const std::string& config); diff --git a/src/configuration/IConfiguration.h b/src/configuration/IConfiguration.h index e6dcf1f..70cc26d 100644 --- a/src/configuration/IConfiguration.h +++ b/src/configuration/IConfiguration.h @@ -21,6 +21,8 @@ virtual ~IConfiguration() = default; virtual bool setConfiguration(std::string config) = 0; virtual std::string getConfiguration() = 0; + virtual int updateSystemConfigurationUid() = 0; + virtual int updateSensorConfigurationUid() = 0; }; #endif // I_CONFIGURATION_H \ No newline at end of file From fe7e7b3ddde90685e964fb99433c70106b07cada Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 16 May 2025 16:07:45 -0500 Subject: [PATCH 10/25] wip on config of sensors, static memory approach --- src/FlightControl_Demo.cpp | 311 ++++++++++++++++++----- src/configuration/ConfigurationManager.h | 11 + 2 files changed, 264 insertions(+), 58 deletions(-) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 4c7fee8..fc7d154 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -119,6 +119,8 @@ GpsSFE_UBLOX_GNSS realGps; HumidityTemperatureAdafruit_SHT4X realTempHumidity; AccelerometerMXC6655 realAccel; AccelerometerBMA456 realBackupAccel; +IOExpanderPCAL9535A ioAlpha(0x20); +IOExpanderPCAL9535A ioBeta(0x21); Kestrel logger(realTimeProvider, realGpio, @@ -141,24 +143,6 @@ Kestrel logger(realTimeProvider, true); KestrelFileHandler fileSys(logger); Gonk battery(5); //Instantiate with defaults, manually set to port 5 -AuxTalon aux(0, 0x14); //Instantiate AUX talon with deaults - null port and hardware v1.4 -I2CTalon i2c(0, 0x21); //Instantiate I2C talon with alt - null port and hardware v2.1 -SDI12Talon sdi12(0, 0x14); //Instantiate SDI12 talon with alt - null port and hardware v1.4 -SDI12TalonAdapter realSdi12(sdi12); -IOExpanderPCAL9535A ioAlpha(0x20); -IOExpanderPCAL9535A ioBeta(0x21); - -String globalNodeID = ""; //Store current node ID - -const uint8_t numTalons = 3; //Number must match the number of objects defined in `talonsToTest` array - -Talon* talons[Kestrel::numTalonPorts]; //Create an array of the total possible length -Talon* talonsToTest[numTalons] = { - &aux, - &i2c, - &sdi12 -}; - namespace LogModes { constexpr uint8_t STANDARD = 0; constexpr uint8_t PERFORMANCE = 1; @@ -178,49 +162,145 @@ int systemConfigUid = 0; //Used to track the UID of the configuration file int sensorConfigUid = 0; //Used to track the UID of the sensor configuration file ConfigurationManager configManager; -// /////////////////////////// BEGIN USER CONFIG //////////////////////// -// //PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product -// PRODUCT_VERSION(34) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number - -// int backhaulCount = 4; //Number of log events before backhaul is performed -// unsigned long logPeriod = 300; //Number of seconds to wait between logging events -// int desiredPowerSaveMode = PowerSaveModes::LOW_POWER; //Specify the power save mode you wish to use: PERFORMANCE, BALANCED, LOW_POWER, ULTRA_LOW_POWER -// int loggingMode = LogModes::STANDARD; //Specify the type of logging mode you wish to use: STANDARD, PERFORMANCE, BALANCED, NO_LOCAL - -Haar haar(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -// Haar haar1(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -// Haar haar2(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -SO421 apogeeO2(sdi12, 0, 0); //Instantiate O2 sensor with default ports and unknown version, pass over SDI12 Talon interface -SP421 apogeeSolar(sdi12, 0, 0); //Instantiate solar sensor with default ports and unknown version, pass over SDI12 Talon interface -// TEROS11 soil(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil1(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil2(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil3(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -Hedorah gas(0, 0, 0x10); //Instantiate CO2 sensor with default ports and v1.0 hardware -// T9602 humidity(0, 0, 0x00); //Instantiate Telair T9602 with default ports and version v0.0 -LI710 et(realTimeProvider, realSdi12, 0, 0); //Instantiate ET sensor with default ports and unknown version, pass over SDI12 Talon interface -BaroVue10 campPressure(sdi12, 0, 0x00); // Instantiate Barovue10 with default ports and v0.0 hardware - -const uint8_t numSensors = 9; //Number must match the number of objects defined in `sensors` array - -Sensor* const sensors[numSensors] = { + +constexpr uint8_t maxTalonsPerType = 3; //Maximum number of talons of each type + +AuxTalon auxTalons[maxTalonsPerType] = { + AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 + AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 + AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 +}; + +I2CTalon i2cTalons[maxTalonsPerType] = { + I2CTalon(0, 0x21), //Instantiate I2C talon with alt - null port and hardware v2.1 + I2CTalon(0, 0x21), //Instantiate I2C talon with alt - null port and hardware v2.1 + I2CTalon(0, 0x21) //Instantiate I2C talon with alt - null port and hardware v2.1 +}; + +SDI12Talon sdi12Talons[maxTalonsPerType] = { + SDI12Talon(0, 0x14), //Instantiate SDI12 talon with alt - null port and hardware v1.4 + SDI12Talon(0, 0x14), //Instantiate SDI12 talon with alt - null port and hardware v1.4 + SDI12Talon(0, 0x14) //Instantiate SDI12 talon with alt - null port and hardware v1.4 +}; + +SDI12TalonAdapter realSdi12(sdi12Talons[0]); + +String globalNodeID = ""; //Store current node ID + +const uint8_t numTalons = 3; //Number must match the number of objects defined in `talonsToTest` array + +Talon* talons[Kestrel::numTalonPorts]; //Create an array of the total possible length +Talon* talonsToTest[numTalons] = { + &auxTalons[0], + &i2cTalons[0], + &sdi12Talons[0] +}; + +constexpr uint8_t maxSensorsPerType = 7; + +Haar haarSensors[maxSensorsPerType] = { + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 + Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 +}; //Instantiate Haar sensors with default ports and version v2.0 + +SO421 apogeeO2Sensors[maxSensorsPerType] = { + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface + SO421(sdi12Talons[0], 0, 0) //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface +}; //Instantiate O2 sensors with default ports and unknown version + +SP421 apogeeSolarSensors[maxSensorsPerType] = { + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface + SP421(sdi12Talons[0], 0, 0) //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface +}; //Instantiate solar sensors with default ports and unknown version + +TDR315H soilSensors[maxSensorsPerType] = { + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface + TDR315H(sdi12Talons[0], 0, 0) //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface +}; //Instantiate soil sensors with default ports and unknown version + +Hedorah gasSensors[maxSensorsPerType] = { + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware + Hedorah(0, 0, 0x10) //Instantiate CO2 sensors with default ports and v1.0 hardware +}; //Instantiate CO2 sensors with default ports and v1.0 hardware + +T9602 humiditySensors[maxSensorsPerType] = { + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 + T9602(0, 0, 0x00) //Instantiate Telair T9602 with default ports and version v0.0 +}; //Instantiate Telair T9602 with default ports and version v1.1 + +LI710 etSensors[maxSensorsPerType] = { + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface + LI710(realTimeProvider, realSdi12, 0, 0) //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface +}; //Instantiate ET sensors with default ports and unknown version + +BaroVue10 pressureSensors[maxSensorsPerType] = { + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware + BaroVue10(sdi12Talons[0], 0, 0x00) // Instantiate Barovue10 with default ports and v0.0 hardware +}; // Instantiate Barovue10 with default ports and v1.1 hardware + +const uint8_t maxNumSensors = 17; //Number must match the number of objects defined in `sensors` array + +Sensor* sensors[maxNumSensors] = { &fileSys, - &aux, - &i2c, - &sdi12, &battery, - &logger, //Add sensors after this line - //&et, - //&haar, - &soil1, - // &apogeeSolar, - - &soil2, - &soil3, - // &gas, - // &apogeeO2, + &logger, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr }; -// /////////////////////////// END USER CONFIG ///////////////////////////////// + +int numSensors = 3; namespace PinsIO { //For Kestrel v1.1 constexpr uint16_t VUSB = 5; @@ -415,6 +495,63 @@ void setup() { desiredPowerSaveMode = configManager.getPowerSaveMode(); loggingMode = configManager.getLoggingMode(); + int numAuxTalons = configManager.getNumAuxTalons(); + int numI2CTalons = configManager.getNumI2CTalons(); + int numSDI12Talons = configManager.getNumSDI12Talons(); + + int numSoil = configManager.getNumSoil(); + int numHaar = configManager.getNumHaar(); + int numET = configManager.getNumET(); + int numApogeeSolar = configManager.getNumApogeeSolar(); + int numCO2 = configManager.getNumCO2(); + int numO2 = configManager.getNumO2(); + int numPressure = configManager.getNumPressure(); + + int talonsIndex = 0; + + for(int i = 0; i < numAuxTalons; i++) { + talons[talonsIndex++] = &auxTalons[i]; + numSensors++; + } + for(int i = 0; i < numI2CTalons; i++) { + talons[talonsIndex++] = &i2cTalons[i]; + numSensors++; + } + for(int i = 0; i < numSDI12Talons; i++) { + talons[talonsIndex++] = &sdi12Talons[i]; + numSensors++; + } + + int sensorsIndex = 3; + for(int i = 0; i < numSoil; i++) { + sensors[sensorsIndex++] = &soilSensors[i]; + numSensors++; + } + for(int i = 0; i < numHaar; i++) { + sensors[sensorsIndex++] = &haarSensors[i]; + numSensors++; + } + for(int i = 0; i < numET; i++) { + sensors[sensorsIndex++] = &etSensors[i]; + numSensors++; + } + for(int i = 0; i < numApogeeSolar; i++) { + sensors[sensorsIndex++] = &apogeeSolarSensors[i]; + numSensors++; + } + for(int i = 0; i < numCO2; i++) { + sensors[sensorsIndex++] = &gasSensors[i]; + numSensors++; + } + for(int i = 0; i < numO2; i++) { + sensors[sensorsIndex++] = &apogeeO2Sensors[i]; + numSensors++; + } + for(int i = 0; i < numPressure; i++) { + sensors[sensorsIndex++] = &pressureSensors[i]; + numSensors++; + } + // Apply power save mode configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) @@ -1448,6 +1585,64 @@ int updateConfiguration(String configJson) { desiredPowerSaveMode = configManager.getPowerSaveMode(); loggingMode = configManager.getLoggingMode(); + int numAuxTalons = configManager.getNumAuxTalons(); + int numI2CTalons = configManager.getNumI2CTalons(); + int numSDI12Talons = configManager.getNumSDI12Talons(); + + int numSoil = configManager.getNumSoil(); + int numHaar = configManager.getNumHaar(); + int numET = configManager.getNumET(); + int numApogeeSolar = configManager.getNumApogeeSolar(); + int numCO2 = configManager.getNumCO2(); + int numO2 = configManager.getNumO2(); + int numPressure = configManager.getNumPressure(); + + int talonsIndex = 0; + int numSensors = 3; //Start at 3 since first three are file system, battery, and logger + + for(int i = 0; i < numAuxTalons; i++) { + talons[talonsIndex++] = &auxTalons[i]; + numSensors++; + } + for(int i = 0; i < numI2CTalons; i++) { + talons[talonsIndex++] = &i2cTalons[i]; + numSensors++; + } + for(int i = 0; i < numSDI12Talons; i++) { + talons[talonsIndex++] = &sdi12Talons[i]; + numSensors++; + } + + int sensorsIndex = 3; + for(int i = 0; i < numSoil; i++) { + sensors[sensorsIndex++] = &soilSensors[i]; + numSensors++; + } + for(int i = 0; i < numHaar; i++) { + sensors[sensorsIndex++] = &haarSensors[i]; + numSensors++; + } + for(int i = 0; i < numET; i++) { + sensors[sensorsIndex++] = &etSensors[i]; + numSensors++; + } + for(int i = 0; i < numApogeeSolar; i++) { + sensors[sensorsIndex++] = &apogeeSolarSensors[i]; + numSensors++; + } + for(int i = 0; i < numCO2; i++) { + sensors[sensorsIndex++] = &gasSensors[i]; + numSensors++; + } + for(int i = 0; i < numO2; i++) { + sensors[sensorsIndex++] = &apogeeO2Sensors[i]; + numSensors++; + } + for(int i = 0; i < numPressure; i++) { + sensors[sensorsIndex++] = &pressureSensors[i]; + numSensors++; + } + // Apply power save mode configurePowerSave(desiredPowerSaveMode); diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 460a931..b6241c9 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -25,6 +25,17 @@ int getBackhaulCount() const { return m_backhaulCount; } int getPowerSaveMode() const { return m_powerSaveMode; } int getLoggingMode() const { return m_loggingMode; } + int getNumAuxTalons() const { return m_numAuxTalons; } + int getNumI2CTalons() const { return m_numI2CTalons; } + int getNumSDI12Talons() const { return m_numSDI12Talons; } + int getNumSoil() const { return m_numSoil; } + int getNumHaar() const { return m_numHaar; } + int getNumET() const { return m_numET; } + int getNumApogeeSolar() const { return m_numApogeeSolar; } + int getNumCO2() const { return m_numCO2; } + int getNumO2() const { return m_numO2; } + int getNumPressure() const { return m_numPressure; } + private: // System configuration From 551c6b65ca35e6fb80078307a0ec92bd2cf913e8 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Mon, 19 May 2025 18:18:31 -0500 Subject: [PATCH 11/25] added sensor maanger --- src/FlightControl_Demo.cpp | 149 +---------------- src/configuration/ConfigurationManager.h | 163 +++++++++++-------- src/configuration/SensorManager.cpp | 194 +++++++++++++++++++++++ src/configuration/SensorManager.h | 66 ++++++++ 4 files changed, 357 insertions(+), 215 deletions(-) create mode 100644 src/configuration/SensorManager.cpp create mode 100644 src/configuration/SensorManager.h diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index fc7d154..0f6b1f9 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -91,6 +91,7 @@ const char* const CoreSensorTypes[] = { #include "hardware/AccelerometerBMA456.h" #include "configuration/ConfigurationManager.h" +#include "configuration/SensorManager.h" const String firmwareVersion = "2.9.11"; const String schemaVersion = "2.2.9"; @@ -161,156 +162,8 @@ int loggingMode; int systemConfigUid = 0; //Used to track the UID of the configuration file int sensorConfigUid = 0; //Used to track the UID of the sensor configuration file -ConfigurationManager configManager; - -constexpr uint8_t maxTalonsPerType = 3; //Maximum number of talons of each type - -AuxTalon auxTalons[maxTalonsPerType] = { - AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 - AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 - AuxTalon(0, 0x14), //Instantiate AUX talon with deaults - null port and hardware v1.4 -}; - -I2CTalon i2cTalons[maxTalonsPerType] = { - I2CTalon(0, 0x21), //Instantiate I2C talon with alt - null port and hardware v2.1 - I2CTalon(0, 0x21), //Instantiate I2C talon with alt - null port and hardware v2.1 - I2CTalon(0, 0x21) //Instantiate I2C talon with alt - null port and hardware v2.1 -}; - -SDI12Talon sdi12Talons[maxTalonsPerType] = { - SDI12Talon(0, 0x14), //Instantiate SDI12 talon with alt - null port and hardware v1.4 - SDI12Talon(0, 0x14), //Instantiate SDI12 talon with alt - null port and hardware v1.4 - SDI12Talon(0, 0x14) //Instantiate SDI12 talon with alt - null port and hardware v1.4 -}; - -SDI12TalonAdapter realSdi12(sdi12Talons[0]); - String globalNodeID = ""; //Store current node ID -const uint8_t numTalons = 3; //Number must match the number of objects defined in `talonsToTest` array - -Talon* talons[Kestrel::numTalonPorts]; //Create an array of the total possible length -Talon* talonsToTest[numTalons] = { - &auxTalons[0], - &i2cTalons[0], - &sdi12Talons[0] -}; - -constexpr uint8_t maxSensorsPerType = 7; - -Haar haarSensors[maxSensorsPerType] = { - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 - Haar(0, 0, 0x20), //Instantiate Haar sensors with default ports and version v2.0 -}; //Instantiate Haar sensors with default ports and version v2.0 - -SO421 apogeeO2Sensors[maxSensorsPerType] = { - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0), //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface - SO421(sdi12Talons[0], 0, 0) //Instantiate O2 sensors with default ports and unknown version, pass over SDI12 Talon interface -}; //Instantiate O2 sensors with default ports and unknown version - -SP421 apogeeSolarSensors[maxSensorsPerType] = { - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0), //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface - SP421(sdi12Talons[0], 0, 0) //Instantiate solar sensors with default ports and unknown version, pass over SDI12 Talon interface -}; //Instantiate solar sensors with default ports and unknown version - -TDR315H soilSensors[maxSensorsPerType] = { - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0), //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface - TDR315H(sdi12Talons[0], 0, 0) //Instantiate soil sensors with default ports and unknown version, pass over SDI12 Talon interface -}; //Instantiate soil sensors with default ports and unknown version - -Hedorah gasSensors[maxSensorsPerType] = { - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10), //Instantiate CO2 sensors with default ports and v1.0 hardware - Hedorah(0, 0, 0x10) //Instantiate CO2 sensors with default ports and v1.0 hardware -}; //Instantiate CO2 sensors with default ports and v1.0 hardware - -T9602 humiditySensors[maxSensorsPerType] = { - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00), //Instantiate Telair T9602 with default ports and version v0.0 - T9602(0, 0, 0x00) //Instantiate Telair T9602 with default ports and version v0.0 -}; //Instantiate Telair T9602 with default ports and version v1.1 - -LI710 etSensors[maxSensorsPerType] = { - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0), //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface - LI710(realTimeProvider, realSdi12, 0, 0) //Instantiate ET sensors with default ports and unknown version, pass over SDI12 Talon interface -}; //Instantiate ET sensors with default ports and unknown version - -BaroVue10 pressureSensors[maxSensorsPerType] = { - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00), // Instantiate Barovue10 with default ports and v0.0 hardware - BaroVue10(sdi12Talons[0], 0, 0x00) // Instantiate Barovue10 with default ports and v0.0 hardware -}; // Instantiate Barovue10 with default ports and v1.1 hardware - -const uint8_t maxNumSensors = 17; //Number must match the number of objects defined in `sensors` array - -Sensor* sensors[maxNumSensors] = { - &fileSys, - &battery, - &logger, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr -}; - -int numSensors = 3; - -namespace PinsIO { //For Kestrel v1.1 - constexpr uint16_t VUSB = 5; -} - -namespace PinsIOAlpha { - constexpr uint16_t I2C_EXT_EN = 10; - constexpr uint16_t SD_CD = 8; - constexpr uint16_t SD_EN = 12; - constexpr uint16_t AUX_EN = 15; constexpr uint16_t CE = 11; constexpr uint16_t LED_EN = 13; } diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index b6241c9..52b3f7d 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -1,71 +1,100 @@ -/** - * @file ConfigurationManager.h - * @brief Configuration manager for sensor deployment - * - * © 2025 Regents of the University of Minnesota. All rights reserved. - */ +// ConfigurationManager.h - Updated to work with vectors +#ifndef CONFIGURATION_MANAGER_H +#define CONFIGURATION_MANAGER_H - #ifndef CONFIGURATION_MANAGER_H - #define CONFIGURATION_MANAGER_H - - #include "IConfiguration.h" - class ConfigurationManager : public IConfiguration { - public: - ConfigurationManager(); - ~ConfigurationManager() = default; // No dynamic allocation, no need for custom destructor - - // IConfiguration implementation - bool setConfiguration(std::string config) override; - std::string getConfiguration() override; - int updateSystemConfigurationUid() override; - int updateSensorConfigurationUid() override; - - // System configuration getters - unsigned long getLogPeriod() const { return m_logPeriod; } - int getBackhaulCount() const { return m_backhaulCount; } - int getPowerSaveMode() const { return m_powerSaveMode; } - int getLoggingMode() const { return m_loggingMode; } - int getNumAuxTalons() const { return m_numAuxTalons; } - int getNumI2CTalons() const { return m_numI2CTalons; } - int getNumSDI12Talons() const { return m_numSDI12Talons; } - int getNumSoil() const { return m_numSoil; } - int getNumHaar() const { return m_numHaar; } - int getNumET() const { return m_numET; } - int getNumApogeeSolar() const { return m_numApogeeSolar; } - int getNumCO2() const { return m_numCO2; } - int getNumO2() const { return m_numO2; } - int getNumPressure() const { return m_numPressure; } +#include "IConfiguration.h" +#include +#include - - private: - // System configuration - unsigned long m_logPeriod; - int m_backhaulCount; - int m_powerSaveMode; - int m_loggingMode; - int m_numAuxTalons; - int m_numI2CTalons; - int m_numSDI12Talons; - // Sensor configuration - int m_numET; - int m_numHaar; - int m_numSoil; - int m_numApogeeSolar; - int m_numCO2; - int m_numO2; - int m_numPressure; +// Forward declarations for sensor types +class Sensor; +class Talon; +class AuxTalon; +class I2CTalon; +class SDI12Talon; +class Haar; +class SO421; +class SP421; +class TDR315H; +class Hedorah; +class T9602; +class LI710; +class BaroVue10; - int m_SystemConfigUid; // Used to track the UID of the system configuration - int m_SensorConfigUid; // Used to track the UID of the sensor configuration +class ConfigurationManager : public IConfiguration { +public: + ConfigurationManager(); + ~ConfigurationManager() = default; + + // IConfiguration implementation + bool setConfiguration(std::string config) override; + std::string getConfiguration() override; + int updateSystemConfigurationUid() override; + int updateSensorConfigurationUid() override; + + // System configuration getters + unsigned long getLogPeriod() const { return m_logPeriod; } + int getBackhaulCount() const { return m_backhaulCount; } + int getPowerSaveMode() const { return m_powerSaveMode; } + int getLoggingMode() const { return m_loggingMode; } + + // Sensor count getters + int getNumAuxTalons() const { return m_numAuxTalons; } + int getNumI2CTalons() const { return m_numI2CTalons; } + int getNumSDI12Talons() const { return m_numSDI12Talons; } + int getNumSoil() const { return m_numSoil; } + int getNumHaar() const { return m_numHaar; } + int getNumET() const { return m_numET; } + int getNumApogeeSolar() const { return m_numApogeeSolar; } + int getNumCO2() const { return m_numCO2; } + int getNumO2() const { return m_numO2; } + int getNumPressure() const { return m_numPressure; } + + // Vector management methods + void createSensorVectors(); + void clearSensorVectors(); + + // Static factory methods for creating sensors + static std::unique_ptr createAuxTalon(); + static std::unique_ptr createI2CTalon(); + static std::unique_ptr createSDI12Talon(); + static std::unique_ptr createHaarSensor(); + static std::unique_ptr createO2Sensor(SDI12Talon& talon); + static std::unique_ptr createSolarSensor(SDI12Talon& talon); + static std::unique_ptr createSoilSensor(SDI12Talon& talon); + static std::unique_ptr createCO2Sensor(); + static std::unique_ptr createHumiditySensor(); + static std::unique_ptr createETSensor(class ITimeProvider& timeProvider, class ISDI12Talon& talon); + static std::unique_ptr createPressureSensor(SDI12Talon& talon); + +private: + // System configuration + unsigned long m_logPeriod; + int m_backhaulCount; + int m_powerSaveMode; + int m_loggingMode; + + // Sensor counts + int m_numAuxTalons; + int m_numI2CTalons; + int m_numSDI12Talons; + int m_numET; + int m_numHaar; + int m_numSoil; + int m_numApogeeSolar; + int m_numCO2; + int m_numO2; + int m_numPressure; - // Internal methods - bool parseConfiguration(const std::string& config); - - // JSON parsing helpers - std::string extractJsonField(const std::string& json, const std::string& fieldName); - int extractJsonIntField(const std::string& json, const std::string& fieldName, int defaultValue); - bool extractJsonBoolField(const std::string& json, const std::string& fieldName, bool defaultValue); - int findMatchingBracket(const std::string& str, int openPos); - }; - - #endif // CONFIGURATION_MANAGER_H \ No newline at end of file + int m_SystemConfigUid; + int m_SensorConfigUid; + + // Internal methods + bool parseConfiguration(const std::string& config); + std::string extractJsonField(const std::string& json, const std::string& fieldName); + int extractJsonIntField(const std::string& json, const std::string& fieldName, int defaultValue); + bool extractJsonBoolField(const std::string& json, const std::string& fieldName, bool defaultValue); + int findMatchingBracket(const std::string& str, int openPos); +}; + +#endif // CONFIGURATION_MANAGER_H \ No newline at end of file diff --git a/src/configuration/SensorManager.cpp b/src/configuration/SensorManager.cpp new file mode 100644 index 0000000..e2ab94d --- /dev/null +++ b/src/configuration/SensorManager.cpp @@ -0,0 +1,194 @@ +// SensorManager.cpp - Implementation +#include "SensorManager.h" +#include "ConfigurationManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SensorManager::SensorManager(ConfigurationManager& configManager) + : configManager(configManager) { +} + +void SensorManager::initializeSensors(ITimeProvider& timeProvider, ISDI12Talon& sdi12Interface) { + // Clear existing sensors + clearAllSensors(); + + // Create Talons + for (int i = 0; i < configManager.getNumAuxTalons(); i++) { + auxTalons.push_back(ConfigurationManager::createAuxTalon()); + } + + for (int i = 0; i < configManager.getNumI2CTalons(); i++) { + i2cTalons.push_back(ConfigurationManager::createI2CTalon()); + } + + for (int i = 0; i < configManager.getNumSDI12Talons(); i++) { + sdi12Talons.push_back(ConfigurationManager::createSDI12Talon()); + } + + // Create Sensors + for (int i = 0; i < configManager.getNumHaar(); i++) { + haarSensors.push_back(ConfigurationManager::createHaarSensor()); + } + + // For SDI12-based sensors, use the first available SDI12 talon + if (!sdi12Talons.empty()) { + SDI12Talon* firstSDI12Talon = sdi12Talons[0].get(); + + for (int i = 0; i < configManager.getNumO2(); i++) { + apogeeO2Sensors.push_back(ConfigurationManager::createO2Sensor(*firstSDI12Talon)); + } + + for (int i = 0; i < configManager.getNumApogeeSolar(); i++) { + apogeeSolarSensors.push_back(ConfigurationManager::createSolarSensor(*firstSDI12Talon)); + } + + for (int i = 0; i < configManager.getNumSoil(); i++) { + soilSensors.push_back(ConfigurationManager::createSoilSensor(*firstSDI12Talon)); + } + + for (int i = 0; i < configManager.getNumET(); i++) { + etSensors.push_back(ConfigurationManager::createETSensor(timeProvider, sdi12Interface)); + } + + for (int i = 0; i < configManager.getNumPressure(); i++) { + pressureSensors.push_back(ConfigurationManager::createPressureSensor(*firstSDI12Talon)); + } + } + + for (int i = 0; i < configManager.getNumCO2(); i++) { + gasSensors.push_back(ConfigurationManager::createCO2Sensor()); + } +} + +void SensorManager::clearAllSensors() { + auxTalons.clear(); + i2cTalons.clear(); + sdi12Talons.clear(); + haarSensors.clear(); + apogeeO2Sensors.clear(); + apogeeSolarSensors.clear(); + soilSensors.clear(); + gasSensors.clear(); + humiditySensors.clear(); + etSensors.clear(); + pressureSensors.clear(); +} + +std::vector SensorManager::getAllTalons() { + std::vector allTalons; + + for (auto& talon : auxTalons) { + allTalons.push_back(talon.get()); + } + + for (auto& talon : i2cTalons) { + allTalons.push_back(talon.get()); + } + + for (auto& talon : sdi12Talons) { + allTalons.push_back(talon.get()); + } + + return allTalons; +} + +std::vector SensorManager::getAllSensors() { + std::vector allSensors; + + // Add all sensor types to the vector + for (auto& sensor : haarSensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : apogeeO2Sensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : apogeeSolarSensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : soilSensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : gasSensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : humiditySensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : etSensors) { + allSensors.push_back(sensor.get()); + } + + for (auto& sensor : pressureSensors) { + allSensors.push_back(sensor.get()); + } + + return allSensors; +} + +int SensorManager::getTotalSensorCount() const { + // Core sensors (3) + Talons + Other Sensors + return 3 + auxTalons.size() + i2cTalons.size() + sdi12Talons.size() + + haarSensors.size() + apogeeO2Sensors.size() + apogeeSolarSensors.size() + + soilSensors.size() + gasSensors.size() + humiditySensors.size() + + etSensors.size() + pressureSensors.size(); +} + +// ConfigurationManager.cpp - Updated factory methods +std::unique_ptr ConfigurationManager::createAuxTalon() { + return std::make_unique(0, 0x14); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createI2CTalon() { + return std::make_unique(0, 0x21); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createSDI12Talon() { + return std::make_unique(0, 0x14); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createHaarSensor() { + return std::make_unique(0, 0, 0x20); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createO2Sensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createSolarSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createSoilSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createCO2Sensor() { + return std::make_unique(0, 0, 0x10); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createHumiditySensor() { + return std::make_unique(0, 0, 0x00); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createETSensor(ITimeProvider& timeProvider, ISDI12Talon& talon) { + return std::make_unique(timeProvider, talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createPressureSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0x00); // Default ports and version +} \ No newline at end of file diff --git a/src/configuration/SensorManager.h b/src/configuration/SensorManager.h new file mode 100644 index 0000000..1834e83 --- /dev/null +++ b/src/configuration/SensorManager.h @@ -0,0 +1,66 @@ +// SensorManager.h - New class to manage sensor vectors +#ifndef SENSOR_MANAGER_H +#define SENSOR_MANAGER_H + +#include +#include + +// Forward declarations +class Sensor; +class Talon; +class AuxTalon; +class I2CTalon; +class SDI12Talon; +class ConfigurationManager; +class ITimeProvider; +class ISDI12Talon; + +class SensorManager { +public: + SensorManager(ConfigurationManager& configManager); + ~SensorManager() = default; + + // Initialize all sensor vectors based on configuration + void initializeSensors(ITimeProvider& timeProvider, ISDI12Talon& sdi12Interface); + + // Clear all sensors + void clearAllSensors(); + + // Getters for sensor vectors + const std::vector>& getAuxTalons() const { return auxTalons; } + const std::vector>& getI2CTalons() const { return i2cTalons; } + const std::vector>& getSDI12Talons() const { return sdi12Talons; } + + std::vector>& getAuxTalons() { return auxTalons; } + std::vector>& getI2CTalons() { return i2cTalons; } + std::vector>& getSDI12Talons() { return sdi12Talons; } + + // Get all talons as a single vector of base pointers + std::vector getAllTalons(); + + // Get all sensors as a single vector (excluding core sensors) + std::vector getAllSensors(); + + // Get total sensor count (including core sensors) + int getTotalSensorCount() const; + +private: + ConfigurationManager& configManager; + + // Talon vectors + std::vector> auxTalons; + std::vector> i2cTalons; + std::vector> sdi12Talons; + + // Sensor vectors + std::vector> haarSensors; + std::vector> apogeeO2Sensors; + std::vector> apogeeSolarSensors; + std::vector> soilSensors; + std::vector> gasSensors; + std::vector> humiditySensors; + std::vector> etSensors; + std::vector> pressureSensors; +}; + +#endif // SENSOR_MANAGER_H \ No newline at end of file From 9273a77231157ded77bfb53a7c3ddce5f695fb6b Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Mon, 19 May 2025 18:19:28 -0500 Subject: [PATCH 12/25] accidental change --- src/FlightControl_Demo.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 0f6b1f9..ff40cd8 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -164,6 +164,20 @@ int sensorConfigUid = 0; //Used to track the UID of the sensor configuration fil String globalNodeID = ""; //Store current node ID +ConfigurationManager configManager; +SensorManager sensorManager(configManager); +std::vector allSensors; +std::vector allTalons; +SDI12TalonAdapter* realSdi12 = nullptr; +namespace PinsIO { //For Kestrel v1.1 + constexpr uint16_t VUSB = 5; +} + +namespace PinsIOAlpha { + constexpr uint16_t I2C_EXT_EN = 10; + constexpr uint16_t SD_CD = 8; + constexpr uint16_t SD_EN = 12; + constexpr uint16_t AUX_EN = 15; constexpr uint16_t CE = 11; constexpr uint16_t LED_EN = 13; } From 4497a98636bdf8b6501c34b1be96e65d658c65bc Mon Sep 17 00:00:00 2001 From: zradlicz Date: Tue, 20 May 2025 11:55:50 -0500 Subject: [PATCH 13/25] added tests --- src/FlightControl_Demo.cpp | 358 ++++++------------ src/configuration/ConfigurationManager.cpp | 47 ++- src/configuration/ConfigurationManager.h | 48 ++- src/configuration/SensorManager.cpp | 56 --- test/sample_configurations/full_config.json | 86 ----- .../sample_configurations/minimal_config.json | 43 --- .../standard_config.json | 55 --- .../ConfigurationManagerTest.cpp | 6 + test/unit/SensorManager/SensorManagerTest.cpp | 0 9 files changed, 195 insertions(+), 504 deletions(-) delete mode 100644 test/sample_configurations/full_config.json delete mode 100644 test/sample_configurations/minimal_config.json delete mode 100644 test/sample_configurations/standard_config.json create mode 100644 test/unit/ConfigurationManager/ConfigurationManagerTest.cpp create mode 100644 test/unit/SensorManager/SensorManagerTest.cpp diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index ff40cd8..060ed8b 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -36,6 +36,9 @@ int configurePowerSave(int desiredPowerSaveMode); int updateConfiguration(String configJson); int getSystemConfiguration(String dummy); int getSensorConfiguration(String dummy); +bool loadConfiguration(); +void updateSensorVectors(); +void initializeSensorSystem(); // Forward declare the types of core sensors (for configuration purposes) const char* const CoreSensorTypes[] = { @@ -166,8 +169,8 @@ String globalNodeID = ""; //Store current node ID ConfigurationManager configManager; SensorManager sensorManager(configManager); -std::vector allSensors; -std::vector allTalons; +std::vector sensors; +std::vector talons; SDI12TalonAdapter* realSdi12 = nullptr; namespace PinsIO { //For Kestrel v1.1 constexpr uint16_t VUSB = 5; @@ -289,135 +292,12 @@ void setup() { Serial.begin(1000000); waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds - - // Load configuration from SD card if possible - bool configLoaded = false; - std::string configStr = fileSys.readFromSD("config.json").c_str(); - if (!configStr.empty()) { - Serial.println("Loading configuration from SD card..."); - configLoaded = configManager.setConfiguration(configStr); - Serial.println(configStr.c_str()); - } - - // If no config loaded from SD, use default - if (!configLoaded) { - Serial.println("Loading default configuration..."); - // { - // "config": { - // "system": { - // "logPeriod": 300, - // "backhaulCount": 4, - // "powerSaveMode": 1, - // "loggingMode": 0, - // "numAuxTalons": 1, - // "numI2CTalons": 1, - // "numSDI12Talons": 1 - // }, - // "sensors": { - // "numET": 0, - // "numHaar": 0, - // "numSoil": 3, - // "numApogecSolar": 0, - // "numCO2": 0, - // "numO2": 0, - // "numPressure": 0 - // } - // } - //} - std::string defaultConfig = "{" - "\"config\":{" - "\"system\":{" - "\"logPeriod\":300," - "\"backhaulCount\":4," - "\"powerSaveMode\":1," - "\"loggingMode\":0," - "\"numAuxTalons\":1," - "\"numI2CTalons\":1," - "\"numSDI12Talons\":1" - "}," - "\"sensors\":{" - "\"numET\":0," - "\"numHaar\":0," - "\"numSoil\":3," - "\"numApogecSolar\":0," - "\"numCO2\":0," - "\"numO2\":0," - "\"numPressure\":0" - "}" - "}" - "}"; - configManager.setConfiguration(defaultConfig); - - - // Save default config to SD card - Serial.println("Saving default configuration to SD card..."); - fileSys.clearFileFromSD("config.json"); - fileSys.writeToSD(defaultConfig.c_str(), "config.json"); - } - - // Set global variables from configuration - logPeriod = configManager.getLogPeriod(); - backhaulCount = configManager.getBackhaulCount(); - desiredPowerSaveMode = configManager.getPowerSaveMode(); - loggingMode = configManager.getLoggingMode(); - - int numAuxTalons = configManager.getNumAuxTalons(); - int numI2CTalons = configManager.getNumI2CTalons(); - int numSDI12Talons = configManager.getNumSDI12Talons(); - - int numSoil = configManager.getNumSoil(); - int numHaar = configManager.getNumHaar(); - int numET = configManager.getNumET(); - int numApogeeSolar = configManager.getNumApogeeSolar(); - int numCO2 = configManager.getNumCO2(); - int numO2 = configManager.getNumO2(); - int numPressure = configManager.getNumPressure(); - - int talonsIndex = 0; - - for(int i = 0; i < numAuxTalons; i++) { - talons[talonsIndex++] = &auxTalons[i]; - numSensors++; - } - for(int i = 0; i < numI2CTalons; i++) { - talons[talonsIndex++] = &i2cTalons[i]; - numSensors++; - } - for(int i = 0; i < numSDI12Talons; i++) { - talons[talonsIndex++] = &sdi12Talons[i]; - numSensors++; - } + //load configuration from SD card, default config if not found or not possible + loadConfiguration(); - int sensorsIndex = 3; - for(int i = 0; i < numSoil; i++) { - sensors[sensorsIndex++] = &soilSensors[i]; - numSensors++; - } - for(int i = 0; i < numHaar; i++) { - sensors[sensorsIndex++] = &haarSensors[i]; - numSensors++; - } - for(int i = 0; i < numET; i++) { - sensors[sensorsIndex++] = &etSensors[i]; - numSensors++; - } - for(int i = 0; i < numApogeeSolar; i++) { - sensors[sensorsIndex++] = &apogeeSolarSensors[i]; - numSensors++; - } - for(int i = 0; i < numCO2; i++) { - sensors[sensorsIndex++] = &gasSensors[i]; - numSensors++; - } - for(int i = 0; i < numO2; i++) { - sensors[sensorsIndex++] = &apogeeO2Sensors[i]; - numSensors++; - } - for(int i = 0; i < numPressure; i++) { - sensors[sensorsIndex++] = &pressureSensors[i]; - numSensors++; - } + //initilize all sensors + initializeSensorSystem(); // Apply power save mode configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) @@ -778,9 +658,9 @@ String getErrorString() if(globalNodeID != "") errors = errors + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID else errors = errors + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID errors = errors + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - errors = errors + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors + errors = errors + "\"NumDevices\":" + String(sensors.size()) + ","; //Concatonate number of sensors errors = errors + "\"Devices\":["; - for(int i = 0; i < numSensors; i++) { + for(int i = 0; i < sensors.size(); i++) { if(sensors[i]->totalErrors() > 0) { numErrors = numErrors + sensors[i]->totalErrors(); //Increment the total error count if(!errors.endsWith("[")) errors = errors + ","; //Only append if not first entry @@ -802,13 +682,13 @@ String getDataString() if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors + leader = leader + "\"NumDevices\":" + String(sensors.size()) + ","; //Concatonate number of sensors leader = leader + "\"Devices\":["; const String closer = "]}}"; String output = leader; uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { + for(int i = 0; i < sensors.size(); i++) { logger.disableDataAll(); //Turn off data to all ports, then just enable those needed if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid @@ -851,7 +731,7 @@ String getDataString() if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry output = output + "{" + val + "}"; //Append result deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry + // if(i + 1 < sensors.size()) diagnostic = diagnostic + ","; //Only append if not last entry } else { output = output + closer + "\n"; //End this packet @@ -862,12 +742,12 @@ String getDataString() // if(deviceCount > 0) data = data + ","; //Preappend comma only if not first addition // data = data + "{" + val + "}"; // deviceCount++; - // // if(i + 1 < numSensors) metadata = metadata + ","; //Only append if not last entry + // // if(i + 1 < sensors.size()) metadata = metadata + ","; //Only append if not last entry // } Serial.print("Cumulative data string: "); //DEBUG! Serial.println(output); //DEBUG! // data = data + sensors[i]->getData(logger.getTime()); //DEBUG! REPLACE! - // if(i + 1 < numSensors) data = data + ","; //Only append if not last entry + // if(i + 1 < sensors.size()) data = data + ","; //Only append if not last entry if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), false); //Turn off power for the given port on the Talon //DEBUG! @@ -885,12 +765,12 @@ String getDiagnosticString(uint8_t level) if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ",\"Level\":" + String(level) + ",\"Devices\":["; //Concatonate number of sensors and level + leader = leader + "\"NumDevices\":" + String(sensors.size()) + ",\"Level\":" + String(level) + ",\"Devices\":["; //Concatonate number of sensors and level const String closer = "]}}"; String output = leader; uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { + for(int i = 0; i < sensors.size(); i++) { logger.disableDataAll(); //Turn off data to all ports, then just enable those needed if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid @@ -910,7 +790,7 @@ String getDiagnosticString(uint8_t level) if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry output = output + "{" + diagnostic + "}"; //Append result deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry + // if(i + 1 < sensors.size()) diagnostic = diagnostic + ","; //Only append if not last entry } else { output = output + closer + "\n"; //End this packet @@ -936,7 +816,7 @@ String getMetadataString() if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors + leader = leader + "\"NumDevices\":" + String(sensors.size()) + ","; //Concatonate number of sensors leader = leader + "\"Devices\":["; const String closer = "]}}"; String output = leader; @@ -954,7 +834,7 @@ String getMetadataString() //FIX! Add support for device name uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { + for(int i = 0; i < sensors.size(); i++) { logger.disableDataAll(); //Turn off data to all ports, then just enable those needed if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on data to required Talon port only if not core and port is valid // if(!sensors[i]->isTalon()) { //If sensor is not Talon @@ -972,14 +852,14 @@ String getMetadataString() // if(deviceCount > 0) metadata = metadata + ","; //Preappend comma only if not first addition // metadata = metadata + val; // deviceCount++; - // // if(i + 1 < numSensors) metadata = metadata + ","; //Only append if not last entry + // // if(i + 1 < sensors.size()) metadata = metadata + ","; //Only append if not last entry // } if(!val.equals("")) { //Only append if not empty string if(output.length() - output.lastIndexOf('\n') + val.length() + closer.length() + 1 < Kestrel::MAX_MESSAGE_LENGTH) { //Add +1 to account for comma appending, subtract any previous lines from count if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry output = output + "{" + val + "}"; //Append result deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry + // if(i + 1 < sensors.size()) diagnostic = diagnostic + ","; //Only append if not last entry } else { output = output + closer + "\n"; //End this packet @@ -1000,7 +880,7 @@ String initSensors() if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ",\"Devices\":["; //Concatonate number of sensors and level + leader = leader + "\"NumDevices\":" + String(sensors.size()) + ",\"Devices\":["; //Concatonate number of sensors and level String closer = "]}}"; String output = leader; @@ -1009,7 +889,7 @@ String initSensors() bool missingSensor = false; // output = output + "\"Devices\":["; uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { + for(int i = 0; i < sensors.size(); i++) { logger.disableDataAll(); //Turn off data to all ports, then just enable those needed if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on data to required Talon port only if not core and the port is valid logger.enableI2C_OB(false); @@ -1046,7 +926,7 @@ String initSensors() if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry output = output + "{" + val + "}"; //Append result deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry + // if(i + 1 < sensors.size()) diagnostic = diagnostic + ","; //Only append if not last entry } else { output = output + closer + "\n"; //End this packet @@ -1185,16 +1065,16 @@ int sleepSensors() { if(powerSaveMode > PowerSaveModes::PERFORMANCE) { //Only turn off is power save requested Serial.println("BEGIN SENSOR SLEEP"); //DEBUG! - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects + for(int s = 0; s < sensors.size(); s++) { //Iterate over all sensors objects //If not set to keep power on and Talon is assocated, power down sensor. Ignore if core device, we will handle these seperately - if(sensors[s]->keepPowered == false && sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < numTalons) { + if(sensors[s]->keepPowered == false && sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < talons.size()) { Serial.print("Power Down Sensor "); //DEBUG! Serial.print(s + 1); Serial.print(","); Serial.println(sensors[s]->getTalonPort()); talons[sensors[s]->getTalonPort() - 1]->enablePower(sensors[s]->getSensorPort(), false); //Turn off power for any sensor which does not need to be kept powered } - else if(sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < numTalons){ //If sensor has a position and is not core, but keepPowered is true, run sleep routine + else if(sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < talons.size()){ //If sensor has a position and is not core, but keepPowered is true, run sleep routine Serial.print("Sleep Sensor "); //DEBUG! Serial.println(s + 1); sensors[s]->sleep(); //If not powered down, run sleep protocol @@ -1241,7 +1121,7 @@ int wakeSensors() logger.enableData(talons[t]->getTalonPort(), false); //Turn data back off for given port } } - for(int s = 0; s < numSensors; s++) { + for(int s = 0; s < sensors.size(); s++) { if(sensors[s]->getTalonPort() != 0) { logger.enableData(sensors[s]->getTalonPort(), true); //Turn on data for given port sensors[s]->wake(realTimeProvider); //Wake each sensor @@ -1260,7 +1140,7 @@ int detectTalons(String dummyStr) // bool hasCriticalError = false; // bool hasError = false; - // for(int i = 0; i < numTalons; i++) { //Initialize all Talons //DEBUG! + // for(int i = 0; i < talons.size(); i++) { //Initialize all Talons //DEBUG! // talons[i]->begin(Time.now(), hasCriticalError, hasError); // } // logger.enableI2C_External(false); //Turn off connection to @@ -1282,20 +1162,19 @@ int detectTalons(String dummyStr) if(error == 0) break; //Exit loop once we are able to connect with Talon } quickTalonShutdown(); //Quickly disables power to all ports on I2C or SDI talons, this is a kluge - for(int t = 0; t < numTalons; t++) { //Iterate over all Talon objects - if(talonsToTest[t]->getTalonPort() == 0) { //If port not already specified + for(int t = 0; t < talons.size(); t++) { //Iterate over all Talon objects + if(talons[t]->getTalonPort() == 0) { //If port not already specified Serial.print("New Talon: "); Serial.println(t); // logger.enableAuxPower(false); //Turn aux power off, then configure port to on, then switch aux power back for faster response // logger.enablePower(port, true); //Toggle power just before testing to get result within 10ms // logger.enablePower(port, false); - if(talonsToTest[t]->isPresent()) { //Test if that Talon is present, if it is, configure the port - talonsToTest[t]->setTalonPort(port); - talons[port - 1] = talonsToTest[t]; //Copy test talon object to index location in talons array + if(talons[t]->isPresent()) { //Test if that Talon is present, if it is, configure the port + talons[t]->setTalonPort(port); Serial.print("Talon Port Result "); //DEBUG! Serial.print(t); Serial.print(": "); - Serial.println(talonsToTest[t]->getTalonPort()); + Serial.println(talons[t]->getTalonPort()); break; //Exit the interation after the first one tests positive } } @@ -1390,7 +1269,7 @@ int detectSensors(String dummyStr) Serial.print(t + 1); Serial.print(","); Serial.println(p); - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects + for(int s = 0; s < sensors.size(); s++) { //Iterate over all sensors objects if(sensors[s]->getTalonPort() == 0 && talons[t]->talonInterface == sensors[s]->sensorInterface) { //If Talon not already specified AND sensor bus is compatible with Talon bus Serial.print("Test Sensor: "); //DEBUG! Serial.println(s); @@ -1441,93 +1320,10 @@ int updateConfiguration(String configJson) { Serial.println("Error: Invalid configuration format. Missing 'config' element."); return -2; // Invalid format } - - // Convert String to std::string - std::string configStr = configJson.c_str(); - - if (configManager.setConfiguration(configStr)) { - // Update global variables - logPeriod = configManager.getLogPeriod(); - backhaulCount = configManager.getBackhaulCount(); - desiredPowerSaveMode = configManager.getPowerSaveMode(); - loggingMode = configManager.getLoggingMode(); - - int numAuxTalons = configManager.getNumAuxTalons(); - int numI2CTalons = configManager.getNumI2CTalons(); - int numSDI12Talons = configManager.getNumSDI12Talons(); - - int numSoil = configManager.getNumSoil(); - int numHaar = configManager.getNumHaar(); - int numET = configManager.getNumET(); - int numApogeeSolar = configManager.getNumApogeeSolar(); - int numCO2 = configManager.getNumCO2(); - int numO2 = configManager.getNumO2(); - int numPressure = configManager.getNumPressure(); - - int talonsIndex = 0; - int numSensors = 3; //Start at 3 since first three are file system, battery, and logger - - for(int i = 0; i < numAuxTalons; i++) { - talons[talonsIndex++] = &auxTalons[i]; - numSensors++; - } - for(int i = 0; i < numI2CTalons; i++) { - talons[talonsIndex++] = &i2cTalons[i]; - numSensors++; - } - for(int i = 0; i < numSDI12Talons; i++) { - talons[talonsIndex++] = &sdi12Talons[i]; - numSensors++; - } - - int sensorsIndex = 3; - for(int i = 0; i < numSoil; i++) { - sensors[sensorsIndex++] = &soilSensors[i]; - numSensors++; - } - for(int i = 0; i < numHaar; i++) { - sensors[sensorsIndex++] = &haarSensors[i]; - numSensors++; - } - for(int i = 0; i < numET; i++) { - sensors[sensorsIndex++] = &etSensors[i]; - numSensors++; - } - for(int i = 0; i < numApogeeSolar; i++) { - sensors[sensorsIndex++] = &apogeeSolarSensors[i]; - numSensors++; - } - for(int i = 0; i < numCO2; i++) { - sensors[sensorsIndex++] = &gasSensors[i]; - numSensors++; - } - for(int i = 0; i < numO2; i++) { - sensors[sensorsIndex++] = &apogeeO2Sensors[i]; - numSensors++; - } - for(int i = 0; i < numPressure; i++) { - sensors[sensorsIndex++] = &pressureSensors[i]; - numSensors++; - } - - // Apply power save mode - configurePowerSave(desiredPowerSaveMode); - - // Save configuration to SD card - //need to clear the file before writing new config to it - fileSys.clearFileFromSD("config.json"); - bool saveSuccess = fileSys.writeToSD(configStr.c_str(), "config.json"); - if (!saveSuccess) { - Serial.println("Warning: Failed to save configuration to SD card"); - //add error for failed configuration save - } - - Serial.println("Configuration update complete"); - return 1; // Success - } - - Serial.println("Failed to update configuration"); - return -1; // Failure + fileSys.clearFileFromSD("config.json"); + fileSys.writeToSD(configJson.c_str(), "config.json"); + + return loadConfiguration(); } int getSystemConfiguration(String dummy) { @@ -1631,12 +1427,78 @@ int systemRestart(String resetType) int configurePowerSave(int desiredPowerSaveMode) { powerSaveMode = desiredPowerSaveMode; //Configure global flag - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects + for(int s = 0; s < sensors.size(); s++) { //Iterate over all sensors objects sensors[s]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all sensors } - for(int t = 0; t < numTalons; t++) { //Iterate over all talon objects - talonsToTest[t]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all talons + for(int t = 0; t < talons.size(); t++) { //Iterate over all talon objects + talons[t]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all talons } return 0; //DEBUG! +} + +bool loadConfiguration() { + bool configLoaded = false; + std::string configStr = fileSys.readFromSD("config.json").c_str(); + + if (!configStr.empty()) { + Serial.println("Loading configuration from SD card..."); + configLoaded = configManager.setConfiguration(configStr); + } + + if (!configLoaded) { + Serial.println("Loading default configuration..."); + std::string defaultConfig = configManager.getDefaultConfigurationJson(); + configLoaded = configManager.setConfiguration(defaultConfig); + fileSys.clearFileFromSD("config.json"); + fileSys.writeToSD(defaultConfig.c_str(), "config.json"); + } + + // Set global variables from configuration + logPeriod = configManager.getLogPeriod(); + backhaulCount = configManager.getBackhaulCount(); + desiredPowerSaveMode = configManager.getPowerSaveMode(); + loggingMode = configManager.getLoggingMode(); + + return configLoaded; +} + +void initializeSensorSystem() { + // Create SDI12 adapter if needed + auto& sdi12Talons = sensorManager.getSDI12Talons(); + if (!sdi12Talons.empty() && realSdi12 == nullptr) { + realSdi12 = new SDI12TalonAdapter(*sdi12Talons[0]); + } + + // Initialize sensors + if (realSdi12 != nullptr) { + sensorManager.initializeSensors(realTimeProvider, *realSdi12); + } else { + SDI12Talon dummyTalon(0, 0x14); + SDI12TalonAdapter dummyAdapter(dummyTalon); + sensorManager.initializeSensors(realTimeProvider, dummyAdapter); + } + + updateSensorVectors(); +} + +void updateSensorVectors() { + sensors.clear(); + talons.clear(); + + // Add core sensors + sensors.push_back(&fileSys); + sensors.push_back(&battery); + sensors.push_back(&logger); + + // Get vectors from sensor manager + talons = sensorManager.getAllTalons(); + for (auto* talon : talons) { + sensors.push_back(talon); + } + + auto configuredSensors = sensorManager.getAllSensors(); + for (auto* sensor : configuredSensors) { + sensors.push_back(sensor); + } } \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.cpp b/src/configuration/ConfigurationManager.cpp index e24518e..c6ed5ea 100644 --- a/src/configuration/ConfigurationManager.cpp +++ b/src/configuration/ConfigurationManager.cpp @@ -156,4 +156,49 @@ } return -1; // No matching bracket found - } \ No newline at end of file + } + + // ConfigurationManager.cpp - Updated factory methods +std::unique_ptr ConfigurationManager::createAuxTalon() { + return std::make_unique(0, 0x14); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createI2CTalon() { + return std::make_unique(0, 0x21); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createSDI12Talon() { + return std::make_unique(0, 0x14); // Default port and hardware version +} + +std::unique_ptr ConfigurationManager::createHaarSensor() { + return std::make_unique(0, 0, 0x20); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createO2Sensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createSolarSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createSoilSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createCO2Sensor() { + return std::make_unique(0, 0, 0x10); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createHumiditySensor() { + return std::make_unique(0, 0, 0x00); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createETSensor(ITimeProvider& timeProvider, ISDI12Talon& talon) { + return std::make_unique(timeProvider, talon, 0, 0); // Default ports and version +} + +std::unique_ptr ConfigurationManager::createPressureSensor(SDI12Talon& talon) { + return std::make_unique(talon, 0, 0x00); // Default ports and version +} \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 52b3f7d..4d86fbb 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -5,21 +5,17 @@ #include "IConfiguration.h" #include #include - -// Forward declarations for sensor types -class Sensor; -class Talon; -class AuxTalon; -class I2CTalon; -class SDI12Talon; -class Haar; -class SO421; -class SP421; -class TDR315H; -class Hedorah; -class T9602; -class LI710; -class BaroVue10; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include class ConfigurationManager : public IConfiguration { public: @@ -29,6 +25,28 @@ class ConfigurationManager : public IConfiguration { // IConfiguration implementation bool setConfiguration(std::string config) override; std::string getConfiguration() override; + std::string getDefaultConfigurationJson() const { + return "{\"config\":{" + "\"system\":{" + "\"logPeriod\":300," + "\"backhaulCount\":4," + "\"powerSaveMode\":1," + "\"loggingMode\":0," + "\"numAuxTalons\":1," + "\"numI2CTalons\":1," + "\"numSDI12Talons\":1" + "}," + "\"sensors\":{" + "\"numET\":0," + "\"numHaar\":0," + "\"numSoil\":3," + "\"numApogeeSolar\":0," + "\"numCO2\":0," + "\"numO2\":0," + "\"numPressure\":0" + "}" + "}}"; + } int updateSystemConfigurationUid() override; int updateSensorConfigurationUid() override; diff --git a/src/configuration/SensorManager.cpp b/src/configuration/SensorManager.cpp index e2ab94d..6dfb2a9 100644 --- a/src/configuration/SensorManager.cpp +++ b/src/configuration/SensorManager.cpp @@ -1,17 +1,6 @@ // SensorManager.cpp - Implementation #include "SensorManager.h" #include "ConfigurationManager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include SensorManager::SensorManager(ConfigurationManager& configManager) : configManager(configManager) { @@ -146,49 +135,4 @@ int SensorManager::getTotalSensorCount() const { haarSensors.size() + apogeeO2Sensors.size() + apogeeSolarSensors.size() + soilSensors.size() + gasSensors.size() + humiditySensors.size() + etSensors.size() + pressureSensors.size(); -} - -// ConfigurationManager.cpp - Updated factory methods -std::unique_ptr ConfigurationManager::createAuxTalon() { - return std::make_unique(0, 0x14); // Default port and hardware version -} - -std::unique_ptr ConfigurationManager::createI2CTalon() { - return std::make_unique(0, 0x21); // Default port and hardware version -} - -std::unique_ptr ConfigurationManager::createSDI12Talon() { - return std::make_unique(0, 0x14); // Default port and hardware version -} - -std::unique_ptr ConfigurationManager::createHaarSensor() { - return std::make_unique(0, 0, 0x20); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createO2Sensor(SDI12Talon& talon) { - return std::make_unique(talon, 0, 0); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createSolarSensor(SDI12Talon& talon) { - return std::make_unique(talon, 0, 0); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createSoilSensor(SDI12Talon& talon) { - return std::make_unique(talon, 0, 0); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createCO2Sensor() { - return std::make_unique(0, 0, 0x10); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createHumiditySensor() { - return std::make_unique(0, 0, 0x00); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createETSensor(ITimeProvider& timeProvider, ISDI12Talon& talon) { - return std::make_unique(timeProvider, talon, 0, 0); // Default ports and version -} - -std::unique_ptr ConfigurationManager::createPressureSensor(SDI12Talon& talon) { - return std::make_unique(talon, 0, 0x00); // Default ports and version } \ No newline at end of file diff --git a/test/sample_configurations/full_config.json b/test/sample_configurations/full_config.json deleted file mode 100644 index 0be9f5d..0000000 --- a/test/sample_configurations/full_config.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "config": { - "system": { - "logPeriod": 300, - "backhaulCount": 4, - "powerSaveMode": 1, - "loggingMode": 2 - }, - "sensors": [ - { - "type": "FileSystem", - "enabled": true - }, - { - "type": "Aux", - "enabled": true - }, - { - "type": "I2C", - "enabled": true - }, - { - "type": "SDI12", - "enabled": true - }, - { - "type": "Battery", - "enabled": true - }, - { - "type": "Logger", - "enabled": true - }, - { - "type": "Haar", - "enabled": true, - "port": 1, - "talonPort": 1, - "version": "0x20" - }, - { - "type": "Hedorah", - "enabled": true, - "port": 2, - "talonPort": 1, - "version": "0x10" - }, - { - "type": "LI710", - "enabled": true, - "port": 1, - "talonPort": 3 - }, - { - "type": "TDR315H", - "enabled": true, - "port": 2, - "talonPort": 3 - }, - { - "type": "TDR315H", - "enabled": true, - "port": 3, - "talonPort": 3 - }, - { - "type": "SO421", - "enabled": true, - "port": 1, - "talonPort": 4 - }, - { - "type": "SP421", - "enabled": true, - "port": 2, - "talonPort": 4 - }, - { - "type": "ATMOS22", - "enabled": true, - "port": 3, - "talonPort": 4 - } - ] - } -} \ No newline at end of file diff --git a/test/sample_configurations/minimal_config.json b/test/sample_configurations/minimal_config.json deleted file mode 100644 index 4d212ae..0000000 --- a/test/sample_configurations/minimal_config.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "config": { - "system": { - "logPeriod": 600, - "backhaulCount": 2, - "powerSaveMode": 2, - "loggingMode": 1 - }, - "sensors": [ - { - "type": "FileSystem", - "enabled": true - }, - { - "type": "Aux", - "enabled": true - }, - { - "type": "I2C", - "enabled": true - }, - { - "type": "SDI12", - "enabled": true - }, - { - "type": "Battery", - "enabled": true - }, - { - "type": "Logger", - "enabled": true - }, - { - "type": "Haar", - "enabled": true, - "port": 1, - "talonPort": 1, - "version": "0x20" - } - ] - } -} \ No newline at end of file diff --git a/test/sample_configurations/standard_config.json b/test/sample_configurations/standard_config.json deleted file mode 100644 index 730b33a..0000000 --- a/test/sample_configurations/standard_config.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "config": { - "system": { - "logPeriod": 300, - "backhaulCount": 4, - "powerSaveMode": 1, - "loggingMode": 0 - }, - "sensors": [ - { - "type": "FileSystem", - "enabled": true - }, - { - "type": "Aux", - "enabled": true - }, - { - "type": "I2C", - "enabled": true - }, - { - "type": "SDI12", - "enabled": true - }, - { - "type": "Battery", - "enabled": true - }, - { - "type": "Logger", - "enabled": true - }, - { - "type": "Haar", - "enabled": true, - "port": 1, - "talonPort": 1, - "version": "0x20" - }, - { - "type": "LI710", - "enabled": true, - "port": 1, - "talonPort": 3 - }, - { - "type": "TDR315H", - "enabled": true, - "port": 2, - "talonPort": 3 - } - ] - } -} \ No newline at end of file diff --git a/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp new file mode 100644 index 0000000..14b4b11 --- /dev/null +++ b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp @@ -0,0 +1,6 @@ +#include +#include + +#include "ConfigurationManager.h" + +#include "MockSensor.h" diff --git a/test/unit/SensorManager/SensorManagerTest.cpp b/test/unit/SensorManager/SensorManagerTest.cpp new file mode 100644 index 0000000..e69de29 From c7f415199a5af29353b8017cc2e7da7fa234717a Mon Sep 17 00:00:00 2001 From: zradlicz Date: Wed, 21 May 2025 00:24:37 -0500 Subject: [PATCH 14/25] added cmake stuff --- test/CMakeLists.txt | 8 + .../ConfigurationManagerTest.cpp | 240 +++++++++++++++++- test/unit/SensorManager/SensorManagerTest.cpp | 187 ++++++++++++++ 3 files changed, 433 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 061bba2..b36e16e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,6 +51,14 @@ add_executable(unit_tests # Li710 tests unit/Driver_-_Li710/Li710Test.cpp ${CMAKE_SOURCE_DIR}/lib/Driver_-_Li710/src/Li710.cpp + + # ConfigurationManger tests + unit/ConfigurationManager/ConfigurationManagerTest.cpp + ${CMAKE_SOURCE_DIR}/src/configuration/ConfigurationManager.cpp + + # SensorManager Tests + unit/SensorManager/SensorManagerTest.cpp + ${CMAKE_SOURCE_DIR}/src/configuration/SensorManager.cpp ) # Link against mocks and GoogleTest diff --git a/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp index 14b4b11..61fc6a4 100644 --- a/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp +++ b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp @@ -1,6 +1,242 @@ #include #include - #include "ConfigurationManager.h" -#include "MockSensor.h" +class ConfigurationManagerTest : public ::testing::Test { +protected: + ConfigurationManager configManager; + + void SetUp() override { + // Reset to known state before each test + } +}; + +// Test default configuration +TEST_F(ConfigurationManagerTest, DefaultConfiguration) { + // Get default configuration + std::string defaultConfig = configManager.getDefaultConfigurationJson(); + + // Apply default configuration + EXPECT_TRUE(configManager.setConfiguration(defaultConfig)); + + // Check default values + EXPECT_EQ(configManager.getLogPeriod(), 300); // Default log period + EXPECT_EQ(configManager.getBackhaulCount(), 4); // Default backhaul count + EXPECT_EQ(configManager.getPowerSaveMode(), 1); // Default power save mode + EXPECT_EQ(configManager.getLoggingMode(), 0); // Default logging mode + + // Check default talon counts + EXPECT_EQ(configManager.getNumAuxTalons(), 1); + EXPECT_EQ(configManager.getNumI2CTalons(), 1); + EXPECT_EQ(configManager.getNumSDI12Talons(), 1); + + // Check default sensor counts + EXPECT_EQ(configManager.getNumET(), 0); + EXPECT_EQ(configManager.getNumHaar(), 0); + EXPECT_EQ(configManager.getNumSoil(), 3); + EXPECT_EQ(configManager.getNumApogeeSolar(), 0); + EXPECT_EQ(configManager.getNumCO2(), 0); + EXPECT_EQ(configManager.getNumO2(), 0); + EXPECT_EQ(configManager.getNumPressure(), 0); +} + +// Test custom configuration +TEST_F(ConfigurationManagerTest, CustomConfiguration) { + // Create custom configuration + std::string customConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":600," + "\"backhaulCount\":10," + "\"powerSaveMode\":2," + "\"loggingMode\":1," + "\"numAuxTalons\":2," + "\"numI2CTalons\":2," + "\"numSDI12Talons\":2" + "}," + "\"sensors\":{" + "\"numET\":1," + "\"numHaar\":2," + "\"numSoil\":4," + "\"numApogeeSolar\":1," + "\"numCO2\":1," + "\"numO2\":1," + "\"numPressure\":1" + "}" + "}}"; + + // Apply custom configuration + EXPECT_TRUE(configManager.setConfiguration(customConfig)); + + // Check custom values + EXPECT_EQ(configManager.getLogPeriod(), 600); + EXPECT_EQ(configManager.getBackhaulCount(), 10); + EXPECT_EQ(configManager.getPowerSaveMode(), 2); + EXPECT_EQ(configManager.getLoggingMode(), 1); + + // Check custom talon counts + EXPECT_EQ(configManager.getNumAuxTalons(), 2); + EXPECT_EQ(configManager.getNumI2CTalons(), 2); + EXPECT_EQ(configManager.getNumSDI12Talons(), 2); + + // Check custom sensor counts + EXPECT_EQ(configManager.getNumET(), 1); + EXPECT_EQ(configManager.getNumHaar(), 2); + EXPECT_EQ(configManager.getNumSoil(), 4); + EXPECT_EQ(configManager.getNumApogeeSolar(), 1); + EXPECT_EQ(configManager.getNumCO2(), 1); + EXPECT_EQ(configManager.getNumO2(), 1); + EXPECT_EQ(configManager.getNumPressure(), 1); +} + +// Test invalid configuration handling +TEST_F(ConfigurationManagerTest, InvalidConfiguration) { + // Set default configuration first to have known state + configManager.setConfiguration(configManager.getDefaultConfigurationJson()); + + // Try invalid configuration + std::string invalidConfig = "{\"not_config\":{}}"; + EXPECT_TRUE(configManager.setConfiguration(invalidConfig)); + + // Values should remain unchanged + EXPECT_EQ(configManager.getLogPeriod(), 300); + EXPECT_EQ(configManager.getBackhaulCount(), 4); +} + +// Test partial configuration +TEST_F(ConfigurationManagerTest, PartialConfiguration) { + // Apply default configuration + configManager.setConfiguration(configManager.getDefaultConfigurationJson()); + + // Apply partial configuration (only modifying system) + std::string partialConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":900," + "\"backhaulCount\":8" + "}" + "}}"; + + EXPECT_TRUE(configManager.setConfiguration(partialConfig)); + + // Check that specified values changed + EXPECT_EQ(configManager.getLogPeriod(), 900); + EXPECT_EQ(configManager.getBackhaulCount(), 8); + + // Check that unspecified values remain at defaults + EXPECT_EQ(configManager.getPowerSaveMode(), 1); + EXPECT_EQ(configManager.getLoggingMode(), 0); + EXPECT_EQ(configManager.getNumSoil(), 3); +} + +// Test configuration UID updates +TEST_F(ConfigurationManagerTest, ConfigurationUIDs) { + // Apply default configuration + configManager.setConfiguration(configManager.getDefaultConfigurationJson()); + + // Get initial UIDs + int initialSystemUID = configManager.updateSystemConfigurationUid(); + int initialSensorUID = configManager.updateSensorConfigurationUid(); + + // Modify system configuration + std::string systemConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":1200" + "}" + "}}"; + + configManager.setConfiguration(systemConfig); + + // System UID should change, sensor UID should remain the same + int newSystemUID = configManager.updateSystemConfigurationUid(); + int newSensorUID = configManager.updateSensorConfigurationUid(); + + EXPECT_NE(initialSystemUID, newSystemUID); + EXPECT_EQ(initialSensorUID, newSensorUID); + + // Modify sensor configuration + std::string sensorConfig = + "{\"config\":{" + "\"sensors\":{" + "\"numSoil\":5" + "}" + "}}"; + + configManager.setConfiguration(sensorConfig); + + // Sensor UID should change + int finalSensorUID = configManager.updateSensorConfigurationUid(); + EXPECT_NE(newSensorUID, finalSensorUID); +} + +// Test configuration serialization +TEST_F(ConfigurationManagerTest, ConfigurationSerialization) { + // Apply custom configuration + std::string customConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":600," + "\"backhaulCount\":10," + "\"powerSaveMode\":2," + "\"loggingMode\":1" + "}" + "}}"; + + configManager.setConfiguration(customConfig); + + // Get serialized configuration + std::string serialized = configManager.getConfiguration(); + + // Parse it back + ConfigurationManager newManager; + newManager.setConfiguration(serialized); + + // Values should match + EXPECT_EQ(newManager.getLogPeriod(), 600); + EXPECT_EQ(newManager.getBackhaulCount(), 10); + EXPECT_EQ(newManager.getPowerSaveMode(), 2); + EXPECT_EQ(newManager.getLoggingMode(), 1); +} + +// Test Factory Methods +TEST_F(ConfigurationManagerTest, FactoryMethods) { + // Test that factory methods create non-null objects + auto auxTalon = ConfigurationManager::createAuxTalon(); + EXPECT_NE(auxTalon, nullptr); + + auto i2cTalon = ConfigurationManager::createI2CTalon(); + EXPECT_NE(i2cTalon, nullptr); + + auto sdi12Talon = ConfigurationManager::createSDI12Talon(); + EXPECT_NE(sdi12Talon, nullptr); + + auto haarSensor = ConfigurationManager::createHaarSensor(); + EXPECT_NE(haarSensor, nullptr); + + // For sensors that need talons, we need to pass in real talons + auto sdi12TalonObj = ConfigurationManager::createSDI12Talon(); + + auto o2Sensor = ConfigurationManager::createO2Sensor(*sdi12TalonObj); + EXPECT_NE(o2Sensor, nullptr); + + auto solarSensor = ConfigurationManager::createSolarSensor(*sdi12TalonObj); + EXPECT_NE(solarSensor, nullptr); + + auto soilSensor = ConfigurationManager::createSoilSensor(*sdi12TalonObj); + EXPECT_NE(soilSensor, nullptr); + + auto co2Sensor = ConfigurationManager::createCO2Sensor(); + EXPECT_NE(co2Sensor, nullptr); + + auto humiditySensor = ConfigurationManager::createHumiditySensor(); + EXPECT_NE(humiditySensor, nullptr); + + // For ET sensor, we need real time provider and SDI12 talon + // This would require mocking in a real test + // auto etSensor = ConfigurationManager::createETSensor(timeProvider, sdi12Interface); + // EXPECT_NE(etSensor, nullptr); + + auto pressureSensor = ConfigurationManager::createPressureSensor(*sdi12TalonObj); + EXPECT_NE(pressureSensor, nullptr); +} \ No newline at end of file diff --git a/test/unit/SensorManager/SensorManagerTest.cpp b/test/unit/SensorManager/SensorManagerTest.cpp index e69de29..5eca19e 100644 --- a/test/unit/SensorManager/SensorManagerTest.cpp +++ b/test/unit/SensorManager/SensorManagerTest.cpp @@ -0,0 +1,187 @@ +#include +#include +#include "SensorManager.h" +#include "ConfigurationManager.h" +#include "MockTimeProvider.h" +#include "MockSDI12Talon.h" + +class SensorManagerTest : public ::testing::Test { +protected: + ConfigurationManager configManager; + SensorManager sensorManager{configManager}; + MockTimeProvider mockTimeProvider; + MockSDI12Talon mockSDI12Talon; + + void SetUp() override { + // Apply a known configuration + std::string testConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":300," + "\"backhaulCount\":4," + "\"powerSaveMode\":1," + "\"loggingMode\":0," + "\"numAuxTalons\":1," + "\"numI2CTalons\":1," + "\"numSDI12Talons\":1" + "}," + "\"sensors\":{" + "\"numET\":1," + "\"numHaar\":1," + "\"numSoil\":2," + "\"numApogeeSolar\":1," + "\"numCO2\":1," + "\"numO2\":1," + "\"numPressure\":1" + "}" + "}}"; + + configManager.setConfiguration(testConfig); + } +}; + +// Test initialization of sensors based on configuration +TEST_F(SensorManagerTest, InitializeSensors) { + // Initialize sensors + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Check that talons were created + EXPECT_EQ(sensorManager.getAuxTalons().size(), 1); + EXPECT_EQ(sensorManager.getI2CTalons().size(), 1); + EXPECT_EQ(sensorManager.getSDI12Talons().size(), 1); + + // Check that sensors were created + auto allSensors = sensorManager.getAllSensors(); + // Expected: 1 ET + 1 Haar + 2 Soil + 1 Solar + 1 CO2 + 1 O2 + 1 Pressure = 8 + EXPECT_EQ(allSensors.size(), 8); + + // Check total sensor count (including 3 core sensors) + EXPECT_EQ(sensorManager.getTotalSensorCount(), 11); +} + +// Test clearing all sensors +TEST_F(SensorManagerTest, ClearAllSensors) { + // Initialize sensors + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Clear all sensors + sensorManager.clearAllSensors(); + + // Check that all collections are empty + EXPECT_EQ(sensorManager.getAuxTalons().size(), 0); + EXPECT_EQ(sensorManager.getI2CTalons().size(), 0); + EXPECT_EQ(sensorManager.getSDI12Talons().size(), 0); + EXPECT_EQ(sensorManager.getAllSensors().size(), 0); + EXPECT_EQ(sensorManager.getTotalSensorCount(), 3); // Core sensors still counted +} + +// Test getting all talons +TEST_F(SensorManagerTest, GetAllTalons) { + // Initialize sensors + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Get all talons + auto allTalons = sensorManager.getAllTalons(); + + // Expected: 1 AuxTalon + 1 I2CTalon + 1 SDI12Talon = 3 + EXPECT_EQ(allTalons.size(), 3); + + // Check that all talons are non-null + for (auto talon : allTalons) { + EXPECT_NE(talon, nullptr); + } +} + +// Test handling of configuration changes +TEST_F(SensorManagerTest, ConfigurationChanges) { + // Initialize with current configuration + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Check initial state + EXPECT_EQ(sensorManager.getAuxTalons().size(), 1); + EXPECT_EQ(sensorManager.getAllSensors().size(), 8); + + // Change configuration + std::string newConfig = + "{\"config\":{" + "\"system\":{" + "\"logPeriod\":300," + "\"backhaulCount\":4," + "\"powerSaveMode\":1," + "\"loggingMode\":0," + "\"numAuxTalons\":2," + "\"numI2CTalons\":1," + "\"numSDI12Talons\":1" + "}," + "\"sensors\":{" + "\"numET\":0," + "\"numHaar\":0," + "\"numSoil\":5," + "\"numApogeeSolar\":0," + "\"numCO2\":0," + "\"numO2\":0," + "\"numPressure\":0" + "}" + "}}"; + + configManager.setConfiguration(newConfig); + + // Re-initialize + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Check that counts have changed + EXPECT_EQ(sensorManager.getAuxTalons().size(), 2); + EXPECT_EQ(sensorManager.getAllSensors().size(), 5); // Only soil sensors now +} + +// Test sensor and talon relationship +TEST_F(SensorManagerTest, SensorTalonRelationship) { + // Initialize with a minimal configuration + std::string minConfig = + "{\"config\":{" + "\"system\":{" + "\"numAuxTalons\":0," + "\"numI2CTalons\":0," + "\"numSDI12Talons\":1" + "}," + "\"sensors\":{" + "\"numET\":0," + "\"numHaar\":0," + "\"numSoil\":1," + "\"numApogeeSolar\":0," + "\"numCO2\":0," + "\"numO2\":0," + "\"numPressure\":0" + "}" + "}}"; + + configManager.setConfiguration(minConfig); + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Check that we have one SDI12 talon and one soil sensor + EXPECT_EQ(sensorManager.getSDI12Talons().size(), 1); + EXPECT_EQ(sensorManager.getAllSensors().size(), 1); + + // The soil sensor should be created using the first SDI12 talon + // This relationship is maintained internally and can't be easily tested + // without additional modifications to the code to expose this relationship +} + +// Test handling of empty configuration +TEST_F(SensorManagerTest, EmptyConfiguration) { + // Apply an empty configuration (only system structure, no values) + std::string emptyConfig = "{\"config\":{\"system\":{},\"sensors\":{}}}"; + configManager.setConfiguration(emptyConfig); + + // Initialize sensors + sensorManager.initializeSensors(mockTimeProvider, mockSDI12Talon); + + // Should fall back to defaults + EXPECT_EQ(sensorManager.getAuxTalons().size(), 1); + EXPECT_EQ(sensorManager.getI2CTalons().size(), 1); + EXPECT_EQ(sensorManager.getSDI12Talons().size(), 1); + + // Default sensor counts from ConfigurationManager + auto allSensors = sensorManager.getAllSensors(); + EXPECT_EQ(allSensors.size(), 3); // Default 3 soil sensors +} \ No newline at end of file From 61afa4c0d6764431db8c04c5828508c461283659 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 21 May 2025 16:46:57 -0500 Subject: [PATCH 15/25] some hard faults still occuring with new sensor and talon vectors --- lib/Driver_-_Kestrel-FileHandler | 2 +- src/FlightControl_Demo.cpp | 123 +++++++++++++----- src/configuration/ConfigurationManager.h | 1 + .../ConfigurationManagerTest.cpp | 2 +- test/unit/SensorManager/SensorManagerTest.cpp | 2 +- 5 files changed, 91 insertions(+), 39 deletions(-) diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index 88c9252..5d16fe1 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit 88c925252ddd79a923e30db4611c610d26129f9b +Subproject commit 5d16fe1d3473f0f21170b30649b921087f1a0b5e diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 060ed8b..afaaaf7 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -96,6 +96,8 @@ const char* const CoreSensorTypes[] = { #include "configuration/ConfigurationManager.h" #include "configuration/SensorManager.h" +int getIndexOfPort(int port); + const String firmwareVersion = "2.9.11"; const String schemaVersion = "2.2.9"; @@ -247,6 +249,8 @@ void setup() { bool hasError = false; // logger.begin(Time.now(), hasCriticalError, hasError); //Needs to be called the first time with Particle time since I2C not yet initialized logger.begin(0, hasCriticalError, hasError); //Called with 0 since time collection system has not been initialized + Serial.println("Critial error: " + String(hasCriticalError)); //DEBUG! + Serial.println("Error: " + String(hasError)); //DEBUG! logger.setIndicatorState(IndicatorLight::ALL,IndicatorMode::INIT); bool batState = logger.testForBat(); //Check if a battery is connected logger.enableI2C_OB(false); @@ -255,6 +259,8 @@ void setup() { if(batState) battery.setIndicatorState(GonkIndicatorMode::SOLID); //Turn on charge indication LEDs during setup else battery.setIndicatorState(GonkIndicatorMode::BLINKING); //If battery not switched on, set to blinking fileSys.begin(0, hasCriticalError, hasError); //Initialzie, but do not attempt backhaul + Serial.println("Critial error: " + String(hasCriticalError)); //DEBUG! + Serial.println("Error: " + String(hasError)); //DEBUG! if(hasCriticalError) { Serial.println(getErrorString()); //Report current error codes logger.setIndicatorState(IndicatorLight::STAT,IndicatorMode::ERROR); //Display error state if critical error is reported @@ -294,10 +300,14 @@ void setup() { waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds //load configuration from SD card, default config if not found or not possible - loadConfiguration(); + Serial.println("Loading configuration..."); //DEBUG! + loadConfiguration(); + Serial.println("Configuration loaded"); //DEBUG! //initilize all sensors + Serial.println("Initializing sensors..."); //DEBUG! initializeSensorSystem(); + Serial.println("Sensors initialized"); //DEBUG! // Apply power save mode configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) @@ -697,12 +707,14 @@ String getDataString() bool dummy1; bool dummy2; - if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) { //DEBUG! REPALCE! + int currentTalonIndex = getIndexOfPort(sensors[i]->getTalonPort()); //Find the talon associated with this sensor + + if((sensors[i]->getTalonPort() > 0) && (currentTalonIndex >= 0)) { //DEBUG! REPALCE! Serial.print("TALON CALL: "); //DEBUG! Serial.println(sensors[i]->getTalonPort()); logger.configTalonSense(); //Setup to allow for current testing // talons[sensors[i]->getTalonPort() - 1]->begin(logger.getTime(), dummy1, dummy2); //DEBUG! Do only if talon is associated with sensor, and object exists - talons[sensors[i]->getTalonPort() - 1]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists + talons[currentTalonIndex]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists // logger.enableI2C_OB(false); //Return to isolation mode // logger.enableI2C_Global(true); } @@ -710,13 +722,15 @@ String getDataString() Serial.print("Device "); //DEBUG! Serial.print(i); Serial.println(" is a sensor"); - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data ports to start for the given Talon - // talons[sensors[i]->getTalonPort() - 1]->disablePowerAll(); //Turn off all power ports to start for the given Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn on data for the given port on the Talon - // bool dummy1; - // bool dummy2; - // sensors[i]->begin(Time.now(), dummy1, dummy2); //DEBUG! + if(currentTalonIndex >= 0) { //DEBUG! REPALCE! + talons[currentTalonIndex]->disableDataAll(); //Turn off all data ports to start for the given Talon + // talons[sensors[i]->getTalonPort() - 1]->disablePowerAll(); //Turn off all power ports to start for the given Talon + // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), true); //Turn on data for the given port on the Talon + // bool dummy1; + // bool dummy2; + // sensors[i]->begin(Time.now(), dummy1, dummy2); //DEBUG! + } } // delay(100); //DEBUG! logger.enableI2C_OB(false); @@ -748,8 +762,8 @@ String getDataString() Serial.println(output); //DEBUG! // data = data + sensors[i]->getData(logger.getTime()); //DEBUG! REPLACE! // if(i + 1 < sensors.size()) data = data + ","; //Only append if not last entry - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon + if((sensors[i]->getSensorPort() > 0) && (sensors[i]->getTalonPort() > 0) && (currentTalonIndex >= 0)) { + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), false); //Turn off power for the given port on the Talon //DEBUG! } } @@ -777,10 +791,13 @@ String getDiagnosticString(uint8_t level) logger.enableI2C_OB(false); logger.enableI2C_Global(true); // if(!sensors[i]->isTalon()) { //If sensor is not Talon - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon + + int currentTalonIndex = getIndexOfPort(sensors[i]->getTalonPort()); //Find the talon associated with this sensor + + if((sensors[i]->getSensorPort() > 0) && (sensors[i]->getTalonPort() > 0) && (currentTalonIndex >= 0)) { //If a Talon is associated with the sensor, turn that port on + talons[currentTalonIndex]->disableDataAll(); //Turn off all data on Talon // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used } @@ -798,8 +815,8 @@ String getDiagnosticString(uint8_t level) } } - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon + if((sensors[i]->getSensorPort() > 0) && (sensors[i]->getTalonPort() > 0) && (currentTalonIndex >= 0)) { + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), false); //Turn off power for the given port on the Talon //DEBUG! } @@ -838,9 +855,12 @@ String getMetadataString() logger.disableDataAll(); //Turn off data to all ports, then just enable those needed if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on data to required Talon port only if not core and port is valid // if(!sensors[i]->isTalon()) { //If sensor is not Talon - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used + + int currentTalonIndex = getIndexOfPort(sensors[i]->getTalonPort()); //Find the talon associated with this sensor + + if((sensors[i]->getSensorPort() > 0) && (sensors[i]->getTalonPort() > 0) && (currentTalonIndex >= 0)) { //If a Talon is associated with the sensor, turn that port on + talons[currentTalonIndex]->disableDataAll(); //Turn off all data on Talon + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used } // logger.enablePower(sensors[i]->getTalon(), true); //Turn on power to port // logger.enableData(sensors[i]->getTalon(), true); //Turn on data to port @@ -899,12 +919,24 @@ String initSensors() // if(!sensors[i]->isTalon()) { //If sensor is not Talon logger.configTalonSense(); //Setup to allow for current testing // if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) talons[sensors[i]->getTalonPort() - 1]->begin(logger.getTime(), dummy1, dummy2); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! - if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) talons[sensors[i]->getTalonPort() - 1]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used - + + Serial.print("iteration number: "); + Serial.println(i); + int currentTalonIndex = getIndexOfPort(sensors[i]->getTalonPort()); + Serial.println("created talonOfSensor"); + if(currentTalonIndex >= 0) + { + if(sensors[i]->getTalonPort() > 0) { //DEBUG! REPLACE! + Serial.println("restart talonOfSensor"); + talons[currentTalonIndex]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! + } + if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on + Serial.println("disableDataAll of talonOfSensor"); + talons[currentTalonIndex]->disableDataAll(); //Turn off all data on Talon + // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon + talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used + Serial.println("enableData of talonOfSensor"); + } } if(sensors[i]->getTalonPort() == 0 && sensors[i]->sensorInterface != BusType::CORE) { missingSensor = true; //Set flag if any sensors not assigned to Talon and not a core sensor @@ -1072,7 +1104,8 @@ int sleepSensors() Serial.print(s + 1); Serial.print(","); Serial.println(sensors[s]->getTalonPort()); - talons[sensors[s]->getTalonPort() - 1]->enablePower(sensors[s]->getSensorPort(), false); //Turn off power for any sensor which does not need to be kept powered + int currentTalonIndex = getIndexOfPort(sensors[s]->getTalonPort()); + talons[currentTalonIndex]->enablePower(sensors[s]->getSensorPort(), false); //Turn off power for any sensor which does not need to be kept powered } else if(sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < talons.size()){ //If sensor has a position and is not core, but keepPowered is true, run sleep routine Serial.print("Sleep Sensor "); //DEBUG! @@ -1091,7 +1124,7 @@ int sleepSensors() } } - for(int t = 0; t < Kestrel::numTalonPorts; t++) { //Iterate over all talon objects + for(int t = 0; t < talons.size(); t++) { //Iterate over all talon objects if(talons[t] && talons[t]->keepPowered == false) { //If NO sensors on a given Talon require it to be kept powered, shut the whole thing down Serial.print("Power Down Talon "); //DEBUG! Serial.println(talons[t]->getTalonPort()); @@ -1177,6 +1210,9 @@ int detectTalons(String dummyStr) Serial.println(talons[t]->getTalonPort()); break; //Exit the interation after the first one tests positive } + else { + Serial.println("Talon not present"); //DEBUG! + } } } logger.enableData(port, false); //Turn port back off @@ -1186,7 +1222,7 @@ int detectTalons(String dummyStr) // talons[i2c.getTalonPort() - 1] = &i2c; bool dummy; bool dummy1; - for(int i = 0; i < Kestrel::numTalonPorts - 1; i++) { + for(int i = 0; i < talons.size(); i++) { if(talons[i] && talons[i]->getTalonPort() > 0) { Serial.print("BEGIN TALON: "); //DEBUG! Serial.print(talons[i]->getTalonPort()); @@ -1206,29 +1242,30 @@ int detectTalons(String dummyStr) } logger.configTalonSense(); //Setup to allow for current testing - // Serial.println("TALON SENSE CONFIG DONE"); //DEBUG! + //Serial.println("TALON SENSE CONFIG DONE"); //DEBUG! // Serial.flush(); //DEBUG! // logger.enableI2C_Global(true); // logger.enableI2C_OB(false); // talons[i]->begin(Time.now(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //DEBUG! talons[i]->begin(logger.getTime(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! // talons[i]->begin(0, dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! - // Serial.println("TALON BEGIN DONE"); //DEBUG! - // Serial.flush(); //DEBUG! - // delay(10000); //DEBUG! + Serial.println("TALON BEGIN DONE"); //DEBUG! + //Serial.flush(); //DEBUG! + //delay(10000); //DEBUG! logger.enableData(i + 1, false); //Turn data back off to prevent conflict - // Serial.println("ENABLE DATA DONE"); //DEBUG! + //Serial.println("ENABLE DATA DONE"); //DEBUG! // Serial.flush(); //DEBUG! - // delay(10000); //DEBUG! + //delay(10000); //DEBUG! } } + //Serial.println("TALON DETECTION DONE"); //DEBUG! return 0; //DEBUG! } int detectSensors(String dummyStr) { /////////////// SENSOR AUTO DETECTION ////////////////////// - for(int t = 0; t < Kestrel::numTalonPorts; t++) { //Iterate over each Talon + for(int t = 0; t < talons.size(); t++) { //Iterate over each Talon // Serial.println(talons[t]->talonInterface); //DEBUG! // Serial.print("DETECT ON TALON: "); //DEBUG! // Serial.println(t); @@ -1487,18 +1524,32 @@ void updateSensorVectors() { talons.clear(); // Add core sensors + Serial.println("Adding core sensors"); //DEBUG! sensors.push_back(&fileSys); sensors.push_back(&battery); sensors.push_back(&logger); // Get vectors from sensor manager talons = sensorManager.getAllTalons(); + Serial.println("Adding talons"); //DEBUG! + Serial.println(talons.size()); for (auto* talon : talons) { sensors.push_back(talon); } auto configuredSensors = sensorManager.getAllSensors(); + Serial.println("Adding sensors"); //DEBUG! for (auto* sensor : configuredSensors) { sensors.push_back(sensor); } + Serial.println(sensors.size()); //DEBUG! +} + +int getIndexOfPort(int port) { + for (int i = 0; i < talons.size(); i++) { + if (talons[i]->getTalonPort() == port) { + return i; + } + } + return -1; } \ No newline at end of file diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 4d86fbb..2ee3ff1 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -25,6 +25,7 @@ class ConfigurationManager : public IConfiguration { // IConfiguration implementation bool setConfiguration(std::string config) override; std::string getConfiguration() override; + //{"config":{"system":{"logPeriod":300,"backhaulCount":4,"powerSaveMode":1,"loggingMode":0,"numAuxTalons":1,"numI2CTalons":1,"numSDI12Talons":1},"sensors":{"numET":0,"numHaar":0,"numSoil":3,"numApogeeSolar":0,"numCO2":0,"numO2":0,"numPressure":0}}} std::string getDefaultConfigurationJson() const { return "{\"config\":{" "\"system\":{" diff --git a/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp index 61fc6a4..d468193 100644 --- a/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp +++ b/test/unit/ConfigurationManager/ConfigurationManagerTest.cpp @@ -1,6 +1,6 @@ #include #include -#include "ConfigurationManager.h" +#include "configuration/ConfigurationManager.h" class ConfigurationManagerTest : public ::testing::Test { protected: diff --git a/test/unit/SensorManager/SensorManagerTest.cpp b/test/unit/SensorManager/SensorManagerTest.cpp index 5eca19e..8938577 100644 --- a/test/unit/SensorManager/SensorManagerTest.cpp +++ b/test/unit/SensorManager/SensorManagerTest.cpp @@ -1,7 +1,7 @@ #include #include #include "SensorManager.h" -#include "ConfigurationManager.h" +#include "configuration/ConfigurationManager.h" #include "MockTimeProvider.h" #include "MockSDI12Talon.h" From 6378ade9a251a58519e57e30be1a78a3dd672661 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Thu, 22 May 2025 11:24:03 -0500 Subject: [PATCH 16/25] after debugging I think it works --- src/FlightControl_Demo.cpp | 51 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index afaaaf7..82c01cb 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -700,8 +700,14 @@ String getDataString() uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended for(int i = 0; i < sensors.size(); i++) { logger.disableDataAll(); //Turn off data to all ports, then just enable those needed - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid + if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) { + logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid + Serial.println("Enabled power for Talon: " + String(sensors[i]->getTalonPort())); //DEBUG! + } + if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) { + logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid + Serial.println("Enabled data for Talon: " + String(sensors[i]->getTalonPort())); //DEBUG! + } logger.enableI2C_OB(false); logger.enableI2C_Global(true); bool dummy1; @@ -1147,7 +1153,7 @@ int wakeSensors() logger.enableI2C_OB(false); logger.disableDataAll(); //Turn off all data to start for(int p = 1; p <= Kestrel::numTalonPorts; p++) logger.enablePower(p, true); //Turn power back on to all Kestrel ports - for(int t = 0; t < Kestrel::numTalonPorts; t++) { + for(int t = 0; t < talons.size(); t++) { if(talons[t] && talons[t]->getTalonPort() != 0) { logger.enableData(talons[t]->getTalonPort(), true); //Turn on data for given port talons[t]->restart(); //Restart all Talons, this turns on all ports it can @@ -1233,12 +1239,12 @@ int detectTalons(String dummyStr) logger.setDirection(talons[i]->getTalonPort(), HIGH); //If the talon is an SDI12 interface type, set port to use serial interface } else if(talons[i]->talonInterface != BusType::CORE) logger.setDirection(talons[i]->getTalonPort(), LOW); //Otherwise set talon to use GPIO interface, unless bus type is core, in which case ignore it - logger.enablePower(i + 1, true); //Turn on specific channel - logger.enableData(i + 1, true); + logger.enablePower(talons[i]->getTalonPort(), true); //Turn on specific channel + logger.enableData(talons[i]->getTalonPort(), true); if(logger.getFault(talons[i]->getTalonPort())) { //Only toggle power if there is a fault on that Talon line - logger.enablePower(i + 1, true); //Toggle power just before testing to get result within 10ms - logger.enablePower(i + 1, false); - logger.enablePower(i + 1, true); + logger.enablePower(talons[i]->getTalonPort(), true); //Toggle power just before testing to get result within 10ms + logger.enablePower(talons[i]->getTalonPort(), false); + logger.enablePower(talons[i]->getTalonPort(), true); } logger.configTalonSense(); //Setup to allow for current testing @@ -1252,7 +1258,7 @@ int detectTalons(String dummyStr) Serial.println("TALON BEGIN DONE"); //DEBUG! //Serial.flush(); //DEBUG! //delay(10000); //DEBUG! - logger.enableData(i + 1, false); //Turn data back off to prevent conflict + logger.enableData(talons[i]->getTalonPort(), false); //Turn data back off to prevent conflict //Serial.println("ENABLE DATA DONE"); //DEBUG! // Serial.flush(); //DEBUG! //delay(10000); //DEBUG! @@ -1266,31 +1272,31 @@ int detectSensors(String dummyStr) { /////////////// SENSOR AUTO DETECTION ////////////////////// for(int t = 0; t < talons.size(); t++) { //Iterate over each Talon - // Serial.println(talons[t]->talonInterface); //DEBUG! + //Serial.println(talons[t]->talonInterface); //DEBUG! // Serial.print("DETECT ON TALON: "); //DEBUG! - // Serial.println(t); + //Serial.println(t); // Serial.flush(); - // if(talons[t]) { + //if(talons[t]) { // delay(5000); // Serial.println("TALON EXISTS"); //DEBUG! // Serial.flush(); - // } + //} // else { // delay(5000); // Serial.println("TALON NOT EXISTS"); //DEBUG! // Serial.flush(); - // } + //} // delay(5000); - // if(talons[t]->talonInterface != BusType::NONE) { + //if(talons[t]->talonInterface != BusType::NONE) { // delay(5000); - // Serial.println("TALON NOT NONE"); //DEBUG! + //Serial.println("TALON NOT NONE"); //DEBUG! // Serial.flush(); // } - // else { + //else { // delay(5000); - // Serial.println("TALON NONE"); //DEBUG! + //Serial.println("TALON NONE"); //DEBUG! // Serial.flush(); - // } + //} // delay(10000); //DEBUG! // Serial.println(talons[t]->talonInterface); //DEBUG! if(talons[t] && talons[t]->talonInterface != BusType::NONE && talons[t]->getTalonPort() != 0) { //Only proceed if Talon has a bus which can be iterated over, and the talon in question exists and has been detected @@ -1300,10 +1306,11 @@ int detectSensors(String dummyStr) talons[t]->disableDataAll(); //Turn off all data ports on Talon for(int p = 1; p <= talons[t]->getNumPorts(); p++) { //Iterate over each port on given Talon // talons[t]->enablePower(p, true); //Turn data and power on for specific channel - talons[t]->enableData(p, true); + Serial.print("Port enable success: "); //DEBUG! + Serial.println(talons[t]->enableData(p, true)); delay(10); //Wait to make sure sensor is responsive after power up command Serial.print("Testing Port: "); //DEBUG! - Serial.print(t + 1); + Serial.print(talons[t]->getTalonPort()); Serial.print(","); Serial.println(p); for(int s = 0; s < sensors.size(); s++) { //Iterate over all sensors objects @@ -1311,7 +1318,7 @@ int detectSensors(String dummyStr) Serial.print("Test Sensor: "); //DEBUG! Serial.println(s); if(sensors[s]->isPresent()) { //Test if that sensor is present, if it is, configure the port - sensors[s]->setTalonPort(t + 1); + sensors[s]->setTalonPort(talons[t]->getTalonPort()); //Set the Talon port for the sensor sensors[s]->setSensorPort(p); if(sensors[s]->keepPowered == true) talons[sensors[s]->getTalonPort() - 1]->keepPowered = true; //If any of the sensors on a Talon require power, set the flag for the Talon Serial.print("Sensor Found:\n\t"); //DEBUG! From ebda0ec77b1508f9b1d1efd7a0e3892affb5223c Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Thu, 22 May 2025 15:56:09 -0500 Subject: [PATCH 17/25] semi stable feature for sd card based configuration --- lib/Driver_-_Kestrel-FileHandler | 2 +- src/FlightControl_Demo.cpp | 39 ++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index 5d16fe1..69db8dd 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit 5d16fe1d3473f0f21170b30649b921087f1a0b5e +Subproject commit 69db8dd9cd41204927ed7d3337f2efceefc3faa3 diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 82c01cb..fc40369 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -1358,16 +1358,41 @@ int setNodeID(String nodeID) int updateConfiguration(String configJson) { Serial.println("Updating configuration..."); Serial.println(configJson); - - // Validate JSON format - if (configJson.indexOf("\"config\"") == -1) { + + //verify the passed config is valid format + if (configJson.indexOf("\"config\"") == -1) { Serial.println("Error: Invalid configuration format. Missing 'config' element."); - return -2; // Invalid format + return -1; // Invalid format } - fileSys.clearFileFromSD("config.json"); - fileSys.writeToSD(configJson.c_str(), "config.json"); + if (configJson.indexOf("\"system\"") == -1) { + Serial.println("Error: Invalid configuration format. Missing 'version' element."); + return -2; // Invalid format + } + if (configJson.indexOf("\"sensors\"") == -1) { + Serial.println("Error: Invalid configuration format. Missing 'version' element."); + return -3; // Invalid format + } + + // test write to SD card + if (!fileSys.writeToSD("", "config.json")) { + Serial.println("Error: Failed to write to SD card."); + return -4; // Failed to write config + } + + //remove current config.json + if(!fileSys.clearFileFromSD("config.json")) { + Serial.println("Error: Failed to clear current configuration from SD card."); + return -5; // Failed to remove current config + } - return loadConfiguration(); + // Write new configuration to SD card + if (!fileSys.writeToSD(configJson.c_str(), "config.json")) { + Serial.println("Error: Failed to write new configuration to SD card."); + return -6; // Failed to write new config + } + + System.reset(); //Reset the system to apply new configuration + return 1; //Success } int getSystemConfiguration(String dummy) { From 9f49fddb85cb0fadd043ad1ddbe2d6a8fae46d21 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 11:39:58 -0500 Subject: [PATCH 18/25] added configuration documentation and updated readme --- CONFIGURATION.md | 193 +++++++++++++ README.md | 257 +++++++++++++++++- lib/Driver_-_Kestrel-FileHandler | 2 +- ...ightControl_Demo.cpp => FlightControl.cpp} | 46 +++- 4 files changed, 482 insertions(+), 16 deletions(-) create mode 100644 CONFIGURATION.md rename src/{FlightControl_Demo.cpp => FlightControl.cpp} (98%) diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..b7bfe98 --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,193 @@ +### Configuration Management + +#### Loading Configuration + +Configuration is loaded at startup in the following priority order: + +1. **SD Card**: `config.json` file on the SD card +2. **Default**: Built-in default configuration if SD file not found + +#### Updating Configuration + +Configuration can be updated through: + +1. **Cloud Function**: `updateConfig` Particle function +2. **SD Card**: Replace `config.json` file and restart system + +#### Configuration UIDs + +The system generates unique identifiers for configuration tracking: + +- **System Configuration UID**: Changes when system parameters are modified +- **Sensor Configuration UID**: Changes when sensor counts are modified + +These UIDs can be retrieved via cloud functions: +- `getSystemConfig`: Returns system configuration UID +- `getSensorConfig`: Returns sensor configuration UID + +##### UID Encoding Format + +The Configuration UIDs are encoded as 32-bit integers using bit-packing to efficiently store multiple configuration parameters in a single value. + +###### System Configuration UID Encoding + +The System Configuration UID is constructed using the following bit layout: + +``` +Bits: 31-16 15-12 11-10 9-8 7-6 5-4 3-2 1-0 +Field: logPeriod backhaul powerSave logMode numAux numI2C numSDI12 reserved +``` + +| Field | Bits | Description | Range | +|-------|------|-------------|-------| +| `logPeriod` | 31-16 | Logging period in seconds | 0-65535 | +| `backhaulCount` | 15-12 | Number of logs before backhaul | 0-15 | +| `powerSaveMode` | 11-10 | Power management mode | 0-3 | +| `loggingMode` | 9-8 | Logging behavior mode | 0-3 | +| `numAuxTalons` | 7-6 | Number of Auxiliary Talons | 0-3 | +| `numI2CTalons` | 5-4 | Number of I2C Talons | 0-3 | +| `numSDI12Talons` | 3-2 | Number of SDI-12 Talons | 0-3 | +| Reserved | 1-0 | Reserved for future use | 0-3 | + +**Encoding Formula:** +```cpp +int systemUID = (logPeriod << 16) | + (backhaulCount << 12) | + (powerSaveMode << 10) | + (loggingMode << 8) | + (numAuxTalons << 6) | + (numI2CTalons << 4) | + (numSDI12Talons << 2); +``` + +###### Sensor Configuration UID Encoding + +The Sensor Configuration UID uses the following bit layout: + +``` +Bits: 31-28 27-24 23-20 19-16 15-12 11-8 7-4 3-0 +Field: numET numHaar numSoil numApogee numCO2 numO2 numPress reserved +``` + +| Field | Bits | Description | Range | +|-------|------|-------------|-------| +| `numET` | 31-28 | Number of ET sensors (LI-710) | 0-15 | +| `numHaar` | 27-24 | Number of Haar atmospheric sensors | 0-15 | +| `numSoil` | 23-20 | Number of soil sensors (TDR315H) | 0-15 | +| `numApogeeSolar` | 19-16 | Number of Apogee solar sensors | 0-15 | +| `numCO2` | 15-12 | Number of CO2 sensors (Hedorah) | 0-15 | +| `numO2` | 11-8 | Number of O2 sensors (SO421) | 0-15 | +| `numPressure` | 7-4 | Number of pressure sensors | 0-15 | +| Reserved | 3-0 | Reserved for future use | 0-15 | + +**Encoding Formula:** +```cpp +int sensorUID = (numET << 28) | + (numHaar << 24) | + (numSoil << 20) | + (numApogeeSolar << 16) | + (numCO2 << 12) | + (numO2 << 8) | + (numPressure << 4); +``` + +##### UID Decoding Examples + +###### Decoding System Configuration UID + +To extract individual values from a System Configuration UID: + +```cpp +// Example UID: 1234567890 (decimal) = 0x499602D2 (hex) +int systemUID = 1234567890; + +// Extract each field +int logPeriod = (systemUID >> 16) & 0xFFFF; // Bits 31-16 +int backhaulCount = (systemUID >> 12) & 0xF; // Bits 15-12 +int powerSaveMode = (systemUID >> 10) & 0x3; // Bits 11-10 +int loggingMode = (systemUID >> 8) & 0x3; // Bits 9-8 +int numAuxTalons = (systemUID >> 6) & 0x3; // Bits 7-6 +int numI2CTalons = (systemUID >> 4) & 0x3; // Bits 5-4 +int numSDI12Talons = (systemUID >> 2) & 0x3; // Bits 3-2 +``` + +###### Decoding Sensor Configuration UID + +```cpp +// Example UID: 305419896 (decimal) = 0x12345678 (hex) +int sensorUID = 305419896; + +// Extract each field +int numET = (sensorUID >> 28) & 0xF; // Bits 31-28 +int numHaar = (sensorUID >> 24) & 0xF; // Bits 27-24 +int numSoil = (sensorUID >> 20) & 0xF; // Bits 23-20 +int numApogeeSolar = (sensorUID >> 16) & 0xF; // Bits 19-16 +int numCO2 = (sensorUID >> 12) & 0xF; // Bits 15-12 +int numO2 = (sensorUID >> 8) & 0xF; // Bits 11-8 +int numPressure = (sensorUID >> 4) & 0xF; // Bits 7-4 +``` + +A tool has been developed to help parse this UID and make sense of it, found [in RTGS_Lab gems_sensing_db_tools](https://github.com/RTGS-Lab/gems_sensing_db_tools) + +##### Practical Examples + +###### Example 1: Default Configuration +```json +{ + "system": { + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 0, + "numAuxTalons": 1, + "numI2CTalons": 1, + "numSDI12Talons": 1 + } +} +``` + +**System UID Calculation:** +- logPeriod (300) << 16 = 19660800 +- backhaulCount (4) << 12 = 16384 +- powerSaveMode (1) << 10 = 1024 +- loggingMode (0) << 8 = 0 +- numAuxTalons (1) << 6 = 64 +- numI2CTalons (1) << 4 = 16 +- numSDI12Talons (1) << 2 = 4 + +**System UID = 19678292** (decimal) or **0x12C4154** (hex) + +###### Example 2: Sensor Configuration +```json +{ + "sensors": { + "numET": 1, + "numHaar": 2, + "numSoil": 3, + "numApogeeSolar": 1, + "numCO2": 1, + "numO2": 1, + "numPressure": 1 + } +} +``` + +**Sensor UID Calculation:** +- numET (1) << 28 = 268435456 +- numHaar (2) << 24 = 33554432 +- numSoil (3) << 20 = 3145728 +- numApogeeSolar (1) << 16 = 65536 +- numCO2 (1) << 12 = 4096 +- numO2 (1) << 8 = 256 +- numPressure (1) << 4 = 16 + +**Sensor UID = 305205520** (decimal) or **0x12311110** (hex) + +##### UID Usage + +Configuration UIDs are used for: + +1. **Change Detection**: Compare current UID with stored UID to detect configuration changes +2. **Remote Monitoring**: Cloud functions return UIDs for remote configuration verification +3. **Debugging**: Quick identification of active configuration without full JSON parsing +4. **Optimization**: Fast configuration comparison without string operations \ No newline at end of file diff --git a/README.md b/README.md index 4fca4f3..5063adc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,255 @@ -# FlightControl-Demo -Demo for testing drivers for the Flight system +# FlightControl + +Frirmware repository for the Flight data logging system. + +## Overview + +This repository serves as a production and development environment for the Flight data logging system, featuring: + +- **Modular Sensor Architecture**: Dynamic sensor configuration and management +- **Hardware Abstraction**: Platform and hardware dependency injection for testability +- **Configuration Management**: JSON-based system and sensor configuration +- **Comprehensive Testing**: Unit tests with Google Test and Google Mock +- **Multiple Platform Support**: Abstracted platform dependencies for Particle devices + +## Key Features + +- Plug and play sensors +- Support for multiple different protocol configurations including Analog, I2C and SDI12 + +### Configuration Management + +The system supports dynamic configuration through JSON files stored on SD card or applied via cloud functions. + +#### Configuration Structure + +This is the default configuration if there is no config.json file on the SD card. + +```json +{ + "config": { + "system": { + "logPeriod": 300, + "backhaulCount": 4, + "powerSaveMode": 1, + "loggingMode": 0, + "numAuxTalons": 1, + "numI2CTalons": 1, + "numSDI12Talons": 1 + }, + "sensors": { + "numET": 0, + "numHaar": 0, + "numSoil": 3, + "numApogeeSolar": 0, + "numCO2": 0, + "numO2": 0, + "numPressure": 0 + } + } +} +``` + +#### System Configuration Parameters + +| Parameter | Description | Default | Range | +|-----------|-------------|---------|-------| +| `logPeriod` | Logging interval in seconds | 300 | 0-65535 | +| `backhaulCount` | Number of logs before cellular backhaul | 4 | 1-15 | +| `powerSaveMode` | Power management mode | 1 | 0-3 | +| `loggingMode` | Logging behavior mode | 0 | 0-3 | +| `numAuxTalons` | Number of Auxiliary Talons | 1 | 0-3 | +| `numI2CTalons` | Number of I2C Talons | 1 | 0-3 | +| `numSDI12Talons` | Number of SDI-12 Talons | 1 | 0-3 | + +#### Sensor Configuration Parameters + +| Parameter | Description | Default | Range | +|-----------|-------------|---------|-------| +| `numET` | Number of Evapotranspiration sensors (LI-710) | 0 | 0-15 | +| `numHaar` | Number of Haar atmospheric sensors | 0 | 0-15 | +| `numSoil` | Number of soil sensors (TDR315H) | 3 | 0-15 | +| `numApogeeSolar` | Number of Apogee solar radiation sensors | 0 | 0-15 | +| `numCO2` | Number of CO2 sensors (Hedorah) | 0 | 0-15 | +| `numO2` | Number of O2 sensors (SO421) | 0 | 0-15 | +| `numPressure` | Number of pressure sensors (BaroVue10) | 0 | 0-15 | + +#### Power Save Modes + +- **0 - Performance**: No power saving, maximum sensor responsiveness +- **1 - Balanced**: Moderate power saving with good performance +- **2 - Low Power**: Aggressive power saving, longer sensor warm-up times +- **3 - Ultra Low Power**: Maximum power saving, minimal sensor operation + +#### Logging Modes + +- **0 - Standard**: Regular diagnostic intervals with full data logging +- **1 - Performance**: Maximum data throughput, minimal diagnostics +- **2 - Balanced**: Hourly diagnostics with standard data logging +- **3 - No Local**: Cloud-only logging, no SD card storage + +## Hardware Architecture + +### Core Components + +- **Kestrel Logger**: Main data logging board with GPS, accelerometer, RTC, and cellular connectivity +- **Gonk Battery**: Smart battery management system +- **Talons**: Expansion boards for sensor connectivity + - **Auxiliary Talon**: General-purpose analog/digital sensor interface + - **I2C Talon**: I2C sensor interface with power management + - **SDI-12 Talon**: SDI-12 protocol sensor interface + +### Supported Sensors + +#### Environmental Sensors +- **LI-710 (ET)**: Evapotranspiration measurements +- **Haar**: Atmospheric temperature, humidity, pressure +- **TDR315H**: Soil moisture and temperature +- **BaroVue10**: Barometric pressure +- **SO421**: Oxygen concentration +- **SP421**: Solar radiation + +#### Gas Sensors +- **Hedorah**: CO2 concentration with SCD30 sensor +- **T9602**: Humidity and temperature + +## Development Setup + +### Prerequisites + +- CMake 3.14 or higher +- C++17 compatible compiler +- Git with submodule support + +### Building Tests + +```bash +# Clone repository with submodules +git clone --recursive https://github.com/RTGS-Lab/Firmware_-_FlightControl.git +cd Firmware_-_FlightControl + +# Create build directory +mkdir build && cd build + +# Configure and build tests +cmake .. +make + +# Run tests +./test/unit_tests +``` + +### Project Structure + +``` +FlightControl-Demo/ +├── src/ # Source code +│ ├── configuration/ # Configuration management +│ ├── hardware/ # Hardware interface implementations +│ └── platform/ # Platform abstraction implementations +├── test/ # Unit tests +│ ├── mocks/ # Mock implementations +│ └── unit/ # Unit test files +├── lib/ # External libraries (git submodules) +└── docs/ # Documentation +``` + +## Testing Framework + +### Unit Testing + +The project uses Google Test and Google Mock for unit testing: + +- **Platform Abstraction Testing**: Mock implementations for all platform dependencies +- **Hardware Interface Testing**: Mock hardware components for isolated testing +- **Configuration Testing**: Validation of configuration parsing and management +- **Sensor Management Testing**: Dynamic sensor initialization and management + +### Mock Architecture + +Mock implementations are provided for: + +- **Platform Dependencies**: TimeProvider, GPIO, System, Wire, Cloud, Serial +- **Hardware Components**: IO Expanders, Current Sensors, RTC, GPS, Accelerometer +- **Sensor Interfaces**: SDI-12, I2C communication protocols + +### Running Tests + +```bash +# Run all tests +./test/unit_tests + +# Run specific test suites +./test/unit_tests --gtest_filter="ConfigurationManagerTest.*" +./test/unit_tests --gtest_filter="KestrelTest.*" +./test/unit_tests --gtest_filter="SensorManagerTest.*" +``` + +## Configuration Examples + +### Full Environmental Station + +```json +{ + "config": { + "system": { + "logPeriod": 300, + "backhaulCount": 8, + "powerSaveMode": 1, + "loggingMode": 0, + "numAuxTalons": 1, + "numI2CTalons": 1, + "numSDI12Talons": 1 + }, + "sensors": { + "numET": 1, + "numHaar": 1, + "numSoil": 2, + "numApogeeSolar": 0, + "numCO2": 0, + "numO2": 0, + "numPressure": 0 + } + } +} +``` + +## Cloud Functions + +The system exposes several Particle cloud functions: + +- `updateConfig`: Update system configuration +- `getSystemConfig`: Get system configuration UID +- `getSensorConfig`: Get sensor configuration UID +- `nodeID`: Set custom node identifier +- `findSensors`: Trigger sensor auto-detection +- `findTalons`: Trigger Talon auto-detection +- `systemRestart`: Restart the system +- `takeSample`: Force immediate data collection +- `commandExe`: Execute system commands + +## Schema and Error Codes + +- **Data Schema**: SEE SCHEMA.md +- **Error Codes**: SEE ERRORCODES.md +- **Command Execution**: SEE COMMANDEXE.md +- **System and Sensor Configuration**: SEE CONFIGURATION.md + +## Contributing + +1. Fork the repository +2. Create a feature branch (feature/name-of-branch) +3. Add unit tests for new functionality +4. Ensure all tests pass +5. Submit a pull request + +## Related Repositories + +This project depends on several driver libraries available as git submodules: + +- Driver libraries for individual sensors +- Hardware abstraction libraries +- Platform dependency interfaces +- Communication protocol implementations + +For a complete list, see [.gitmodules](.gitmodules). \ No newline at end of file diff --git a/lib/Driver_-_Kestrel-FileHandler b/lib/Driver_-_Kestrel-FileHandler index 69db8dd..1b5db1a 160000 --- a/lib/Driver_-_Kestrel-FileHandler +++ b/lib/Driver_-_Kestrel-FileHandler @@ -1 +1 @@ -Subproject commit 69db8dd9cd41204927ed7d3337f2efceefc3faa3 +Subproject commit 1b5db1ad66baeced2e2c0356a2a5e72ee600478a diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl.cpp similarity index 98% rename from src/FlightControl_Demo.cpp rename to src/FlightControl.cpp index fc40369..f7f4533 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl.cpp @@ -1356,42 +1356,62 @@ int setNodeID(String nodeID) } int updateConfiguration(String configJson) { + if(configJson == "remove") { + // Remove the configuration file from the SD card + if (!fileSys.removeFileFromSD("config.json")) { + Serial.println("Error: Failed to remove configuration from SD card."); + return -1; // Failed to remove config + } + else { + Serial.println("Configuration removed from SD card."); + } + return 0; // Success + } + Serial.println("Updating configuration..."); Serial.println(configJson); + //remove all whitespace and newlines from the config string + configJson.replace(" ", ""); + configJson.replace("\n", ""); + configJson.replace("\r", ""); + configJson.replace("\t", ""); + + Serial.println(configJson); + //verify the passed config is valid format if (configJson.indexOf("\"config\"") == -1) { Serial.println("Error: Invalid configuration format. Missing 'config' element."); - return -1; // Invalid format + return -2; // Invalid format } if (configJson.indexOf("\"system\"") == -1) { - Serial.println("Error: Invalid configuration format. Missing 'version' element."); - return -2; // Invalid format + Serial.println("Error: Invalid configuration format. Missing 'system' element."); + return -3; // Invalid format } if (configJson.indexOf("\"sensors\"") == -1) { - Serial.println("Error: Invalid configuration format. Missing 'version' element."); - return -3; // Invalid format + Serial.println("Error: Invalid configuration format. Missing 'sensors' element."); + return -4; // Invalid format } // test write to SD card if (!fileSys.writeToSD("", "config.json")) { Serial.println("Error: Failed to write to SD card."); - return -4; // Failed to write config + return -5; // Failed to write config } - //remove current config.json - if(!fileSys.clearFileFromSD("config.json")) { - Serial.println("Error: Failed to clear current configuration from SD card."); - return -5; // Failed to remove current config + //clear current config.json + if(!fileSys.removeFileFromSD("config.json")) { + Serial.println("Error: Failed to remove current configuration from SD card."); + return -6; // Failed to remove current config } // Write new configuration to SD card if (!fileSys.writeToSD(configJson.c_str(), "config.json")) { Serial.println("Error: Failed to write new configuration to SD card."); - return -6; // Failed to write new config + return -7; // Failed to write new config } - System.reset(); //Reset the system to apply new configuration + System.reset(); //restart the system to apply new configuration return 1; //Success } @@ -1519,7 +1539,7 @@ bool loadConfiguration() { Serial.println("Loading default configuration..."); std::string defaultConfig = configManager.getDefaultConfigurationJson(); configLoaded = configManager.setConfiguration(defaultConfig); - fileSys.clearFileFromSD("config.json"); + fileSys.removeFileFromSD("config.json"); fileSys.writeToSD(defaultConfig.c_str(), "config.json"); } From 47ff028a0d5d2a548e206ce3ccf3fa0499749ca1 Mon Sep 17 00:00:00 2001 From: Zach Radlicz <66800917+zradlicz@users.noreply.github.com> Date: Fri, 23 May 2025 11:44:52 -0500 Subject: [PATCH 19/25] Update CONFIGURATION.md --- CONFIGURATION.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index b7bfe98..139ca7d 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -12,7 +12,13 @@ Configuration is loaded at startup in the following priority order: Configuration can be updated through: 1. **Cloud Function**: `updateConfig` Particle function -2. **SD Card**: Replace `config.json` file and restart system + - copy the intended config.json and paste it as and argument into updateConfig + - If successful, the device will restart without returning any value. + - verify that the first metadata packet after reset matches your configuration + - See below for other types of responses. +3. **SD Card**: Replace `config.json` file and restart system + +#### updateConfig Information #### Configuration UIDs @@ -190,4 +196,4 @@ Configuration UIDs are used for: 1. **Change Detection**: Compare current UID with stored UID to detect configuration changes 2. **Remote Monitoring**: Cloud functions return UIDs for remote configuration verification 3. **Debugging**: Quick identification of active configuration without full JSON parsing -4. **Optimization**: Fast configuration comparison without string operations \ No newline at end of file +4. **Optimization**: Fast configuration comparison without string operations From 0918f09574ad986db1b00196ee008ef97854d80b Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 12:00:06 -0500 Subject: [PATCH 20/25] updated config documentation --- CONFIGURATION.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 139ca7d..58c5cf6 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -18,7 +18,100 @@ Configuration can be updated through: - See below for other types of responses. 3. **SD Card**: Replace `config.json` file and restart system -#### updateConfig Information +#### updateConfig Function Details + +The `updateConfig` Particle cloud function accepts a JSON configuration string and applies it to the system. The function provides detailed feedback about the configuration update process. + +##### Usage + +```bash +# Using Particle CLI +particle call updateConfig '{"config":{"system":{"logPeriod":600}}}' + +# Using Particle Console +# Paste the JSON configuration directly into the function argument field + +# Remove configuration (revert to defaults) +particle call updateConfig 'remove' +``` + +##### Response Types + +The `updateConfig` function returns different responses based on the outcome: + +**Success Responses:** +- **Return 1**: Configuration updated successfully, device will restart automatically +- **Return 0**: Configuration removed successfully (when using "remove" command) +- Verify success by checking the first metadata packet after reset matches your configuration +- New configuration UIDs will be available via `getSystemConfig` and `getSensorConfig` + +**Error Responses:** +The function returns specific error codes when configuration updates fail: + +| Error Code | Description | Troubleshooting | +|------------|-------------|-----------------| +| `0` | Success - Configuration removed from SD card | | +| `1` | Success - Configuration updated, system restarting | | +| `-1` | Failed to remove configuration from SD card | | +| `-2` | Invalid configuration format - Missing 'config' element | | +| `-3` | Invalid configuration format - Missing 'system' element | | +| `-4` | Invalid configuration format - Missing 'sensors' element | | +| `-5` | Failed to write test file to SD card | | +| `-6` | Failed to remove current configuration from SD card | | +| `-7` | Failed to write new configuration to SD card | | + +##### Configuration Validation Rules + +The system performs several validation checks: + +1. **JSON Structure Validation** + - Must contain "config" root element (checked by string search) + - Must contain "system" section within config (checked by string search) + - Must contain "sensors" section within config (checked by string search) + - All whitespace, newlines, carriage returns, and tabs are automatically stripped + +2. **SD Card Validation** + - SD card must be accessible for writing + - System tests write capability before attempting configuration update + - Current configuration must be removable before writing new configuration + +3. **Special Commands** + - Use "remove" as the configuration string to delete config.json and revert to defaults + +##### Example Error Scenarios + +###### Missing Configuration Elements +```bash +particle call device_name updateConfig '{"system":{"logPeriod":300}}' # Missing "config" wrapper +# Returns: -2 + +particle call device_name updateConfig '{"config":{"sensors":{"numSoil":3}}}' # Missing "system" section +# Returns: -3 + +particle call device_name updateConfig '{"config":{"system":{"logPeriod":300}}}' # Missing "sensors" section +# Returns: -4 +``` + +###### SD Card Issues +```bash +# If SD card is not available or full +particle call device_name updateConfig '{"config":{"system":{"logPeriod":300},"sensors":{}}}' +# May return: -5, -6, or -7 depending on the specific SD card failure point +``` + +###### Configuration Removal +```bash +particle call device_name updateConfig 'remove' +# Returns: 0 (success) or -1 (failed to remove) +``` + +##### Best Practices + +1. **Validate JSON First**: Use a JSON validator before sending to the device +2. **Check Parameter Ranges**: Verify all values are within acceptable ranges +3. **Plan Hardware Requirements**: Ensure sufficient Talons for sensor configuration +4. **Monitor Device Status**: Watch for restart after successful configuration +5. **Verify Configuration**: Check UIDs after restart to confirm changes applied #### Configuration UIDs @@ -196,4 +289,4 @@ Configuration UIDs are used for: 1. **Change Detection**: Compare current UID with stored UID to detect configuration changes 2. **Remote Monitoring**: Cloud functions return UIDs for remote configuration verification 3. **Debugging**: Quick identification of active configuration without full JSON parsing -4. **Optimization**: Fast configuration comparison without string operations +4. **Optimization**: Fast configuration comparison without string operations \ No newline at end of file From 79f3d3bba727b38925b971c54c82c5bebc8cf635 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 13:01:51 -0500 Subject: [PATCH 21/25] removed outdated docs --- docs/RemoteConfiguration.md | 120 ------------------------------------ 1 file changed, 120 deletions(-) delete mode 100644 docs/RemoteConfiguration.md diff --git a/docs/RemoteConfiguration.md b/docs/RemoteConfiguration.md deleted file mode 100644 index 8f06b01..0000000 --- a/docs/RemoteConfiguration.md +++ /dev/null @@ -1,120 +0,0 @@ -# Remote Configuration Guide - -The FlightControl-Demo firmware supports remote configuration updates through Particle functions. This allows you to change the system configuration and enabled sensors without requiring a firmware update. - -## Configuration Overview - -The firmware can be configured using a JSON configuration format. The configuration includes: - -1. **System settings**: Controls logging periods, backhaul frequency, power save modes, etc. -2. **Sensors list**: Defines which sensors are enabled and their connection details - -## JSON Format - -The configuration uses the following format: - -```json -{ - "config": { - "system": { - "logPeriod": 300, - "backhaulCount": 4, - "powerSaveMode": 1, - "loggingMode": 0 - }, - "sensors": [ - { - "type": "FileSystem", - "enabled": true - }, - { - "type": "Haar", - "enabled": true, - "port": 1, - "talonPort": 1, - "version": "0x20" - } - ] - } -} -``` - -### System Parameters - -- `logPeriod`: Time in seconds between logging events (default: 300) -- `backhaulCount`: Number of log events before a backhaul is performed (default: 4) -- `powerSaveMode`: Power saving mode (0=Performance, 1=Low Power, 2=Ultra Low Power) -- `loggingMode`: Logging mode (0=Standard, 1=Performance, 2=Balanced, 3=No Local) - -### Sensor Parameters - -- `type`: The sensor type identifier (required) -- `enabled`: Whether the sensor is enabled (true/false) -- `port`: The sensor port on the Talon (only for non-core sensors) -- `talonPort`: The Talon port on the Kestrel (only for non-core sensors) -- `version`: The hardware version, in decimal or hex (e.g., "0x20") - -## Core Sensors - -The following sensors are considered "core" sensors and are always available: - -- FileSystem -- Aux -- I2C -- SDI12 -- Battery -- Logger - -## Dynamic Sensors - -The following sensors can be configured dynamically: - -- Haar -- Hedorah -- T9602 -- TDR315H (up to 3 instances) -- TEROS11 (up to 2 instances) -- LI710 -- SO421 -- SP421 -- ATMOS22 -- BaroVue10 - -## Updating Configuration Remotely - -To update the configuration remotely, call the Particle function `updateConfig` with a valid JSON configuration string. The function can be called using the Particle CLI or the Particle Cloud API. - -### Using Particle CLI - -```bash -particle call updateConfig -``` - -### Return Codes - -- `1`: Success -- `-1`: Failed to update configuration -- `-2`: Invalid JSON format - -## Configuration Files - -Sample configuration files can be found in the `test/sample_configurations/` directory: - -- `standard_config.json`: The default configuration -- `minimal_config.json`: A minimal configuration with fewer sensors -- `full_config.json`: A configuration with many sensors - -## Reset Process - -When a new configuration is applied: - -1. The system settings are updated -2. All non-core sensors are reset -3. Talons and sensors are re-detected -4. Sensors are re-initialized - -This ensures that the device applies the new configuration completely. - -## Persistent Storage - -The current configuration is stored on the SD card as `config.json`. If the device reboots, it will load this configuration during startup. If no configuration file exists or there's an error loading it, the device will use the default configuration. \ No newline at end of file From 62a3aa43c2d3fd88bc860f8efe61c3e51cc57450 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 13:23:17 -0500 Subject: [PATCH 22/25] removed unused functions --- src/configuration/ConfigurationManager.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/configuration/ConfigurationManager.h b/src/configuration/ConfigurationManager.h index 2ee3ff1..a94ea9b 100644 --- a/src/configuration/ConfigurationManager.h +++ b/src/configuration/ConfigurationManager.h @@ -69,10 +69,6 @@ class ConfigurationManager : public IConfiguration { int getNumO2() const { return m_numO2; } int getNumPressure() const { return m_numPressure; } - // Vector management methods - void createSensorVectors(); - void clearSensorVectors(); - // Static factory methods for creating sensors static std::unique_ptr createAuxTalon(); static std::unique_ptr createI2CTalon(); From 90bd3c9af729717213748859ba93e7537fdb217a Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 14:01:38 -0500 Subject: [PATCH 23/25] disabled tests for sensor manager and config manager --- test/CMakeLists.txt | 8 ++++---- test/unit/SensorManager/SensorManagerTest.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b36e16e..12f35af 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,12 +53,12 @@ add_executable(unit_tests ${CMAKE_SOURCE_DIR}/lib/Driver_-_Li710/src/Li710.cpp # ConfigurationManger tests - unit/ConfigurationManager/ConfigurationManagerTest.cpp - ${CMAKE_SOURCE_DIR}/src/configuration/ConfigurationManager.cpp + #unit/ConfigurationManager/ConfigurationManagerTest.cpp + #${CMAKE_SOURCE_DIR}/src/configuration/ConfigurationManager.cpp # SensorManager Tests - unit/SensorManager/SensorManagerTest.cpp - ${CMAKE_SOURCE_DIR}/src/configuration/SensorManager.cpp + #unit/SensorManager/SensorManagerTest.cpp + #${CMAKE_SOURCE_DIR}/src/configuration/SensorManager.cpp ) # Link against mocks and GoogleTest diff --git a/test/unit/SensorManager/SensorManagerTest.cpp b/test/unit/SensorManager/SensorManagerTest.cpp index 8938577..89f4ac0 100644 --- a/test/unit/SensorManager/SensorManagerTest.cpp +++ b/test/unit/SensorManager/SensorManagerTest.cpp @@ -1,14 +1,14 @@ #include #include -#include "SensorManager.h" -#include "configuration/ConfigurationManager.h" +#include "configuration/SensorManager.h" +#include "MockConfiguration.h" #include "MockTimeProvider.h" #include "MockSDI12Talon.h" class SensorManagerTest : public ::testing::Test { protected: - ConfigurationManager configManager; - SensorManager sensorManager{configManager}; + MockConfiguration mockConfig; + SensorManager sensorManager{mockConfig}; MockTimeProvider mockTimeProvider; MockSDI12Talon mockSDI12Talon; From 0521f8925e8f25cf9098e10e93fc9e868344e756 Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Fri, 23 May 2025 14:13:45 -0500 Subject: [PATCH 24/25] cleaned up debug after review --- src/FlightControl.cpp | 45 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/src/FlightControl.cpp b/src/FlightControl.cpp index f7f4533..9bbf512 100644 --- a/src/FlightControl.cpp +++ b/src/FlightControl.cpp @@ -40,16 +40,6 @@ bool loadConfiguration(); void updateSensorVectors(); void initializeSensorSystem(); -// Forward declare the types of core sensors (for configuration purposes) -const char* const CoreSensorTypes[] = { - "FileSystem", - "Aux", - "I2C", - "SDI12", - "Battery", - "Logger" -}; - #define WAIT_GPS false #define USE_CELL //System attempts to connect to cell #include @@ -242,7 +232,8 @@ void setup() { Particle.function("systemRestart", systemRestart); Particle.function("takeSample", takeSample); Particle.function("commandExe", commandExe); - + Serial.begin(1000000); + waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds Serial.print("RESET CAUSE: "); //DEBUG! Serial.println(System.resetReason()); //DEBUG! bool hasCriticalError = false; @@ -296,9 +287,6 @@ void setup() { // logger.enableData(i, false); //Turn off all data by default // } - Serial.begin(1000000); - waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds - //load configuration from SD card, default config if not found or not possible Serial.println("Loading configuration..."); //DEBUG! loadConfiguration(); @@ -926,22 +914,17 @@ String initSensors() logger.configTalonSense(); //Setup to allow for current testing // if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) talons[sensors[i]->getTalonPort() - 1]->begin(logger.getTime(), dummy1, dummy2); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! - Serial.print("iteration number: "); - Serial.println(i); int currentTalonIndex = getIndexOfPort(sensors[i]->getTalonPort()); - Serial.println("created talonOfSensor"); + if(currentTalonIndex >= 0) { if(sensors[i]->getTalonPort() > 0) { //DEBUG! REPLACE! - Serial.println("restart talonOfSensor"); talons[currentTalonIndex]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! } if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - Serial.println("disableDataAll of talonOfSensor"); talons[currentTalonIndex]->disableDataAll(); //Turn off all data on Talon // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon talons[currentTalonIndex]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used - Serial.println("enableData of talonOfSensor"); } } if(sensors[i]->getTalonPort() == 0 && sensors[i]->sensorInterface != BusType::CORE) { @@ -1255,7 +1238,7 @@ int detectTalons(String dummyStr) // talons[i]->begin(Time.now(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //DEBUG! talons[i]->begin(logger.getTime(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! // talons[i]->begin(0, dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! - Serial.println("TALON BEGIN DONE"); //DEBUG! + //Serial.println("TALON BEGIN DONE"); //DEBUG! //Serial.flush(); //DEBUG! //delay(10000); //DEBUG! logger.enableData(talons[i]->getTalonPort(), false); //Turn data back off to prevent conflict @@ -1272,31 +1255,31 @@ int detectSensors(String dummyStr) { /////////////// SENSOR AUTO DETECTION ////////////////////// for(int t = 0; t < talons.size(); t++) { //Iterate over each Talon - //Serial.println(talons[t]->talonInterface); //DEBUG! + // Serial.println(talons[t]->talonInterface); //DEBUG! // Serial.print("DETECT ON TALON: "); //DEBUG! - //Serial.println(t); + // Serial.println(t); // Serial.flush(); - //if(talons[t]) { + // if(talons[t]) { // delay(5000); // Serial.println("TALON EXISTS"); //DEBUG! // Serial.flush(); - //} + // } // else { // delay(5000); // Serial.println("TALON NOT EXISTS"); //DEBUG! // Serial.flush(); - //} + // } // delay(5000); - //if(talons[t]->talonInterface != BusType::NONE) { + // if(talons[t]->talonInterface != BusType::NONE) { // delay(5000); - //Serial.println("TALON NOT NONE"); //DEBUG! + // Serial.println("TALON NOT NONE"); //DEBUG! // Serial.flush(); // } - //else { + // else { // delay(5000); - //Serial.println("TALON NONE"); //DEBUG! + // Serial.println("TALON NONE"); //DEBUG! // Serial.flush(); - //} + // } // delay(10000); //DEBUG! // Serial.println(talons[t]->talonInterface); //DEBUG! if(talons[t] && talons[t]->talonInterface != BusType::NONE && talons[t]->getTalonPort() != 0) { //Only proceed if Talon has a bus which can be iterated over, and the talon in question exists and has been detected From 5b68fe5000975afbcf2d1b4aa31bb3b8a909bfcf Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Tue, 27 May 2025 15:35:52 -0500 Subject: [PATCH 25/25] added release to all v3 products on particle --- .github/workflows/release-workflow.yaml | 39 +++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-workflow.yaml b/.github/workflows/release-workflow.yaml index 45214ad..33fe06e 100644 --- a/.github/workflows/release-workflow.yaml +++ b/.github/workflows/release-workflow.yaml @@ -99,12 +99,37 @@ jobs: commit: ${{ steps.commit.outputs.updated-version-sha || github.sha }} token: ${{ steps.app-token.outputs.token }} - upload: - name: Upload to Particle + upload-to-particle: + name: Upload to Particle Projects needs: release runs-on: ubuntu-latest # Only run if release job has completed and the firmware version was updated if: needs.release.outputs.firmware-version-updated == 'true' + strategy: + matrix: + project: + - name: "GEMS Demo" + product_id_secret: "PARTICLE_GEMS_DEMO_PRODUCT_ID" + - name: "Runk Lab (B SoM)" + product_id_secret: "PARTICLE_RUNCK_LAB_BSOM_PRODUCT_ID" + - name: "WinterTurf - v3 International" + product_id_secret: "PARTICLE_WINTERTURF_INTERNATIONAL_PRODUCT_ID" + - name: "WinterTurf - v3" + product_id_secret: "PARTICLE_WINTERTURF_PRODUCT_ID" + - name: "Plant Pathways" + product_id_secret: "PARTICLE_PLANT_PATHWAYS_PRODUCT_ID" + - name: "LCCMR Irrigation" + product_id_secret: "PARTICLE_LCCMR_IRRIGATION_PRODUCT_ID" + - name: "Roadside Turf" + product_id_secret: "PARTICLE_ROADSIDE_TURF_PRODUCT_ID" + - name: "PepsiCo" + product_id_secret: "PARTICLE_PEPSICO_PRODUCT_ID" + - name: "Stellenbosch" + product_id_secret: "PARTICLE_STELLENBOSCH_PRODUCT_ID" + - name: "Runk Lab (B5 SoM)" + product_id_secret: "PARTICLE_RUNCK_LAB_B5SOM_PRODUCT_ID" + - name: "LCCMR Irrigation Sensing" + product_id_secret: "PARTICLE_LCCMR_IRRIGATION_SENSING_PRODUCT_ID" steps: - name: Checkout code uses: actions/checkout@v4 @@ -121,12 +146,16 @@ jobs: FIRMWARE=$(find ./release -name "*.bin" -type f | head -n 1) echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT - - name: Upload product firmware to Particle + - name: Upload firmware to ${{ matrix.project.name }} uses: particle-iot/firmware-upload-action@v1 with: particle-access-token: ${{ secrets.PARTICLE_ACCESS_TOKEN }} firmware-path: ${{ steps.find_binary.outputs.firmware-path }} firmware-version: ${{ needs.release.outputs.firmware-version }} - product-id: ${{ secrets.PARTICLE_GEMS_DEMO_PRODUCT_ID }} + product-id: ${{ secrets[matrix.project.product_id_secret] }} title: 'Firmware v${{ needs.release.outputs.firmware-version }}' - description: '[Firmware v${{ needs.release.outputs.firmware-version }} GitHub Release](${{ needs.release.outputs.release-url }}' \ No newline at end of file + description: '[Firmware v${{ needs.release.outputs.firmware-version }} GitHub Release](${{ needs.release.outputs.release-url }})' + + - name: Log upload success + run: | + echo "✅ Successfully uploaded firmware v${{ needs.release.outputs.firmware-version }} to ${{ matrix.project.name }}" \ No newline at end of file