Repository navigation
Splitting out an embeddable core so changes flow to AgOpenWeb's HAT daemon #101
Description
The team asked how this project could be arranged so that changes here reach AgOpenWeb's HAT daemon more easily. This is the answer from that side. It is based on the code at 4ede403; none of it has been built as a proof.
Where things stand
The HAT daemon runs this project's section control task controller inside its own process on a Raspberry Pi CM4. It talks to the app through its own PGN link, with no AgIO and no UDP hop.
src/task_controller.cpp,include/task_controller.hpp,resources/AOG_TC.iopandAOG_TC.iop.hare used unmodified, byte for byte. A fix in those files reaches the daemon as a file copy.src/app.cpp(1,400 lines) could not be used. Its ISOBUS behaviour is mixed with things only this program has: the UDP link, Boost.Asio, the settings file, the CAN driver choice. The daemon re-implements that behaviour in two files of its own, so a fix inapp.cpphas to be re-done there by hand.
Suggestions, in order of value
-
Move the ISOBUS behaviour out of
app.cppinto a core with no UDP, Boost or settings file in it. The pieces the daemon had to re-implement are:- starting the server after the address claim, and the 2-second status message;
- the rule for when section control is on;
- the tractor ECU role and its speed messages;
- driving the status screen on a virtual terminal (
setup_vt_client,update_vt_section_map,update_vt_status_stringsand the rest); - packing the section heartbeat (PGN 0xF0).
-
Give that core a small interface to the outside, in place of calls to
UdpConnections. Inputs: the section bits, section control on/off, speed. Outputs: the implement's reported sections and the status strings. The UDP layer becomes one implementation of it; the daemon would supply another. -
Build the core as its own CMake library target, with the executable linking it. The daemon would then fetch this repository at a pinned commit, as it already does for AgIsoStack++, and link the library, with no files copied.
-
Pass in the two things
task_controller.cpptakes from the rest of the program. It uses one settings call,Settings::get_filename_path(where to store implement descriptions), andlog()in 32 places. A directory given at construction and a log callback, or AgIsoStack++'s own logger, would do. The daemon currently supplies stand-insettings.hppandlogging_utils.hppheaders to satisfy these. -
Make the core compile cleanly on its own.
task_controller.cppusesstd::find_ifwithout#include <algorithm>, which GCC on Linux rejects unless the include is forced from the build. Building the core in CI on Linux with-Wall -Wextrawould keep it that way. -
Add an end-to-end test against AgIsoStack++'s simulated implement on a virtual CAN bus. The tests here cover the object pool and the logger. The daemon has three probes that do this (connect, sections each way, section control off and on, implement lost and replaced, the terminal screen) and they could be offered as a starting point.
-
Fix the slow section lookup (get_section_actual_state() is slow with many sections: 2.3 ms a call on a Raspberry Pi #100) inside the core, so the daemon can drop the subclass it uses to work around it.
A smaller version
If 1 to 3 is more than you want to take on, 4 and 5 alone remove every workaround the daemon has for task_controller.cpp and leave app.cpp as it is.
Glad to help with any of it, including pull requests.
Activity
gunicsba commentedon Oct 12, 2026
Thanks for writing this up. Everything here checks out against the code. Here's how we plan to take it, in order.
Done (in review)
MyTCServersubclass. A run on your CM4 with the 64-section simulator would be very welcome.<algorithm>include. We already build on Linux in CI. The include only went unnoticed because one of our other headers pulls it in, which your stand-in headers don't.Next
Settings::get_filename_pathcalls andlog()intask_controller.cppwith a storage directory and a log callback passed in from outside, so you can drop your stand-insettings.hppandlogging_utils.hpp. This is small and doesn't conflict with anything open.task_controller.cppon its own on Linux with-Wall -Wextrain CI, so it stays self-contained.Later, once the open PRs land (items 1–3)
About ten open PRs touch
app.cpp: TECU, guidance track, GNSS quality, track control, hydration snapshots and others. Moving the ISOBUS behaviour out ofapp.cppnow would conflict with nearly all of them. Once they're merged, we'd extract the core in steps, each as its own PR, starting with the parts that drift most for you:Then the small input/output interface in place of
UdpConnections, and a CMake library target you can fetch at a pinned commit.Item 6: yes please. Your three probes against the AgIsoStack++ simulator on a virtual CAN bus would be a great starting point, and they would cover what our unit tests can't.
One thing to know now: #102 switches section control on automatically when an implement connects. The logic is in
MyTCServer, so it reaches you as a file copy. But the server starts with it off, and ourapp.cppswitches it on from settings. To get the same behaviour in the daemon, calltcServer->set_initial_section_control_enabled(true)after constructing the server.