Repository navigation
Conversation
Parses the fix-quality byte from AOG's GPS/IMU PGN 0xD6 (source 0x7C) and pushes it to every client whose DDOP declares DDI 514 (GNSSQuality), every 250 ms. - AOG fix values 0-8 are forwarded unchanged (they are the NMEA 2000 GNSS Method values DDI 514 uses, including 6=Estimated, 7=Manual, 8=Simulated). Values above 8 and a stale/missing 0xD6 map to 1 (weakest real fix) rather than 0, since some implements gate TRACK and section control on GNSS quality being non-zero. - DDI 514 is mapped to its element in request_measurement_commands() alongside the other DDI mappings; mapping only, no OnChange subscription, since it is a value the TC pushes rather than one it reads back. Only implements that declare DDI 514 as settable are mapped, since a non-settable DDI cannot accept a Value command. - docs/PROTOCOL.md and the readme document the new PGN and DDI. The readme gets its own "Guidance data sent to implements" subsection so follow-up guidance PRs append bullets there instead of all editing the shared capabilities list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Parses AOG's guidance track context (PGN 0xF4) and field name (PGN 0xF3) and pushes the current track to every client whose DDOP declares the DDIs, every 250 ms while AOG has a valid track: - 507 GuidanceTrackSequenceNumber: advances whenever the track number or the reference line changes (a line switch can land on the same track index). - 508 UniqueGuidanceReferenceLineID: AOG's 16-bit line ID in the low 16 bits and a persistent per-field index in the high 16 bits, so it is unique across fields. The index comes from FieldRegistry (field_registry.csv next to settings.json). - 509-511 actual/right/left track number, with the +1 offset field testing showed implements need to stay in phase with AOG's on/off state. - 512 GuidanceLineSwathWidth: AOG's track spacing (tool width minus overlap), read from the optional bytes 10-11 of PGN 0xF4. Not sent when AOG omits it or reports 0, so older AOG builds keep working. - 513 GuidanceLineDeviation from AOG's XTE. The DDIs are mapped in request_measurement_commands() by extending the loop that maps GNSS quality, so, as for DDI 514, only DDIs an implement declares as settable are mapped. Nothing is gated on track-control negotiation: the TC pushes these values and any implement declaring them receives them. Adds unit tests for the PGN 0xF4 parser (sequence freshness and wrap, offset, validity, swath width). test_guidance_track is a dependency of iop_validator so the validation workflow, which builds only that target, builds it too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tate (DDI 515) Negotiates a track control level with implements and switches their track control on and off together with AgOpenGPS's Auto command: 1. The implement reports the levels it supports as a bitmask in DDI 505. 2. The TC writes DDI 506 (an enum, unlike 505) back. Only Level 1 is implemented, so it asks for Level 1 whenever the implement supports it, even if it also advertises Level 2 or 3. 3. The implement's DDI 506 echo completes the negotiation. DDI 515 (TrackControlState) is written only to clients that completed the negotiation, each time AOG's section control mode arrives. The guidance data from the previous PR is not gated on any of this. The DDIs are mapped with find_owning_element_number() from the guidance data mapping, plus an OnChange subscription because the implement reports them. Also announces functionality 27 (Task Controller TRACK server) in PGN 64654 and shows the track state and the implement's supported levels on the VT status page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds TractorFacilities: builds and sends the PGN 65033 facility bitmask (ISO 11783-7 B.24.3), advertising which PGNs the TECU actually broadcasts (ground- and wheel-based speed, distance and direction). It is sent once on power-up and on PGN 65033 request. PGN 65032 (Required Tractor Facilities) requests are logged at debug level without changing the response. Also adds diagnostic logging for Request-for-Repetition-Rate (PGN 0xCC00), both the addressed and the broadcast form, and unit tests for the facilities bitmask encode/decode logic (tools/test_tractor_facilities.cpp, registered via CTest). The IOP validation workflow built only the iop_validator target and then ran all of ctest, so it now builds test_tractor_facilities as well. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolve conflict in request_measurement_commands(): keep the GNSS quality (DDI 514) mapping and switch its output, plus the two GNSS prints in app.cpp, to log() to match the async logging from #79. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nnouncement Resolve conflict in request_measurement_commands(): keep this branch's generalized guidance-DDI mapping and use log() as the base branch now does. Also switch the FieldRegistry and TRACK prints to log() to match the async logging from #79. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…levels Resolve conflict in request_measurement_commands(): keep the track control DDI subscriptions and switch their output to log() to match the async logging from #79. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guidance_track_context.cpp now logs through log(), which needs async_log.cpp; the standalone test target only compiled the parser, so the IOP validation build failed to link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…levels Picks up the test_guidance_track link fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Unknown start of message" was printed twice per packet: both UDP sockets listen on port 8888 (one on the AOG subnet address, one on all interfaces), so every broadcast arrives on both. Log it through UdpConnections::log_unknown_start(), which skips an identical report (same sender, same leading bytes) within 50 ms and includes the sender IP:port and up to 16 bytes in hex, so the source can be found. "Received a Request to Send (RTS) message with a global destination" is a stack warning without a source address, printed for every frame. The CAN stack log sink now treats it as Debug. The application counts such RTS frames per source address from the CAN frame received event and logs one summary line per minute when there were any. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A client whose DDOP has no section elements, such as a tractor ECU that
only describes hitch or GNSS geometry, has nothing to section-control.
It was reported with "WARNING: No supported section control method
detected!", which sent people looking for a problem that is not there.
Log it as info ("Non-section client") when the DDOP has zero sections;
DDOPs that do have sections but no supported method still get the
warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until now an implement stayed in manual mode after connecting until the operator pressed AOG's ISOBUS SC button, which sends PGN 241. The TC now remembers the section control state it wants and applies it to each client 2 s after its pool is activated: it sets the local auto flag and writes DDI 160 when the implement declares it. The initial state comes from the new settings.json key "sectionControlOnConnect" (default true). Pressing AOG's button still switches all clients and also becomes the state for implements that connect later. The implement can still decline or go back to manual; its DDI 160 report is followed as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he next one The receive loop kept unparsed bytes in a static buffer between datagrams and never reset its read index after moving leftover bytes to the front. A packet shorter than 8 bytes (AOG's 7-byte section control request, PGN 241) waited in the buffer for the next datagram, and the packet after it was then read from the wrong offset, logged as "Unknown start of message" and dropped. It also read the 2 start bytes with two unsequenced index++ in one expression, and did not check that a whole packet was present before handing it on. AgIO never splits a packet across datagrams, so each datagram is now parsed on its own by aog_packet::parse_datagram (header-only, unit tested), shared by the AOG and address detection sockets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An implement only reconnects when it sees no TC Status for 6 s. Restarting the TC takes 2-3 s, and both our TC Status and the stack's (sent from TaskControllerServer::update()) were back on the bus before that, so the implement kept sending process data as if nothing happened. The new TC did not know it and NACKed every message; in the field a spreader stayed cut off for 20 minutes across three restarts, until the tractor was power-cycled. - Startup: hold TC Status, and the TC server update that sends the stack's copy, for 6.5 s, so clients of the previous run time out and reconnect. - Runtime: watch for Client Task and Value messages addressed to us from a CF the stack has no active client for. Such a client still thinks it is connected; hold TC Status for 6.5 s so it starts over. A 30 s cooldown lets reconnecting clients finish, and after 3 attempts for the same NAME the TC stops and asks for a power-cycle instead of disturbing the other clients. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New sectionControlOnFieldOpen setting (default on): when AOG opens a field, the TC switches section control on as if AOG's SC button was pressed (the same update_section_control_enabled(true) that PGN 241 "enabled" triggers). AOG sends the field name (PGN 0xF3) on every field open and close, and again when the TC reconnects. Older AOG versions do not, so PGN 229 starting after a 2 s gap (AOG sends it only while a field is open) counts as a field opening too. Both report the same opening, so it is acted on at most once per 5 s. Field open and close are logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The field log showed datagrams with 2 or 4 bytes after a complete packet, and others that are not AOG packets at all (one starts with "$P"). The log only said how many bytes were dropped, so the sender could not be identified. Log the sender's address and port, and up to 64 bytes as hex with a "|" where parsing stopped, followed by the printable characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Field-open detection now uses lastSectionDemandMs and SECTION_DEMAND_TIMEOUT_MS from #108 in place of its own lastSectionStatesMs and FIELD_CLOSED_GAP_MS, which recorded the same PGN 229 arrival with the same 2 s gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ISO 11783-9:2012 4.4.2.1: with several Tractor ECUs on the implement bus, function instance 0 is the primary, and a higher instance must not send what a lower one provides, requests PGN 65033 from it, and sets only the facility bits it lacks. - At startup, claim 0xF0 as the primary only if no other Tractor ECU answered the TC's address claim. Otherwise claim an arbitrary address as a secondary with the next function instance. - The TECU NAME is now arbitrary-address-capable, its most significant bit, so a primary loses a contest for 0xF0 to a tractor's fixed-address Tractor ECU instead of taking the address from it. - Once a second, check for other Tractor ECUs. A primary steps down when another instance 0 appears or it lost 0xF0, rebuilding the control function without blocking the main loop. - A secondary sends nothing until the lower instances answered PGN 65033 (2.5 s at most), then sends each of ground-based, wheel-based and machine selected speed, J1939 vehicle direction/speed and NMEA 2000 COG/SOG only if none of them advertises it or sent it in the last 3 s. PGN 65033 advertises exactly what it sends, and is re-sent on change. - The primary now advertises the selected speed facility (byte 5 bit 3), since the TECU sends machine selected speed (PGN 61474). - TractorFacilities gets a destructor that removes its callbacks, and set_provided_facilities(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tecuEnabled off is what operators set when the tractor has its own Tractor ECU, and so far it meant no TECU at all. Now it starts our TECU as a secondary, never at 0xF0: it listens to the tractor's Tractor ECU and sends only what that one lacks, or everything when there is none. tecuEnabled on still makes AOG the primary, stepping down if a tractor TECU turns up after all. The VT label shows who leads: "TECU: AOG" or "TECU: tractor" (pool length 23). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the upstream fixes since 1d081d9, among them: - #834 take a control function offline only after two missed address claim requests - #857 ignore short TC process data frames and truncated DDOP objects - #818 TC server: width editable flag, working set logging, version request - #798 VT client terminate/receive safe against a restart from another thread - #808 send a TP session's first frame once when sending from another thread - #781 bounds-check VT Change String Value, log the VT's error response - #724 keep a new partner's address claim credit when it replaces a CF - #789 Windows WinUSB driver for candleLight/gs_usb adapters (not enabled here) No TC code changes needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AgIsoStack++ #789 added a WinUSB driver for adapters running candleLight (gs_usb) firmware, such as CANable 2 and the WeAct USB2CANFD. Build it into the Windows TC and select it with --can_adapter=candlelight (or gs_usb). --can_channel picks the adapter when several are plugged in (1 = first, empty = 1). It uses Windows' own WinUSB, so no vendor DLL ships with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TC number is the TC NAME's function instance + 1 (ISO 11783-10), and was fixed at TC #1. Add a tcNumber setting (1-32, default 1) so the TC can run as TC #2 or higher next to a tractor's own TC. Out-of-range values fall back to 1. It is part of the NAME, so it takes effect after a restart. The TECU keeps function instance 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup_tecu_interfaces() said a secondary waits, but plan_tecu_messages() with no knowledge of the other Tractor ECUs answers "send everything". A secondary doubled the messages of the tractor for up to 2.5 s after claiming an address, which ISO 11783-9 4.4.2.1 c forbids. Start from an empty plan instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ISO 11783-7 B.1 lists Time/Date as "on request", so the TECU answers requests and never sends it unrequested. The answer is UTC from the PC clock plus the local offset, which now includes daylight saving time. It follows the TECU plan: the primary always answers, and a secondary only when no lower Tractor ECU advertises or sends Time/Date (ISO 11783-9 4.4.2.1). The facility bit in PGN 65033 follows the same state. Time/Date seen from another Tractor ECU counts for 30 s rather than 3 s, because it may only appear on request. The answer lives in TractorFacilities' PGN request handling rather than in TimeDateInterface: that registers a global receive callback, which the stack can still call once after the TECU is rebuilt. Replaces the on-request half of #87, without its 10 s broadcast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tegration # Conflicts: # src/app.cpp
…-integration # Conflicts: # src/udp_connections.cpp
…ration # Conflicts: # include/app.hpp # src/app.cpp
# Conflicts: # docs/PROTOCOL.md # include/app.hpp # include/task_controller.hpp # src/app.cpp # src/task_controller.cpp
PR #84 also adds a PGN 0xF3 branch to this if/else chain, between 0xF1 and 0xF2. In different places, the two merge without a conflict and the second never runs. In the same place, Git reports the conflict. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ready combined here)
…egration # Conflicts: # docs/PROTOCOL.md # src/app.cpp
Test planYou don't need to test everything. Start with 1. Basics, then pick the sections that match your tractor and implement. The quoted text is what appears in the log, so you can search for it. For every test, please note: tractor and implement model, whether the tractor has its own TECU, the Reading a CAN trace: in a 29-bit ID like Normal, not a bug: for the first 6.5 s after the TC starts, implements can't connect. The log shows 1. Basics (#120 AgIsoStack update): everyone, pleaseThis update touches address claiming, VT handling and threading, so normal use is the real test.
2. Second TECU (#118): tractors with and without their own TECUChanging A. Tractor has its own TECU,
Report if: any of those comes from both the tractor and the TC, the implement's speed jumps around, or the tractor shows new errors. B. Tractor has no TECU, C. Tractor has no TECU, D. Stepping down ( E. The tractor's TECU goes away (if you can do this safely): expect Always report: 3. Section control switches on by itself (#102)
4. Reconnect after a TC restart (#105)
If an implement keeps talking without having reconnected, the log shows Report if: the log repeats 5. UDP and log noise (#107, #98)
6. Tractor Facilities and Time/Date (#82, #122)
7. Guidance track data and track control (#84, #85): TRACK-capable implement onlyNeeds AgOpenGPS built from AgOpenGPS-Official/AgOpenGPS#1218. Without it, the "Track state" line on the VT stays
An implement without TRACK should behave exactly as before. 8. Only if it applies to you
|
Do not merge. This is a test build that combines the changes waiting to go into
develop, so they can be tried together on a real tractor. Please leave what you find as comments here.Getting the build
Open the Checks tab of this PR, then the Build workflow run. The Windows Installer and the Linux tarballs are under Artifacts at the bottom of the run page (you need to be signed in to GitHub). Each new push here produces a new build.
Please attach the log to your comment. When AgIO starts the TC, logging is already on; if you start it yourself, add
--log2file. On Windows the logs are in%APPDATA%\AOG-TaskController\logs.What to try
sectionControlOnConnect,sectionControlOnFieldOpen(both default to on).[TractorFacilities]in the log.tcNumberto2insettings.json, then restart the TC.Second TECU (#118). The
tecuEnabledsetting now picks the role:[TECU]to see what it decided to send. Please check that it doesn't duplicate any of the tractor's messages, including in the first seconds after start.Changes needed in AgOpenGPS
Checked against AgOpenGPS
develop(64984a3d).0xF4from AgOpenGPS-Official/AgOpenGPS#1218 (branchtracksForTC). Still open, so test with an AgOpenGPS built from that PR.develop)0xD6to the TC whensetUDP_isSendNMEAToUDPis on. That defaults to off and has no setting in the UI, so it has to be changed in the AgIO profile for now.1, instead of the real fix quality.candleLightentry at the end of the adapter list inFormISOBUS.Designer.cs. It has to go at the end because AgIO saves the selected position. To choose between several adapters, also add it toadapterChannelsinFormISOBUS.cs.--can_adapter=candlelight.Nothing else needs an AgOpenGPS change. AgOpenGPS
developalready sends the field name (PGN0xF3) that #102 and #84 use.tcNumberand thesectionControlOn...settings have no screen in AgIO, so they're edited insettings.json.Not in this build
Draft PRs #106, #111, #76 and #75.
Combined here
Merging these needed conflict resolution, so a problem could come from the combination rather than from a single PR:
0xF3). This build has one handler that does both jobs.Built on Windows, and all 11 unit tests pass. It has not been run on a bus yet.
🤖 Generated with Claude Code