From 561c807050fb5e4627b9f20df0a744eab34b6782 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 19:49:19 +0000 Subject: [PATCH 1/3] Initial plan From f0eb3e1bf055633d448b225b0cd3529a14e44fd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 19:57:42 +0000 Subject: [PATCH 2/3] Carry forward tcFunctionalities, TC version/locale settings, and VersionPayload exchange from PR #47 - Settings: add get/set_tc_version, get/set_language_code, get/set_country_code with JSON persistence - MyTCServer: accept configurable TaskControllerVersion (default SecondEditionDraft) - app: initialize tcFunctionalities announcing TC-BAS + TC-SC (1 boom / 64 sections) - app: create tcServer with version and locale from settings - app: drive bidirectional VersionPayload exchange via ProcessData PGN callback --- include/app.hpp | 7 +++ include/settings.hpp | 48 +++++++++++++++ include/task_controller.hpp | 3 +- src/app.cpp | 112 ++++++++++++++++++++++++++++++++- src/settings.cpp | 119 ++++++++++++++++++++++++++++++++++++ src/task_controller.cpp | 5 +- 6 files changed, 288 insertions(+), 6 deletions(-) diff --git a/include/app.hpp b/include/app.hpp index 6ceb977..c7c0858 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -10,8 +10,10 @@ #include #include +#include #include "isobus/hardware_integration/can_hardware_plugin.hpp" +#include "isobus/isobus/can_message.hpp" #include "isobus/isobus/isobus_functionalities.hpp" #include "isobus/isobus/isobus_speed_distance_messages.hpp" #include "isobus/isobus/nmea2000_message_interface.hpp" @@ -33,6 +35,8 @@ class Application private: void send_task_controller_status_message(); + static void on_process_data_pgn_received(const isobus::CANMessage &message, void *parent); + std::shared_ptr settings = std::make_shared(); boost::asio::io_context ioContext = boost::asio::io_context(); std::shared_ptr udpConnections = std::make_shared(settings, ioContext); @@ -44,8 +48,11 @@ class Application std::unique_ptr speedMessagesInterface; std::unique_ptr nmea2000MessageInterface; std::unique_ptr tecuFunctionalities; + std::unique_ptr tcFunctionalities; std::uint8_t nmea2000SequenceIdentifier = 0; std::uint32_t lastJ1939SpeedTransmit = 0; std::uint32_t lastTCStatusTransmit = 0; std::int32_t lastSpeedValue = 0; + + std::set implementVersionRequested; ///< NAMEs of implements we have already sent a RequestVersion to }; diff --git a/include/settings.hpp b/include/settings.hpp index f1e5b13..c5c9055 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -77,6 +77,48 @@ class Settings */ bool set_aog_heartbeat_enabled(bool enabled, bool save = true); + /** + * @brief Get the configured TC ISO 11783-10 version + * @return The configured version (0-4, default 3) + */ + std::uint8_t get_tc_version() const; + + /** + * @brief Set the TC ISO 11783-10 version + * @param version The version to set (0=DIS, 1=FDIS.1, 2=FirstEdition, 3=SecondEditionDraft, 4=SecondPublishedEdition) + * @param save Whether or not to save the settings to file + * @return True if the version was set successfully, false otherwise + */ + bool set_tc_version(std::uint8_t version, bool save = true); + + /** + * @brief Get the configured language code (ISO 639-1) + * @return The language code (default "en") + */ + std::string get_language_code() const; + + /** + * @brief Set the language code + * @param code The ISO 639-1 language code + * @param save Whether or not to save the settings to file + * @return True if the setting was set successfully, false otherwise + */ + bool set_language_code(std::string code, bool save = true); + + /** + * @brief Get the configured country code (ISO 3166-1 alpha-2) + * @return The country code (default "US") + */ + std::string get_country_code() const; + + /** + * @brief Set the country code + * @param code The ISO 3166-1 alpha-2 country code + * @param save Whether or not to save the settings to file + * @return True if the setting was set successfully, false otherwise + */ + bool set_country_code(std::string code, bool save = true); + /** * @brief Get the absolute path to the settings file * @param filename The filename to get the path for @@ -88,7 +130,13 @@ class Settings constexpr static std::array DEFAULT_SUBNET = { 192, 168, 5 }; constexpr static bool DEFAULT_TECU_ENABLED = true; constexpr static bool DEFAULT_AOG_HEARTBEAT_ENABLED = true; + constexpr static std::uint8_t DEFAULT_TC_VERSION = 3; ///< SecondEditionDraft (V3 default for maximum implement compatibility) + static const std::string DEFAULT_LANGUAGE_CODE; + static const std::string DEFAULT_COUNTRY_CODE; std::array configuredSubnet = DEFAULT_SUBNET; bool tecuEnabled = DEFAULT_TECU_ENABLED; bool aogHeartbeatEnabled = DEFAULT_AOG_HEARTBEAT_ENABLED; + std::uint8_t tcVersion = DEFAULT_TC_VERSION; + std::string languageCode = DEFAULT_LANGUAGE_CODE; + std::string countryCode = DEFAULT_COUNTRY_CODE; }; diff --git a/include/task_controller.hpp b/include/task_controller.hpp index 9546457..d1ca919 100644 --- a/include/task_controller.hpp +++ b/include/task_controller.hpp @@ -76,7 +76,8 @@ class ClientState class MyTCServer : public isobus::TaskControllerServer { public: - MyTCServer(std::shared_ptr internalControlFunction); + MyTCServer(std::shared_ptr internalControlFunction, + isobus::TaskControllerServer::TaskControllerVersion version = isobus::TaskControllerServer::TaskControllerVersion::SecondEditionDraft); bool activate_object_pool(std::shared_ptr partnerCF, ObjectPoolActivationError &, ObjectPoolErrorCodes &, std::uint16_t &, std::uint16_t &) override; bool change_designator(std::shared_ptr, std::uint16_t, const std::vector &) override; bool deactivate_object_pool(std::shared_ptr partnerCF) override; diff --git a/src/app.cpp b/src/app.cpp index 3425304..65da80d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -10,9 +10,11 @@ #include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" +#include "isobus/isobus/can_general_parameter_group_numbers.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/isobus_preferred_addresses.hpp" #include "isobus/isobus/isobus_standard_data_description_indices.hpp" +#include "isobus/isobus/isobus_task_controller_server.hpp" #include "isobus/utility/system_timing.hpp" #include "task_controller.hpp" @@ -174,13 +176,68 @@ bool Application::initialize() } } - tcServer = std::make_shared(tcCF); + // Map settings version to TaskControllerVersion enum + isobus::TaskControllerServer::TaskControllerVersion tcVersionEnum; + switch (settings->get_tc_version()) + { + case 0: + tcVersionEnum = isobus::TaskControllerServer::TaskControllerVersion::DraftInternationalStandard; + break; + case 1: + tcVersionEnum = isobus::TaskControllerServer::TaskControllerVersion::FinalDraftInternationalStandardFirstEdition; + break; + case 2: + tcVersionEnum = isobus::TaskControllerServer::TaskControllerVersion::FirstPublishedEdition; + break; + case 3: + tcVersionEnum = isobus::TaskControllerServer::TaskControllerVersion::SecondEditionDraft; + break; + case 4: + default: + tcVersionEnum = isobus::TaskControllerServer::TaskControllerVersion::SecondPublishedEdition; + break; + } + + tcServer = std::make_shared(tcCF, tcVersionEnum); auto &languageInterface = tcServer->get_language_command_interface(); - languageInterface.set_language_code("en"); // This is the default, but you can change it if you want - languageInterface.set_country_code("US"); // This is the default, but you can change it if you want + languageInterface.set_language_code(settings->get_language_code()); + languageInterface.set_country_code(settings->get_country_code()); tcServer->initialize(); tcServer->set_task_totals_active(true); // TODO: make this dynamic based on status in AOG + // Announce TC Control Function Functionalities (PGN 64654 / 0xFC8E) per ISO 11783-12. + tcFunctionalities = std::make_unique(tcCF); + + // TC-BAS (Basic): mandatory baseline for any TC server + tcFunctionalities->set_functionality_is_supported( + isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerBasicServer, + 1, + true); + + // TC-GEO: DISABLED – variable rate / prescription maps are not supported + tcFunctionalities->set_functionality_is_supported( + isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerGeoServer, + 1, + false); + tcFunctionalities->set_task_controller_geo_server_option_state( + isobus::ControlFunctionFunctionalities::TaskControllerGeoServerOptions::PolygonBasedPrescriptionMapsAreSupported, + false); + + // TC-SC (Section Control): 1 boom, 64 sections – matching MyTCServer constructor limits + tcFunctionalities->set_functionality_is_supported( + isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerSectionControlServer, + 1, + true); + tcFunctionalities->set_task_controller_section_control_server_option_state( + 1, // numberOfSupportedBooms + 64); // numberOfSupportedSections + + // Register a ProcessData PGN callback to drive the bidirectional VersionPayload exchange + isobus::CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback( + static_cast(isobus::CANLibParameterGroupNumber::ProcessData), + Application::on_process_data_pgn_received, + this); + // Initialize speed and distance messages if (tecuCF && tecuCF->get_address_valid()) { @@ -332,6 +389,8 @@ bool Application::update() tcServer->request_measurement_commands(); tcServer->update(); + if (tcFunctionalities) + tcFunctionalities->update(); if (tecuFunctionalities) tecuFunctionalities->update(); if (speedMessagesInterface) @@ -471,6 +530,53 @@ void Application::send_task_controller_status_message() void Application::stop() { + isobus::CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback( + static_cast(isobus::CANLibParameterGroupNumber::ProcessData), + Application::on_process_data_pgn_received, + this); tcServer->terminate(); isobus::CANHardwareInterface::stop(); } + +void Application::on_process_data_pgn_received(const isobus::CANMessage &message, void *parent) +{ + if (nullptr == parent) + { + return; + } + auto *app = static_cast(parent); + + auto source = message.get_source_control_function(); + if (nullptr == source) + { + return; + } + + const auto &data = message.get_data(); + if (data.empty()) + { + return; + } + + // RequestVersion: both nibbles of byte 0 are zero (command = 0x0, element = 0x0) + if (data[0] == 0x00 && app->tcCF && app->tcCF->get_address_valid()) + { + auto nameFull = source->get_NAME().get_full_name(); + if (app->implementVersionRequested.find(nameFull) == app->implementVersionRequested.end()) + { + // Send RequestVersion toward the implement so we also learn its reported TC version + std::array requestVersionPayload = { + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + if (isobus::CANNetworkManager::CANNetwork.send_can_message( + 0xCB00, + requestVersionPayload.data(), + requestVersionPayload.size(), + app->tcCF, + source)) + { + app->implementVersionRequested.insert(nameFull); + } + } + } +} diff --git a/src/settings.cpp b/src/settings.cpp index a601b9f..608175c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,6 +21,9 @@ using json = nlohmann::json; +const std::string Settings::DEFAULT_LANGUAGE_CODE = "en"; +const std::string Settings::DEFAULT_COUNTRY_CODE = "US"; + bool Settings::load() { std::ifstream file(get_filename_path("settings.json")); @@ -84,6 +87,66 @@ bool Settings::load() aogHeartbeatEnabled = DEFAULT_AOG_HEARTBEAT_ENABLED; // Key not found, use default } + if (data.contains("tcVersion")) + { + try + { + int version = data["tcVersion"].get(); + if (version >= 0 && version <= 4) + { + tcVersion = static_cast(version); + } + else + { + std::cout << "[" << get_timestamp() << "] Invalid tcVersion " << version << ", using default " << static_cast(DEFAULT_TC_VERSION) << std::endl; + tcVersion = DEFAULT_TC_VERSION; + } + } + catch (const nlohmann::json::exception &e) + { + std::cout << "[" << get_timestamp() << "] Error parsing 'tcVersion': " << e.what() << std::endl; + tcVersion = DEFAULT_TC_VERSION; + } + } + else + { + tcVersion = DEFAULT_TC_VERSION; // Key not found, use default + } + + if (data.contains("languageCode")) + { + try + { + languageCode = data["languageCode"].get(); + } + catch (const nlohmann::json::exception &e) + { + std::cout << "[" << get_timestamp() << "] Error parsing 'languageCode': " << e.what() << std::endl; + languageCode = DEFAULT_LANGUAGE_CODE; + } + } + else + { + languageCode = DEFAULT_LANGUAGE_CODE; + } + + if (data.contains("countryCode")) + { + try + { + countryCode = data["countryCode"].get(); + } + catch (const nlohmann::json::exception &e) + { + std::cout << "[" << get_timestamp() << "] Error parsing 'countryCode': " << e.what() << std::endl; + countryCode = DEFAULT_COUNTRY_CODE; + } + } + else + { + countryCode = DEFAULT_COUNTRY_CODE; + } + return true; } @@ -93,6 +156,9 @@ bool Settings::save() const data["subnet"] = configuredSubnet; data["tecuEnabled"] = tecuEnabled; data["aogHeartbeatEnabled"] = aogHeartbeatEnabled; + data["tcVersion"] = tcVersion; + data["languageCode"] = languageCode; + data["countryCode"] = countryCode; std::ofstream file(get_filename_path("settings.json")); if (!file.is_open()) @@ -154,6 +220,59 @@ bool Settings::set_aog_heartbeat_enabled(bool enabled, bool save) return true; } +std::uint8_t Settings::get_tc_version() const +{ + return tcVersion; +} + +bool Settings::set_tc_version(std::uint8_t version, bool save) +{ + if (version > 4) + { + std::cout << "[" << get_timestamp() << "] Invalid TC version " << static_cast(version) << ", using default " << static_cast(DEFAULT_TC_VERSION) << std::endl; + tcVersion = DEFAULT_TC_VERSION; + } + else + { + tcVersion = version; + } + if (save) + { + return this->save(); + } + return true; +} + +std::string Settings::get_language_code() const +{ + return languageCode; +} + +bool Settings::set_language_code(std::string code, bool save) +{ + languageCode = code; + if (save) + { + return this->save(); + } + return true; +} + +std::string Settings::get_country_code() const +{ + return countryCode; +} + +bool Settings::set_country_code(std::string code, bool save) +{ + countryCode = code; + if (save) + { + return this->save(); + } + return true; +} + namespace { std::filesystem::path get_base_config_dir() diff --git a/src/task_controller.cpp b/src/task_controller.cpp index 6155074..ce6a2b0 100644 --- a/src/task_controller.cpp +++ b/src/task_controller.cpp @@ -244,14 +244,15 @@ bool ClientState::try_get_element_work_state(std::uint16_t elementNumber, bool & return false; } -MyTCServer::MyTCServer(std::shared_ptr internalControlFunction) : +MyTCServer::MyTCServer(std::shared_ptr internalControlFunction, + isobus::TaskControllerServer::TaskControllerVersion version) : TaskControllerServer(internalControlFunction, 1, // AOG limits to 1 boom 64, // AOG limits to 16 sections of unique width but can be 64 by using zones 64, // 64 channels for position based control isobus::TaskControllerOptions() .with_implement_section_control(), // We support section control - TaskControllerVersion::SecondEditionDraft) + version) { } From 0f719a32ab548d45aa96d554addf4c35123e1156 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 20:45:45 +0000 Subject: [PATCH 3/3] Remove tcFunctionalities/VersionPayload code and simplify settings.hpp comments per review --- include/app.hpp | 7 ---- include/settings.hpp | 12 +++---- src/app.cpp | 83 -------------------------------------------- 3 files changed, 6 insertions(+), 96 deletions(-) diff --git a/include/app.hpp b/include/app.hpp index c7c0858..6ceb977 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -10,10 +10,8 @@ #include #include -#include #include "isobus/hardware_integration/can_hardware_plugin.hpp" -#include "isobus/isobus/can_message.hpp" #include "isobus/isobus/isobus_functionalities.hpp" #include "isobus/isobus/isobus_speed_distance_messages.hpp" #include "isobus/isobus/nmea2000_message_interface.hpp" @@ -35,8 +33,6 @@ class Application private: void send_task_controller_status_message(); - static void on_process_data_pgn_received(const isobus::CANMessage &message, void *parent); - std::shared_ptr settings = std::make_shared(); boost::asio::io_context ioContext = boost::asio::io_context(); std::shared_ptr udpConnections = std::make_shared(settings, ioContext); @@ -48,11 +44,8 @@ class Application std::unique_ptr speedMessagesInterface; std::unique_ptr nmea2000MessageInterface; std::unique_ptr tecuFunctionalities; - std::unique_ptr tcFunctionalities; std::uint8_t nmea2000SequenceIdentifier = 0; std::uint32_t lastJ1939SpeedTransmit = 0; std::uint32_t lastTCStatusTransmit = 0; std::int32_t lastSpeedValue = 0; - - std::set implementVersionRequested; ///< NAMEs of implements we have already sent a RequestVersion to }; diff --git a/include/settings.hpp b/include/settings.hpp index c5c9055..2bcbf36 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -78,13 +78,13 @@ class Settings bool set_aog_heartbeat_enabled(bool enabled, bool save = true); /** - * @brief Get the configured TC ISO 11783-10 version + * @brief Get the configured TC version * @return The configured version (0-4, default 3) */ std::uint8_t get_tc_version() const; /** - * @brief Set the TC ISO 11783-10 version + * @brief Set the TC version * @param version The version to set (0=DIS, 1=FDIS.1, 2=FirstEdition, 3=SecondEditionDraft, 4=SecondPublishedEdition) * @param save Whether or not to save the settings to file * @return True if the version was set successfully, false otherwise @@ -92,28 +92,28 @@ class Settings bool set_tc_version(std::uint8_t version, bool save = true); /** - * @brief Get the configured language code (ISO 639-1) + * @brief Get the configured language code * @return The language code (default "en") */ std::string get_language_code() const; /** * @brief Set the language code - * @param code The ISO 639-1 language code + * @param code The language code * @param save Whether or not to save the settings to file * @return True if the setting was set successfully, false otherwise */ bool set_language_code(std::string code, bool save = true); /** - * @brief Get the configured country code (ISO 3166-1 alpha-2) + * @brief Get the configured country code * @return The country code (default "US") */ std::string get_country_code() const; /** * @brief Set the country code - * @param code The ISO 3166-1 alpha-2 country code + * @param code The country code * @param save Whether or not to save the settings to file * @return True if the setting was set successfully, false otherwise */ diff --git a/src/app.cpp b/src/app.cpp index 65da80d..18fb8aa 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -10,7 +10,6 @@ #include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" -#include "isobus/isobus/can_general_parameter_group_numbers.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/isobus_preferred_addresses.hpp" #include "isobus/isobus/isobus_standard_data_description_indices.hpp" @@ -205,39 +204,6 @@ bool Application::initialize() tcServer->initialize(); tcServer->set_task_totals_active(true); // TODO: make this dynamic based on status in AOG - // Announce TC Control Function Functionalities (PGN 64654 / 0xFC8E) per ISO 11783-12. - tcFunctionalities = std::make_unique(tcCF); - - // TC-BAS (Basic): mandatory baseline for any TC server - tcFunctionalities->set_functionality_is_supported( - isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerBasicServer, - 1, - true); - - // TC-GEO: DISABLED – variable rate / prescription maps are not supported - tcFunctionalities->set_functionality_is_supported( - isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerGeoServer, - 1, - false); - tcFunctionalities->set_task_controller_geo_server_option_state( - isobus::ControlFunctionFunctionalities::TaskControllerGeoServerOptions::PolygonBasedPrescriptionMapsAreSupported, - false); - - // TC-SC (Section Control): 1 boom, 64 sections – matching MyTCServer constructor limits - tcFunctionalities->set_functionality_is_supported( - isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerSectionControlServer, - 1, - true); - tcFunctionalities->set_task_controller_section_control_server_option_state( - 1, // numberOfSupportedBooms - 64); // numberOfSupportedSections - - // Register a ProcessData PGN callback to drive the bidirectional VersionPayload exchange - isobus::CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback( - static_cast(isobus::CANLibParameterGroupNumber::ProcessData), - Application::on_process_data_pgn_received, - this); - // Initialize speed and distance messages if (tecuCF && tecuCF->get_address_valid()) { @@ -389,8 +355,6 @@ bool Application::update() tcServer->request_measurement_commands(); tcServer->update(); - if (tcFunctionalities) - tcFunctionalities->update(); if (tecuFunctionalities) tecuFunctionalities->update(); if (speedMessagesInterface) @@ -530,53 +494,6 @@ void Application::send_task_controller_status_message() void Application::stop() { - isobus::CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback( - static_cast(isobus::CANLibParameterGroupNumber::ProcessData), - Application::on_process_data_pgn_received, - this); tcServer->terminate(); isobus::CANHardwareInterface::stop(); } - -void Application::on_process_data_pgn_received(const isobus::CANMessage &message, void *parent) -{ - if (nullptr == parent) - { - return; - } - auto *app = static_cast(parent); - - auto source = message.get_source_control_function(); - if (nullptr == source) - { - return; - } - - const auto &data = message.get_data(); - if (data.empty()) - { - return; - } - - // RequestVersion: both nibbles of byte 0 are zero (command = 0x0, element = 0x0) - if (data[0] == 0x00 && app->tcCF && app->tcCF->get_address_valid()) - { - auto nameFull = source->get_NAME().get_full_name(); - if (app->implementVersionRequested.find(nameFull) == app->implementVersionRequested.end()) - { - // Send RequestVersion toward the implement so we also learn its reported TC version - std::array requestVersionPayload = { - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; - if (isobus::CANNetworkManager::CANNetwork.send_can_message( - 0xCB00, - requestVersionPayload.data(), - requestVersionPayload.size(), - app->tcCF, - source)) - { - app->implementVersionRequested.insert(nameFull); - } - } - } -}