diff --git a/CMakeLists.txt b/CMakeLists.txt index c36781c..bffc7a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.16) project("AOG-TaskController") set(PROJECT_VERSION_MAJOR 1) -set(PROJECT_VERSION_MINOR 2) -set(PROJECT_VERSION_PATCH 0) +set(PROJECT_VERSION_MINOR 3) +set(PROJECT_VERSION_PATCH 1) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" ) diff --git a/include/settings.hpp b/include/settings.hpp index 878adc0..97adac0 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -48,6 +48,20 @@ class Settings */ bool set_subnet(std::array subnet, bool save = true); + /** + * @brief Check if Tractor ECU is enabled + * @return True if TECU is enabled, false otherwise + */ + bool is_tecu_enabled() const; + + /** + * @brief Set the Tractor ECU enabled state + * @param enabled Whether to enable the Tractor ECU + * @param save Whether or not to save the settings to file + * @return True if the setting was set successfully, false otherwise + */ + bool set_tecu_enabled(bool enabled, bool save = true); + /** * @brief Get the absolute path to the settings file * @param filename The filename to get the path for @@ -57,5 +71,7 @@ class Settings private: constexpr static std::array DEFAULT_SUBNET = { 192, 168, 5 }; + constexpr static bool DEFAULT_TECU_ENABLED = true; std::array configuredSubnet = DEFAULT_SUBNET; + bool tecuEnabled = DEFAULT_TECU_ENABLED; }; diff --git a/readme.md b/readme.md index 190a36f..cddce1e 100644 --- a/readme.md +++ b/readme.md @@ -25,8 +25,48 @@ Then, you can run the following commands: ```bash mkdir build -cmake -S . -B build -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -Wno-dev +cmake -S . -B build -DBUILD_EXAMPLES=OFF -DCMAKE_POLICY_VERSION_MINIMUM="3.16" -DBUILD_TESTING=OFF -Wno-dev cmake --build build --config Release --target package ``` The installer will be generated in the `build` directory. + +## Configuration + +AOG-TaskController reads its configuration from a `settings.json` file located in: + +- **Windows:** `%APPDATA%\AOG-Taskcontroller\settings.json` + +One of the available options is the `tecuEnabled` flag: + +- **Key:** `tecuEnabled` +- **Default:** `true` +- **Description:** Enables communication with the Tractor ECU (TECU) over ISOBUS/CAN. +- **When to disable:** Set to `false` if your tractor or simulator does not provide TECU messages, if you do not need TECU-related data, or if you are troubleshooting TECU-related issues on the CAN bus. + +Example `settings.json` snippet: + +```json +{ + "tecuEnabled": true +} +``` + +Before committing it's better to run these commands: (requires the LLVM project to be installed) + ```powershell +git ls-files | Select-String '\.(c|cc|cpp|cxx|h|hh|hpp|hxx|proto)$' | ForEach-Object { + clang-format -i $_.ToString() +} +``` + +Install cmake-format with: +```powershell +python -m pip install --upgrade pip +pip install cmake-format pyyaml +``` +Then execute with: +```powershell +Get-ChildItem -Recurse -Filter CMakeLists.txt | ForEach-Object { + python -m cmakelang.format -i $_.FullName +} +``` diff --git a/src/app.cpp b/src/app.cpp index e11ba02..f5cce0d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -84,8 +84,8 @@ bool Application::initialize() // Create TECU control function // TODO: Should we wait between this and TC? // TODO: If there's already a TECU on the bus we should not create ours - if (tcCF) - { // Only create TECU if TC was created + if (tcCF && settings->is_tecu_enabled()) + { // Only create TECU if TC was created and ECU is enabled std::cout << "[Init] Creating Tractor ECU control function..." << std::endl; tecuCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(tecuNAME, 0, isobus::preferred_addresses::IndustryGroup2::TractorECU); std::cout << "[Init] Tractor ECU control function created, waiting 1.5 seconds..." << std::endl; @@ -125,7 +125,14 @@ bool Application::initialize() } else { - std::cout << "[Warning] TECU Control Function not available, Speed/NMEA interfaces not created" << std::endl; + if (!settings->is_tecu_enabled()) + { + std::cout << "[Info] Tractor ECU disabled in settings, skipping ECU initialization." << std::endl; + } + else + { + std::cout << "[Warning] TECU Control Function not available, Speed/NMEA interfaces not created" << std::endl; + } } std::cout << "Task controller server started." << std::endl; @@ -248,6 +255,13 @@ bool Application::update() for (auto &client : tcServer->get_clients()) { auto &state = client.second; + + // Skip clients with no sections (e.g., tractors or non-implement devices) + if (state.get_number_of_sections() == 0) + { + continue; + } + std::vector data = { state.is_section_control_enabled(), state.get_number_of_sections() }; std::uint8_t sectionIndex = 0; diff --git a/src/settings.cpp b/src/settings.cpp index c6c5cf9..d8a7474 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -44,6 +44,23 @@ bool Settings::load() configuredSubnet = DEFAULT_SUBNET; // Key not found, use default } + if (data.contains("tecuEnabled")) + { + try + { + tecuEnabled = data["tecuEnabled"].get(); + } + catch (const nlohmann::json::exception &e) + { + std::cout << "Error parsing 'tecuEnabled': " << e.what() << std::endl; + tecuEnabled = DEFAULT_TECU_ENABLED; // Fallback to default + } + } + else + { + tecuEnabled = DEFAULT_TECU_ENABLED; // Key not found, use default + } + return true; } @@ -51,6 +68,7 @@ bool Settings::save() const { json data; data["subnet"] = configuredSubnet; + data["tecuEnabled"] = tecuEnabled; std::ofstream file(get_filename_path("settings.json")); if (!file.is_open()) @@ -82,6 +100,21 @@ bool Settings::set_subnet(std::array subnet, bool save) return true; } +bool Settings::is_tecu_enabled() const +{ + return tecuEnabled; +} + +bool Settings::set_tecu_enabled(bool enabled, bool save) +{ + tecuEnabled = enabled; + if (save) + { + return this->save(); + } + return true; +} + std::string Settings::get_filename_path(std::string fileName) { char path[MAX_PATH]; diff --git a/src/task_controller.cpp b/src/task_controller.cpp index 3d7e61f..8b260bc 100644 --- a/src/task_controller.cpp +++ b/src/task_controller.cpp @@ -238,6 +238,7 @@ MyTCServer::MyTCServer(std::shared_ptr internal bool MyTCServer::activate_object_pool(std::shared_ptr partnerCF, ObjectPoolActivationError &, ObjectPoolErrorCodes &, std::uint16_t &, std::uint16_t &) { + std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " requesting object pool activation" << std::endl; // Safety check to make sure partnerCF has uploaded a DDOP if (uploadedPools.find(partnerCF) == uploadedPools.end()) { @@ -339,6 +340,8 @@ bool MyTCServer::activate_object_pool(std::shared_ptr p } clients[partnerCF] = state; + std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " registered successfully with " + << static_cast(state.get_number_of_sections()) << " sections." << std::endl; return true; } @@ -385,6 +388,7 @@ void MyTCServer::identify_task_controller(std::uint8_t) void MyTCServer::on_client_timeout(std::shared_ptr partner) { // Cleanup the client state + std::cout << "[TC Server] Client " << partner->get_NAME().get_full_name() << " has timed out!" << std::endl; clients.erase(partner); } @@ -452,6 +456,7 @@ bool MyTCServer::on_value_command(std::shared_ptr partn bool MyTCServer::store_device_descriptor_object_pool(std::shared_ptr partnerCF, const std::vector &binaryPool, bool appendToPool) { + std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " requesting object pool transfer of " << binaryPool.size() << " bytes" << std::endl; if (uploadedPools.find(partnerCF) == uploadedPools.end()) { uploadedPools[partnerCF] = std::queue>(); @@ -579,7 +584,13 @@ void MyTCServer::update_section_states(std::vector §ionStates) if (!state.is_section_control_enabled()) { // According to standard, the section setpoint states should only be sent when in auto mode - return; + continue; + } + + // Skip clients that don't have any sections configured (e.g., tractors or other non-implement devices) + if (state.get_number_of_sections() == 0) + { + continue; } bool requiresUpdate = false; @@ -614,9 +625,16 @@ void MyTCServer::update_section_control_enabled(bool enabled) { for (auto &client : clients) { + // Always update the local flag if (client.second.is_section_control_enabled() != enabled) { client.second.set_section_control_enabled(enabled); + } + + // Only send ISOBUS command to clients that support SectionControlState DDI and have sections + if (client.second.has_element_number_for_ddi(isobus::DataDescriptionIndex::SectionControlState) && + client.second.get_number_of_sections() > 0) + { send_section_control_state(client.first, enabled); } }