Skip to content

Resolve section control targets from linked process data only - #111

Draft
gunicsba wants to merge 3 commits into
developfrom
fix/section-control-linked-ddis
Draft

gunicsba wants to merge 3 commits into
developfrom
fix/section-control-linked-ddis

Conversation

@gunicsba

Copy link
Copy Markdown
Contributor

Summary

Section control detection now looks only at process data that a device element actually links. The method and its targets are resolved once, when the implement activates, by a pure function, resolve_section_control(). A new test runs that function against every sample DDOP.

Draft for review. Some of these paths have only synthetic evidence (see Evidence below), so part of the discussion is whether to keep them.

Commits

  1. Add sample implement DDOPs for tests: the 24 real pools plus manifest.csv and README from the integration build, unchanged. They aren't on develop yet.
  2. Add synthetic MIDI 3 section control variants: three *_synthetic.ddop files, derived from the real MIDI 3 capture by the firmware's own DDOP rules (from reverse engineering). The same rules rebuild the real capture byte for byte. The manifest and README mark them as never seen on the wire.
  3. Resolve section control targets from linked process data only: the refactor, the fix and the test.

What changes in the TC

  • Process data that no element references is ignored. The MIDI 3 sends every template object, so its pool declares DDI 289, 291–305 and 162–176 objects that belong to no element.
  • "Settable" is checked on the object a given element links, not on the first object in the pool with that DDI. is_ddi_settable() is removed.
  • New per-element method: DDI 289 on each section, actual state on DDI 141 of the same section. Settable DDI 141 stays as the fallback.
  • The global DDI 289 is only taken from an element that is not a section.
  • "DDI 289 not available" is logged once per client at activation, not on every send.
  • Device element children are read by index, because iterating get_child_object_ids() reads past the end of the list (Bump AgIsoStack to pick up the DataSpan fix (wrong DDI-to-element mapping) #110).

Order of preference: DDI 290 > settable DDI 161 > per-element DDI 289 > per-element settable DDI 141. This follows the fallback rules in the ISO 11783-11 attachment for DDI 289/290: a TC "shall be able to fall back to using the Actual Work State DDIs" when the implement marks them settable.

Evidence

Change Evidence
Child IDs read by index Real. On the real MIDI 3 capture, the old logic mapped DDI 291 to element 15 and DDI 289 to random sections, and DDI 290 on PWRW_II to element 17. The result changed between runs. Same root cause as #109.
DDI 289 log once Real. The MIDI 3 and SKY Easydrill pools have no linked DDI 289.
Only linked process data counts Synthetic. Every real pool already resolves correctly without it. It matters for the MIDI 3 in old-SC or per-element mode.
Settable check per linked object Synthetic. It matters for MIDI 3 old-SC + condensed mode, where the unlinked non-settable DDI 161 comes first in the pool.
Per-element DDI 289 Synthetic and the most speculative. The data dictionary allows the layout, but the same attachment makes condensed 290/161 mandatory for AEF TC-SC.

If the synthetic-only paths aren't wanted, the per-element DDI 289 method is the easiest to drop: one branch in the resolver and one manifest row.

Test results

ctest: 6 of 7 pass.

  • Before the fix, using today's logic moved into the resolver unchanged, the three synthetic MIDI 3 files fail as predicted.
  • After the fix, all four MIDI 3 pools resolve as follows:
    • capture: 290 on element 2
    • old SC, condensed: 161 on element 2
    • new SC, per-element: 289 on elements 10–20
    • old SC, per-element: 141 on elements 10–20
  • Every other pool keeps the method the TC picks today.

section_control_method still fails on 4 manifest rows, to be decided before merge. The Kuhn Espro DDI141 files (en and hu) and the JD 6000M and 6R175 tractors are listed as DDI141, but their DDI 141 is not settable. Today's TC and this branch both pick none for them (issue #10: "SC switch is not working"). The proposal is to change those rows to none; no code change is needed.

Notes for reviewers

Not tested

  • Real implements: no MIDI 3 is configured in old-SC or per-element mode.
  • Only Windows (MSVC Release) was built locally.

🤖 Generated with Claude Code

gunicsba and others added 3 commits October 18, 2026 15:55
Real-world Device Descriptor Object Pools collected from issues and bench
captures, sorted by implement type under tools/testdata/implements, with a
manifest.csv index and a README. Serial numbers and NAME identity bits are
zeroed. Taken unchanged from the integration build, so tests can run section
control detection against real pools without the machines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Mueller MIDI 3 sprayer (SPR_MIDI_3 V07.10.05.33) presents section
control in four ways, chosen by two configuration bits: old or new SC DDIs,
condensed work state on or off. Only one of them has been captured.

The other three were derived from that capture with the firmware's own
DDOP rules (from reverse engineering): it sends every template object and
only filters the element child lists, and old SC mode relabels DDI 289 to
141 and 290+ to 161+ as settable. The same rules rebuild the real capture
byte for byte.

The files are named *_synthetic.ddop and marked as never seen on the wire
in the manifest and README. Replace them once real captures exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the section control method detection and the DDI to element mapping
out of activate_object_pool(), request_measurement_commands() and
send_section_setpoint_states() into resolve_section_control(), which takes
the DDOP and returns the method and where to write it. The client keeps the
result as its SectionControlPlan.

What changes:
- Only process data that a device element links counts. Some implements
  (Mueller MIDI 3) send every template object and leave the unused ones
  unreferenced, so a pool can declare DDI 290 or a settable DDI 161 that no
  element owns.
- "Settable" is checked on the linked object for that element, not on the
  first object in the pool with that DDI. This replaces is_ddi_settable().
- New per-element method: DDI 289 on each section element, actual state on
  DDI 141 of the same element. Settable DDI 141 stays as the fallback.
- The global Setpoint Work State (DDI 289) is only taken from an element
  that is not a section, so per-element pools no longer get it on their
  last section.
- "DDI 289 (SetpointWorkState) not available" is logged once at activation
  instead of on every setpoint send.
- Device element children are read by index: iterating
  get_child_object_ids() reads past the end of the list (AgIsoStack DataSpan
  bug, #110) and mapped DDIs to random elements on the MIDI 3 capture.

Order of preference: DDI 290 > settable DDI 161 > per-element DDI 289 >
per-element settable DDI 141, matching the fallback rules of the
ISO 11783-11 DDI 289/290 attachment.

The new test_section_control target resolves every DDOP in
tools/testdata/implements and checks the section_control column of
manifest.csv, plus the exact targets of the four MIDI 3 pools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant