Skip to content

Switch section control on automatically when an implement connects - #102

Merged
gunicsba merged 7 commits into
developfrom
feature/section-control-on-connect
Nov 11, 2026
Merged

gunicsba merged 7 commits into
developfrom
feature/section-control-on-connect

Conversation

@gunicsba

@gunicsba gunicsba commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #94.

An implement used to stay in manual mode after connecting until someone pressed AOG's ISOBUS SC button. With this change the TC switches section control on by itself.

  • The TC remembers the section control state it wants. At startup that comes from a new settings.json key, sectionControlOnConnect, which defaults to true.
  • 2 s after a client's object pool is activated, the TC applies that state. It sets its local auto flag, so AOG's section states (PGN 229) are forwarded, and it writes DDI 160 (Section Control State) if the implement declares it.
  • Every activation starts with a fresh ClientState, so this also covers implements that connect after startup, reconnects and pool re-uploads.
  • AOG's SC button works as before: PGN 241 switches every connected client. It now also sets the state used for implements that connect later.
  • The implement keeps the final say. If it declines, or the operator switches it back to manual, its DDI 160 report is followed as before.

AOG only sends PGN 241 when the button is pressed, and the button shows the state from the TC heartbeat. That means AOG doesn't override the default, and its button shows ON once the implement is switched on.

Switching on when AOG opens a field

A second setting, sectionControlOnFieldOpen (default true), does the same thing when AOG opens a field: the TC calls the same code as PGN 241 "enabled", as if the SC button had been pressed.

  • AOG sends the field name (PGN 0xF3) on every field open and close, and again when the TC reconnects. A non-empty name counts as a field opening.
  • Older AOG versions don't send the field name. For those, PGN 229 starting after a 2 s gap counts as a field opening, because AOG only sends PGN 229 while a field is open.
  • Both signals report the same opening, so the TC acts on it at most once per 5 s.
  • Field open and close are logged as [Field] AOG opened field '...' and [Field] AOG closed the field.

Background: in a field log from 2026-10-13, after field changes a spreader in automatic spread full width without receiving setpoints (see #106). This option makes a field change restart automatic section control in the TC without anyone pressing the button.

Notes

  • Track control (DDI 515) is not switched on automatically. It is still only driven by the AOG button.
  • To turn these off, set "sectionControlOnConnect": false or "sectionControlOnFieldOpen": false in settings.json. Both are listed in the readme. There is no VT Config toggle yet.
  • Announce guidance track data (DDI 507-511, 513) to implements #84 also handles PGN 0xF3; whichever lands second should merge the two handlers.

Testing

  • Release build passes on Windows (MSVC).
  • Not yet tested on a real implement. Things to check: the log shows [TC] Applying initial section control state: enabled about 2 s after the pool is activated, the implement reports DDI 160 = 1, and AOG's button shows ON.

🤖 Generated with Claude Code

Summary by Sourcery

Automatically synchronize section control with implement connections and AOG field openings while keeping the behavior configurable.

New Features:

  • Automatically enable section control after implement connections and when AOG opens a field, with configurable behavior.

Bug Fixes:

  • Ensure newly enabled section control receives current section setpoints, including all-off demands, to prevent stale implement state.

Enhancements:

  • Track and deduplicate AOG field open and close events across field-name and section-demand signals.
  • Preserve AOG's requested section-control state for connected and subsequently connecting implements while respecting implement capabilities and operator overrides.

Documentation:

  • Document the new section-control-on-connect and section-control-on-field-open settings.

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>
gunicsba and others added 3 commits October 14, 2026 09:09
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>
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>
@gunicsba gunicsba mentioned this pull request Nov 8, 2026
2 of 20 tasks
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>
@gunicsba

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai

sourcery-ai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Data structures

Add the desired section-control state, connection scheduling, and demand tracking that the server uses when applying automatic control.

  • include/app.hpp L123-133
  • include/task_controller.hpp L129-133, L139-144, L149-154 (+3 small edits)

API

Expose settings accessors and the server operation for configuring automatic section control on connection.

  • include/settings.hpp L101-112

Uses of the API

Wire the settings into server setup and detect field openings from field names or legacy section-demand traffic.

  • src/app.cpp L450-470, L535-538, L547-566 (+2 small edits)

Load, save, and expose the new automatic section-control settings.

  • src/settings.cpp L73-74, L148-149, L268-289

Implementation

Schedule and apply the initial section-control state after client activation, and send current setpoints whenever section control is enabled.

  • src/task_controller.cpp L249-264, L896-907, L967-1020 (+7 small edits)

Configuration

Add defaults and stored values for the connection and field-open section-control options.

  • include/settings.hpp L176-177, L186-187

Documentation

Document the new settings, their defaults, and the field-open detection behavior.

  • readme.md L67-68

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai

sourcery-ai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Linked issues

Checked at commit 3c90c20

#94 Late-registering implement is never told to go to Auto if AOG enabled SC earlier · 1 of 2 met

# Requirement Status Notes
1 Remember the section-control state and apply it to later clients with sections. 🟡 Partly met With the default setting, the server remembers the requested state and applies it after a delay, including to reconnecting clients. When sectionControlOnConnect is false, it schedules no application, even if AOG has since sent PGN 241 to enable section control.
2 Set the client’s local state and send DDI 160 when the client declares it. ✅ Met The delayed application updates the local flag and sends DDI 160 for clients with sections that declare the DDI.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/app.cpp" line_range="537" />
<code_context>
 			tcServer->update_section_states(sectionStates);
+			if (resumed)
+			{
+				on_aog_field_opened("started sending section states (PGN 229)");
+			}
 		}
