From 0eb6a662dba9ee2df72dd97c09bc047c8284c597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunics=20Bal=C3=A1zs?= Date: Wed, 14 Oct 2026 08:46:47 +0200 Subject: [PATCH] Log AOG's section demand and warn when an implement works without it When a spreader in automatic ran full width without receiving any section setpoint, the log could not tell whether AOG sent all-off section states or none at all, because PGN 229 was never logged. - Log AOG's section demand (PGN 229) when it changes, as on-ranges ("4 on: 3-6"), at most every 200 ms, counting changes in between. - Log when PGN 229 starts and stops arriving; AOG sends it only while a field is open. - When an implement is in automatic with sections on, but AOG has asked for no section for 10 s, log a warning and show it in AOG: either AOG's section Auto button is off (AOG turns it off on every field open/close) or no field is open. Co-Authored-By: Claude Opus 5.5 --- include/app.hpp | 19 +++++++ src/app.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/include/app.hpp b/include/app.hpp index 6ee3ca7..9c76d06 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -54,6 +54,13 @@ class Application void send_task_controller_status_message(); + /// @brief Logs AOG's section demand (PGN 229) when it changed, at most every SECTION_DEMAND_LOG_INTERVAL_MS + void log_section_demand(); + /// @brief Logs when PGN 229 starts and stops, and warns when an implement in automatic is working + /// while AOG asks for no sections — e.g. AOG's own section Auto button is off after a field change + void check_section_demand(); + static std::string describe_section_demand(const std::vector &demand, std::uint8_t numberOfSections); + bool setup_can_hardware(); bool setup_control_functions(); void setup_task_controller_server(); @@ -107,6 +114,18 @@ class Application static constexpr std::uint32_t GNSS_QUALITY_TIMEOUT_MS = 2000; ///< No PGN 0xD6 for this long = fix quality unknown std::uint32_t lastDistanceMm = 0; std::uint32_t lastAogPacketMs = 0; + + static constexpr std::uint32_t SECTION_DEMAND_LOG_INTERVAL_MS = 200; + static constexpr std::uint32_t SECTION_DEMAND_TIMEOUT_MS = 2000; ///< AOG sends PGN 229 every frame while a field is open + static constexpr std::uint32_t WORKING_WITHOUT_DEMAND_WARNING_MS = 10000; + std::vector sectionDemand; ///< Last section states from AOG (PGN 229) + std::vector loggedSectionDemand; ///< The section demand last written to the log + std::uint32_t sectionDemandChangesSinceLog = 0; + std::uint32_t lastSectionDemandMs = 0; ///< When PGN 229 was last received, 0 = never + std::uint32_t lastSectionDemandLogMs = 0; + bool sectionDemandArriving = false; + std::map workingWithoutDemandSinceMs; ///< Per client NAME + std::map workingWithoutDemandWarned; ///< Per client NAME std::uint32_t vtDisconnectedSinceMs = 0; std::uint32_t lastVtStatusUpdateMs = 0; std::uint32_t lastVtSectionUpdateMs = 0; diff --git a/src/app.cpp b/src/app.cpp index ed61f32..b10779f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -501,6 +501,12 @@ void Application::setup_udp_connections() sectionStates.push_back(data[j] & (1 << i)); } } + lastSectionDemandMs = (0 == lastAogPacketMs) ? 1 : lastAogPacketMs; + if (sectionStates != sectionDemand) + { + sectionDemand = sectionStates; + sectionDemandChangesSinceLog++; + } tcServer->update_section_states(sectionStates); } else if (pgn == 0xF1 && !data.empty()) // 241 - Section Control @@ -643,6 +649,14 @@ bool Application::update() if (vtClient) update_vt_client(); + log_section_demand(); + static std::uint32_t lastSectionDemandCheckMs = 0; + if (isobus::SystemTiming::time_expired_ms(lastSectionDemandCheckMs, 1000)) + { + check_section_demand(); + lastSectionDemandCheckMs = isobus::SystemTiming::get_timestamp_ms(); + } + // Check for TC address conflicts every 15 seconds static std::uint32_t lastConflictCheck = 0; if (isobus::SystemTiming::time_expired_ms(lastConflictCheck, 15000)) @@ -933,6 +947,124 @@ Application::ImplementDetails Application::derive_implement_details(ClientState return details; } +std::string Application::describe_section_demand(const std::vector &demand, std::uint8_t numberOfSections) +{ + // Sections that are on, as ranges: "4 on: 3-6" + std::ostringstream ranges; + std::size_t onCount = 0; + std::size_t count = (0 == numberOfSections) ? demand.size() : std::min(numberOfSections, demand.size()); + for (std::size_t i = 0; i < count; i++) + { + if (!demand[i] || ((i > 0) && demand[i - 1])) + { + onCount += demand[i] ? 1 : 0; + continue; + } + std::size_t last = i; + while ((last + 1 < count) && demand[last + 1]) + { + last++; + } + ranges << ((0 == onCount) ? "" : ",") << (i + 1); + if (last > i) + { + ranges << "-" << (last + 1); + } + onCount++; + } + if (0 == onCount) + { + return "all off"; + } + return std::to_string(onCount) + " on: " + ranges.str(); +} + +void Application::log_section_demand() +{ + if ((0 == sectionDemandChangesSinceLog) || + !isobus::SystemTiming::time_expired_ms(lastSectionDemandLogMs, SECTION_DEMAND_LOG_INTERVAL_MS)) + { + return; + } + + if (sectionDemand != loggedSectionDemand) + { + std::uint8_t numberOfSections = 0; + for (auto &client : tcServer->get_clients()) + { + numberOfSections = std::max(numberOfSections, client.second.get_number_of_sections()); + } + + std::ostream &line = log("AOG"); + line << "Section demand (PGN 229): " << describe_section_demand(sectionDemand, numberOfSections); + if (sectionDemandChangesSinceLog > 1) + { + line << " (" << (sectionDemandChangesSinceLog - 1) << " intermediate changes not logged)"; + } + line << std::endl; + loggedSectionDemand = sectionDemand; + } + sectionDemandChangesSinceLog = 0; + lastSectionDemandLogMs = isobus::SystemTiming::get_timestamp_ms(); +} + +void Application::check_section_demand() +{ + const bool arriving = (0 != lastSectionDemandMs) && !isobus::SystemTiming::time_expired_ms(lastSectionDemandMs, SECTION_DEMAND_TIMEOUT_MS); + if (arriving != sectionDemandArriving) + { + sectionDemandArriving = arriving; + log("AOG") << (arriving ? "Section demand (PGN 229) started arriving" + : "Section demand (PGN 229) stopped arriving (AOG sends it only while a field is open)") + << std::endl; + } + + const std::uint32_t nowMs = isobus::SystemTiming::get_timestamp_ms(); + for (auto &client : tcServer->get_clients()) + { + auto &state = client.second; + const std::uint64_t name = client.first->get_NAME().get_full_name(); + std::vector actual; + bool demanded = false; + for (std::uint8_t i = 0; i < state.get_number_of_sections(); i++) + { + actual.push_back(SectionState::ON == state.get_section_actual_state(i)); + demanded = demanded || (arriving && (i < sectionDemand.size()) && sectionDemand[i]); + } + const bool working = (actual.end() != std::find(actual.begin(), actual.end(), true)); + + std::ostringstream clientText; + clientText << "0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(client.first->get_address()); + + if (!state.is_section_control_enabled() || !working || demanded) + { + if (workingWithoutDemandWarned[name]) + { + log("AOG") << "Client " << clientText.str() << " sections follow AOG's section control again" << std::endl; + } + workingWithoutDemandSinceMs.erase(name); + workingWithoutDemandWarned.erase(name); + continue; + } + + auto since = workingWithoutDemandSinceMs.try_emplace(name, nowMs).first; + if (!workingWithoutDemandWarned[name] && ((nowMs - since->second) >= WORKING_WITHOUT_DEMAND_WARNING_MS)) + { + workingWithoutDemandWarned[name] = true; + log("AOG") << "WARNING: client " << clientText.str() << " has been in automatic and working for " + << (WORKING_WITHOUT_DEMAND_WARNING_MS / 1000) << " s (actual: " + << describe_section_demand(actual, state.get_number_of_sections()) << ") but AOG " + << (arriving ? "asks for no sections. Is AOG's section Auto button on?" + : "sends no section states. Is a field open in AOG?") + << std::endl; + send_hardware_message(arriving ? "Implement working but AOG sections are off: turn on AOG's section Auto" + : "Implement working but no field is open in AOG", + 20, + HW_MSG_ALERT); + } + } +} + void Application::send_task_controller_status_message() { // ISO 11783-10 B.8.1 Task Controller Status message