From ec4de1a7078d55582c830f10d7b4e0c46af3189a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunics=20Bal=C3=A1zs?= Date: Mon, 1 Jun 2026 01:14:48 +0200 Subject: [PATCH 1/3] feat: Add ISOBUS bus control function enumeration and TC conflict detection - Add enumerate_bus_control_functions() to list all CFs on the bus with NAME details (address, manufacturer code, function code, industry group, identity number) - Log bus CFs before and after creating internal TC/TECU control functions - Add check_tc_address_conflict() to detect and warn when another TC claims preferred address - Periodic WARN message every 30 seconds when TC address conflict is present - Use numeric codes only for cleaner, more maintainable output format - Tag internal control functions with [INTERNAL] marker in enumeration output This improves debugging visibility into ISOBUS network state and helps diagnose address claim conflicts when multiple Task Controllers are present on the bus. --- src/app.cpp | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/app.cpp b/src/app.cpp index 3425304..8095a4b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -10,6 +10,7 @@ #include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" +#include "isobus/isobus/can_internal_control_function.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/isobus_preferred_addresses.hpp" #include "isobus/isobus/isobus_standard_data_description_indices.hpp" @@ -19,11 +20,131 @@ #include "logging_utils.hpp" +#include #include +#include +#include #include using boost::asio::ip::udp; +// Enumerate and log all Control Functions on the bus +static void enumerate_bus_control_functions(const std::string &context) +{ + std::cout << "\n"; + std::cout << "[" << get_timestamp() << "] [Bus CFs] " << context << std::endl; + std::cout << "[" << get_timestamp() << "] [Bus CFs] ==================================================" << std::endl; + std::cout << "[" << get_timestamp() << "] [Bus CFs] Control Functions on ISOBUS:" << std::endl; + std::cout << "[" << get_timestamp() << "] [Bus CFs] --------------------------------------------------" << std::endl; + + // Get all control functions (including offline ones to see address claim history) + auto allCFs = isobus::CANNetworkManager::CANNetwork.get_control_functions(false); + + std::uint32_t cfCount = 0; + + for (const auto &cf : allCFs) + { + if (!cf) + continue; + + // Only show control functions with valid addresses (online) + if (!cf->get_address_valid()) + continue; + + cfCount++; + isobus::NAME name = cf->get_NAME(); + bool isInternal = (cf->get_type() == isobus::ControlFunction::Type::Internal); + + std::cout << "[" << get_timestamp() << "] [Bus CFs] Address: " << std::setw(3) << std::right << static_cast(cf->get_address()) + << " | Mfg: " << std::left << std::setw(5) << name.get_manufacturer_code() + << " | Func: " << std::left << std::setw(3) << static_cast(name.get_function_code()) + << " | IG: " << static_cast(name.get_industry_group()) + << " | Identity: " << std::setw(6) << std::right << name.get_identity_number() + << " | ECU Inst: " << static_cast(name.get_ecu_instance()) + << " | Func Inst: " << static_cast(name.get_function_instance()) + << (isInternal ? " [INTERNAL]" : "") + << std::endl; + } + + if (cfCount == 0) + { + std::cout << "[" << get_timestamp() << "] [Bus CFs] (No control functions detected on bus)" << std::endl; + } + + std::cout << "[" << get_timestamp() << "] [Bus CFs] ==================================================" << std::endl; + std::cout << "[" << get_timestamp() << "] [Bus CFs] Total CFs found: " << cfCount << std::endl; + std::cout << "\n"; +} + +// Check for TC address conflicts and log warning if we couldn't claim preferred address +static void check_tc_address_conflict(std::shared_ptr ourTC) +{ + if (!ourTC || !ourTC->get_address_valid()) + return; + + static constexpr std::uint8_t PREFERRED_TC_ADDRESS = isobus::preferred_addresses::IndustryGroup2::TaskController_MappingComputer; + static std::uint32_t lastWarnTime = 0; + static bool conflictDetected = false; + + // Check if we have the preferred address + if (ourTC->get_address() == PREFERRED_TC_ADDRESS) + { + // We have the preferred address, clear conflict state + if (conflictDetected) + { + conflictDetected = false; + std::cout << "[" << get_timestamp() << "] [TC Address] Successfully claimed preferred address " << static_cast(PREFERRED_TC_ADDRESS) << std::endl; + } + return; + } + + // We don't have the preferred address - check if another TC has it + auto allCFs = isobus::CANNetworkManager::CANNetwork.get_control_functions(false); + + for (const auto &cf : allCFs) + { + if (!cf || !cf->get_address_valid()) + continue; + + if (cf->get_address() == PREFERRED_TC_ADDRESS && cf != ourTC) + { + isobus::NAME otherName = cf->get_NAME(); + + // Check if it's actually a TC (function code 10 or 11) + std::uint8_t funcCode = otherName.get_function_code(); + if (funcCode == 10 || funcCode == 11) // Task Controller - Mapping Computer or Basic + { + // Periodic warning every 30 seconds + if (isobus::SystemTiming::time_expired_ms(lastWarnTime, 30000)) + { + conflictDetected = true; + std::cout << "\n"; + std::cout << "[" << get_timestamp() << "] [WARN] ==================================================" << std::endl; + std::cout << "[" << get_timestamp() << "] [WARN] TC ADDRESS CONFLICT - Another TC at preferred address " << static_cast(PREFERRED_TC_ADDRESS) << std::endl; + std::cout << "[" << get_timestamp() << "] [WARN] Conflicting TC: Mfg=" << otherName.get_manufacturer_code() + << ", Func=" << static_cast(funcCode) + << ", Identity=" << otherName.get_identity_number() + << ", ECU Inst=" << static_cast(otherName.get_ecu_instance()) + << ", Func Inst=" << static_cast(otherName.get_function_instance()) << std::endl; + std::cout << "[" << get_timestamp() << "] [WARN] Our TC using address: " << static_cast(ourTC->get_address()) << std::endl; + std::cout << "[" << get_timestamp() << "] [WARN] ==================================================" << std::endl; + std::cout << "\n"; + lastWarnTime = isobus::SystemTiming::get_timestamp_ms(); + } + return; // Only report first conflicting TC found + } + } + } + + // If we get here, we didn't find a conflicting TC at the preferred address + // This means we arbitrated to a different address for another reason + if (conflictDetected) + { + conflictDetected = false; + std::cout << "[" << get_timestamp() << "] [TC Address] TC address conflict resolved" << std::endl; + } +} + Application::Application(std::shared_ptr canDriver) : canDriver(canDriver) { @@ -48,6 +169,9 @@ bool Application::initialize() isobus::CANNetworkManager::CANNetwork.get_configuration().set_number_of_packets_per_cts_message(255); + // Enumerate CFs on the bus BEFORE creating our own functions + enumerate_bus_control_functions("Before creating internal control functions"); + isobus::NAME ourNAME(0); //! Make sure you change these for your device!!!! @@ -174,6 +298,9 @@ bool Application::initialize() } } + // Enumerate CFs on the bus AFTER creating our internal functions + enumerate_bus_control_functions("After creating internal control functions"); + tcServer = std::make_shared(tcCF); auto &languageInterface = tcServer->get_language_command_interface(); languageInterface.set_language_code("en"); // This is the default, but you can change it if you want @@ -339,6 +466,9 @@ bool Application::update() if (nmea2000MessageInterface) nmea2000MessageInterface->update(); + // Check for TC address conflicts periodically + check_tc_address_conflict(tcCF); + // Send section control heartbeat to AOG every 100ms (PGN 0xF0, source 0x80) // When no clients with sections, send 0 sections as heartbeat so AOG knows TC is alive if (isobus::SystemTiming::time_expired_ms(lastHeartbeatTransmit, 100)) From f0944e6a9d18f7f59b7216f07ba3e8499f6209fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Gunics?= Date: Mon, 1 Jun 2026 07:50:24 +0200 Subject: [PATCH 2/3] Changes based on copilot review --- src/app.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 087c1c2..63a2626 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include @@ -78,7 +77,7 @@ static void enumerate_bus_control_functions(const std::string &context) } // Check for TC address conflicts and log warning if we couldn't claim preferred address -static void check_tc_address_conflict(std::shared_ptr ourTC) +static void check_tc_address_conflict(const std::shared_ptr &ourTC) { if (!ourTC || !ourTC->get_address_valid()) return; @@ -170,6 +169,11 @@ bool Application::initialize() isobus::CANNetworkManager::CANNetwork.get_configuration().set_number_of_packets_per_cts_message(255); + // Start CAN network and allow a brief update window to observe bus CFs + isobus::CANNetworkManager::CANNetwork.update(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + isobus::CANNetworkManager::CANNetwork.update(); + // Enumerate CFs on the bus BEFORE creating our own functions enumerate_bus_control_functions("Before creating internal control functions"); @@ -302,7 +306,7 @@ bool Application::initialize() // Enumerate CFs on the bus AFTER creating our internal functions enumerate_bus_control_functions("After creating internal control functions"); - // Map settings version to TaskControllerVersion enum + // Map settings version to TaskControllerVersion enum isobus::TaskControllerServer::TaskControllerVersion tcVersionEnum; switch (settings->get_tc_version()) { @@ -509,8 +513,13 @@ bool Application::update() if (nmea2000MessageInterface) nmea2000MessageInterface->update(); - // Check for TC address conflicts periodically - check_tc_address_conflict(tcCF); + // Check for TC address conflicts every 15 seconds + static std::uint32_t lastConflictCheck = 0; + if (isobus::SystemTiming::time_expired_ms(lastConflictCheck, 15000)) + { + check_tc_address_conflict(tcCF); + lastConflictCheck = isobus::SystemTiming::get_timestamp_ms(); + } // Send section control heartbeat to AOG every 100ms (PGN 0xF0, source 0x80) // When no clients with sections, send 0 sections as heartbeat so AOG knows TC is alive From b47b3e98796101a5e9c85a152b6de15a09662315 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 06:06:35 +0000 Subject: [PATCH 3/3] fix: address remaining PR review feedback in app diagnostics --- src/app.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 63a2626..e0b7bbd 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -37,7 +37,7 @@ static void enumerate_bus_control_functions(const std::string &context) std::cout << "[" << get_timestamp() << "] [Bus CFs] Control Functions on ISOBUS:" << std::endl; std::cout << "[" << get_timestamp() << "] [Bus CFs] --------------------------------------------------" << std::endl; - // Get all control functions (including offline ones to see address claim history) + // Get all control functions; offline CFs are filtered out below. auto allCFs = isobus::CANNetworkManager::CANNetwork.get_control_functions(false); std::uint32_t cfCount = 0; @@ -110,9 +110,9 @@ static void check_tc_address_conflict(const std::shared_ptrget_NAME(); - // Check if it's actually a TC (function code 10 or 11) + // Check if it's actually a TC std::uint8_t funcCode = otherName.get_function_code(); - if (funcCode == 10 || funcCode == 11) // Task Controller - Mapping Computer or Basic + if (funcCode == static_cast(isobus::NAME::Function::TaskController)) { // Periodic warning every 30 seconds if (isobus::SystemTiming::time_expired_ms(lastWarnTime, 30000)) @@ -170,9 +170,12 @@ bool Application::initialize() isobus::CANNetworkManager::CANNetwork.get_configuration().set_number_of_packets_per_cts_message(255); // Start CAN network and allow a brief update window to observe bus CFs - isobus::CANNetworkManager::CANNetwork.update(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - isobus::CANNetworkManager::CANNetwork.update(); + auto busDiscoveryWindowStart = isobus::SystemTiming::get_timestamp_ms(); + while (isobus::SystemTiming::get_time_elapsed_ms(busDiscoveryWindowStart) < 100) + { + isobus::CANNetworkManager::CANNetwork.update(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } // Enumerate CFs on the bus BEFORE creating our own functions enumerate_bus_control_functions("Before creating internal control functions");