</code_context>
<issue_to_address>
**Unauthenticated field events activate implements**

When an untrusted local-network sender can reach the UDP listener and section control is enabled for field openings, a sender on the local network can forge a UDP packet with protocol source `0x7F` and a PGN 229 field-opening pattern or non-empty PGN 0xF3 payload; the handler calls `update_section_control_enabled(true)`, which applies DDI 160 and switches connected implements to automatic without operator authorization.

Authenticate and authorize the UDP sender and validate packet integrity before allowing field events to change implement state.

Also at `src/app.cpp:558`.
</issue_to_address>

### Comment 2
<location path="src/task_controller.cpp" line_range="977-979" />
<code_context>
+	state.set_section_control_enabled(enabled);
+
+	// Only send ISOBUS command to clients that support SectionControlState DDI and have sections
+	if (state.has_element_number_for_ddi(isobus::DataDescriptionIndex::SectionControlState) &&
+	    state.get_number_of_sections() > 0)
+	{
+		send_section_control_state(client, enabled);
 	}
</code_context>
<issue_to_address>
**Implement remains in manual mode**

When an activated implement maps DDI 160 but does not mark it settable, `apply_section_control_state` sends the command and records section control as enabled; the implement rejects it and remains in manual mode while AOG reports enabled.

Check that DDI 160 is settable before sending the command or recording section control as enabled.
</issue_to_address>

### Comment 3
<location path="src/task_controller.cpp" line_range="899-905" />
<code_context>
+
+			// Switch the implement to automatic shortly after it connects, so AOG's SC button
+			// does not need pressing on every connect.
+			client.second.schedule_initial_section_control(isobus::SystemTiming::get_timestamp_ms() + INITIAL_SECTION_CONTROL_DELAY_MS);
+		}
+		if (client.second.take_due_initial_section_control(isobus::SystemTiming::get_timestamp_ms()))
+		{
</code_context>
<issue_to_address>
**Delayed enable overrides manual mode**

When the implement or its operator reports manual mode after activation but before the initial-state timer expires, `take_due_initial_section_control()` applies `desiredSectionControlEnabled` even if the implement has reported DDI 160 = 0 since the schedule was created. The TC therefore sends enabled after the operator has switched the implement to manual, and the implement ends up back in automatic mode.

Cancel or revise the pending initial application when the implement reports a newer section-control state, or otherwise distinguish the initial command from a later operator choice.
</issue_to_address>

### Comment 4
<location path="src/app.cpp" line_range="456" />
<code_context>
+	{
+		return;
+	}
+	if ((0 != lastFieldOpenAppliedMs) && !isobus::SystemTiming::time_expired_ms(lastFieldOpenAppliedMs, FIELD_OPEN_DEDUPLICATE_MS))
+	{
+		return;
</code_context>
<issue_to_address>
**New field openings are ignored**

When AOG opens a distinct field within five seconds of the previous applied opening, `on_aog_field_opened()` returns based only on the elapsed time, so it ignores the new field opening and leaves section control in its previous state, including manual mode if it was turned off.

Deduplicate repeated openings by field identity or transition rather than suppressing every opening within the five-second window.
</issue_to_address>

### Comment 5
<location path="src/app.cpp" line_range="527" />
<code_context>
 				}
 			}
+			// Older AOG versions do not send the field name, but PGN 229 only flows while a field is open
+			const bool resumed = (0 == lastSectionDemandMs) || isobus::SystemTiming::time_expired_ms(lastSectionDemandMs, SECTION_DEMAND_TIMEOUT_MS);
 			lastSectionDemandMs = lastAogPacketMs;
 			if (sectionStates != sectionDemand)
</code_context>
<issue_to_address>
**Manual-off choice is overridden**

When PGN 229 traffic pauses for more than two seconds while the same field remains open and section control has been turned off, `resumed` becomes true based on the packet gap, so `on_aog_field_opened()` enables section control without a new field opening and overrides the operator's manual-off choice.

Determine whether a field actually opened or reopened instead of treating a PGN 229 traffic gap as a field opening.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/app.cpp Resolved
Comment thread src/task_controller.cpp Resolved
Comment thread src/task_controller.cpp Resolved
Comment thread src/app.cpp Resolved
Comment thread src/app.cpp Resolved
A gap of more than 2 s in PGN 229 (AOG stalling, lost UDP packets) was
treated as a field opening and switched section control back on, even
when the operator had switched it off and the same field stayed open.
Once AOG has sent a field name (PGN 0xF3), only a change of field name
switches section control on; the PGN 229 fallback stays for AOG
versions that do not send the field name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/task_controller.cpp Resolved
Comment thread src/task_controller.cpp Resolved
…tting is off

- After section control is switched on (connect, field open, PGN 241), send
  every condensed setpoint block and DDI 289, forced, from AOG's current
  demand (all off when none arrived in the last 2 s). Change-only sends
  never told the implement "all off" when AOG's Auto was off, so it kept
  running on its own state (ISO 11783-10 F.3.5.3 b).
- With sectionControlOnConnect off, schedule nothing on connect. Before,
  DDI 160 = 0 went out on every connect, and after one SC press or field
  open every later connect switched on.

Helpers match #106 (send_all_section_setpoint_states, forceWorkState) so
the two merge cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New gaps against the linked issues

Comment thread src/task_controller.cpp Resolved
@gunicsba
gunicsba merged commit b861aab into develop Nov 11, 2026
7 checks passed
@gunicsba
gunicsba deleted the feature/section-control-on-connect branch November 11, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Late-registering implement is never told to go to Auto if AOG enabled SC earlier

2 participants