diff --git a/include/settings.hpp b/include/settings.hpp index 06f0278..04d052a 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -98,6 +98,14 @@ class Settings */ bool set_vt_enabled(bool enabled, bool save = true); + /// @brief Check whether every implement section follows AOG's section 1. + /// @details For implements that declare several sections (or booms) that really are one, such as + /// several dispensers on one working width. AOG then sees the implement as a single section. + bool is_all_sections_follow_first() const; + + /// @brief Set whether every implement section follows AOG's section 1. + bool set_all_sections_follow_first(bool enabled, bool save = true); + /** * @brief Get the configured TC version * @return The configured version (0-4, default 3) @@ -161,6 +169,7 @@ class Settings constexpr static bool DEFAULT_NMEA_SEND_ENABLED = true; constexpr static bool DEFAULT_AOG_HEARTBEAT_ENABLED = true; constexpr static bool DEFAULT_VT_ENABLED = true; + constexpr static bool DEFAULT_ALL_SECTIONS_FOLLOW_FIRST = false; 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; @@ -169,6 +178,7 @@ class Settings bool nmeaSendEnabled = DEFAULT_NMEA_SEND_ENABLED; bool aogHeartbeatEnabled = DEFAULT_AOG_HEARTBEAT_ENABLED; bool vtEnabled = DEFAULT_VT_ENABLED; + bool allSectionsFollowFirst = DEFAULT_ALL_SECTIONS_FOLLOW_FIRST; std::uint8_t tcVersion = DEFAULT_TC_VERSION; std::string languageCode = DEFAULT_LANGUAGE_CODE; std::string countryCode = DEFAULT_COUNTRY_CODE; diff --git a/readme.md b/readme.md index 8c67159..e769bf8 100644 --- a/readme.md +++ b/readme.md @@ -64,6 +64,7 @@ AOG-TaskController reads its configuration from a `settings.json` file located i | `nmeaSendEnabled` | `bool` | `true` | Enables cyclic NMEA2000 COG/SOG transmission. Requires `tecuEnabled`. | | `aogHeartbeatEnabled` | `bool` | `true` | Sends the 100 ms zero-section heartbeat when no implement is connected. Disable for AgOpenGPS versions before v6.8.2 beta 5. | | `vtEnabled` | `bool` | `true` | Enables the ISOBUS Virtual Terminal UI. | +| `allSectionsFollowFirst` | `bool` | `false` | Every implement section follows AOG's section 1, and AOG sees the implement as one section (on while any section is on). For implements that declare several sections or booms that really are one, such as several dispensers on one working width. Set AOG to 1 section. Applies to every connected implement: with two section-controlled implements, both follow section 1 and AOG gets one combined report. | | `tcVersion` | `integer` | `3` | Task Controller version code, from `0` through `4`; `3` is Second Edition Draft. | | `languageCode` | `string` | `"en"` | Two-character language code advertised on ISOBUS. | | `countryCode` | `string` | `"US"` | Two-character country code advertised on ISOBUS. | diff --git a/src/app.cpp b/src/app.cpp index a697e8b..82d6ba4 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -507,6 +507,12 @@ void Application::setup_udp_connections() sectionDemand = sectionStates; sectionDemandChangesSinceLog++; } + if (settings->is_all_sections_follow_first()) + { + // The implement's sections are really one: all follow AOG's section 1 (sectionDemand above stays as AOG sent it). + // PGN 229 carries 64 bits; cover every section a DDOP can declare (up to 255), not just the first 64. + sectionStates.assign(256, sectionStates.front()); + } tcServer->update_section_states(sectionStates); } else if (pgn == 0xF1 && !data.empty()) // 241 - Section Control @@ -728,6 +734,9 @@ bool Application::update() if (isobus::SystemTiming::time_expired_ms(lastHeartbeatTransmit, 100)) { bool anyClientWithSections = false; + const bool followFirst = settings->is_all_sections_follow_first(); + bool followFirstEnabled = false; + bool followFirstOn = false; for (auto &client : tcServer->get_clients()) { auto &state = client.second; @@ -739,6 +748,19 @@ bool Application::update() } anyClientWithSections = true; + + if (followFirst) + { + // AOG is set up with one section, and section 1 drives every connected implement: + // collect them all into one report, on while any implement section is on + followFirstEnabled = followFirstEnabled || state.is_section_control_enabled(); + for (std::uint8_t i = 0; i < state.get_number_of_sections(); i++) + { + followFirstOn = followFirstOn || (state.get_section_actual_state(i) == SectionState::ON); + } + continue; + } + std::vector data = { state.is_section_control_enabled(), state.get_number_of_sections() }; std::uint8_t sectionIndex = 0; @@ -758,6 +780,12 @@ bool Application::update() udpConnections->send(0x80, 0xF0, data); } + if (followFirst && anyClientWithSections) + { + std::vector oneSection = { followFirstEnabled, 1, static_cast(followFirstOn ? 1 : 0) }; + udpConnections->send(0x80, 0xF0, oneSection); + } + // If no clients with sections, send heartbeat with 0 sections if (settings->is_aog_heartbeat_enabled() && !anyClientWithSections) { @@ -1029,7 +1057,9 @@ void Application::check_section_demand() 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]); + // With allSectionsFollowFirst, AOG has one section: only its bit is demand, any others are padding + const std::uint8_t demandIndex = settings->is_all_sections_follow_first() ? 0 : i; + demanded = demanded || (arriving && (demandIndex < sectionDemand.size()) && sectionDemand[demandIndex]); } const bool working = (actual.end() != std::find(actual.begin(), actual.end(), true)); diff --git a/src/settings.cpp b/src/settings.cpp index 960927a..cdc73d8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -70,6 +70,7 @@ bool Settings::load() nmeaSendEnabled = loadBoolean("nmeaSendEnabled", DEFAULT_NMEA_SEND_ENABLED); aogHeartbeatEnabled = loadBoolean("aogHeartbeatEnabled", DEFAULT_AOG_HEARTBEAT_ENABLED); vtEnabled = loadBoolean("vtEnabled", DEFAULT_VT_ENABLED); + allSectionsFollowFirst = loadBoolean("allSectionsFollowFirst", DEFAULT_ALL_SECTIONS_FOLLOW_FIRST); if (data.contains("tcVersion")) { @@ -143,6 +144,7 @@ bool Settings::save() const data["nmeaSendEnabled"] = nmeaSendEnabled; data["aogHeartbeatEnabled"] = aogHeartbeatEnabled; data["vtEnabled"] = vtEnabled; + data["allSectionsFollowFirst"] = allSectionsFollowFirst; data["tcVersion"] = tcVersion; data["languageCode"] = languageCode; data["countryCode"] = countryCode; @@ -261,6 +263,17 @@ bool Settings::set_vt_enabled(bool enabled, bool save) return set_boolean(vtEnabled, enabled, save); } +bool Settings::is_all_sections_follow_first() const +{ + std::scoped_lock lock(settingsMutex); + return allSectionsFollowFirst; +} + +bool Settings::set_all_sections_follow_first(bool enabled, bool save) +{ + return set_boolean(allSectionsFollowFirst, enabled, save); +} + bool Settings::is_aog_heartbeat_enabled() const { std::scoped_lock lock(settingsMutex);