From 0420176de926dba4213702647943f2c6cec899cc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 16:48:11 +0000 Subject: [PATCH 01/24] Web map: long curves cost less to draw (an older PC no longer freezes) A closed Boundary Curve (2,249 points) with 10 extra guidelines each side drew at 7 fps on an Intel HD 4000 (109 ms a frame): the screen froze and the page missed its presence heartbeat (AutoSteer off, deadman). - Extra guidelines go in one path, stroked once with the shadow and once with the green (was 2 strokes per line, 40 screen-sized masks a frame). - Scene lines are drawn from a copy without points within 2 mm of their neighbours (Douglas-Peucker, cached per array), then within 0.25 px on the screen every frame. - The tool band's edges are made once per pass, each point is projected once, and quads off the screen are left out. No visible change. Client only; the host's hot paths are untouched. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDGviRvcfDmKpDsRru9 --- CLAUDE.md | 6 + .../wwwroot/js/frame.js | 21 ++- .../wwwroot/js/map-draw.js | 148 +++++++++++++++--- 3 files changed, 140 insertions(+), 35 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7003eea0d..cd39eb6b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,6 +125,12 @@ sections), the `Scene` (tracks, boundary, imagery extent), `Config`, `Status`, c init + incremental cell deltas, and smaller frames for dialogs, prompts, sounds and toasts. Position between ticks is extrapolated client-side (`RENDER_DELAY`); the pose estimator on the host does the same for the 100 Hz control loop. +Long lines cost the most to draw: Skia strokes a bent, anti-aliased path through a CPU mask the +size of the path on the screen, so a curve that goes round the field costs a screen-sized mask. +Lines from the `Scene` are drawn with `strokeFixedPtsSk` (`js/map-draw.js`: a copy without the +points that add nothing, made once per array; the host replaces its arrays, the page never edits +them); a line the page draws or edits uses `strokePtsSk`. Lines in one style share one path +(`strokeCmdsSk`), as the extra guidelines do. ### Control authority Several browsers may be connected; only one holds control (`ControlAuthority`, deadman model). diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/frame.js b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/frame.js index d02937818..6a438af02 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/frame.js +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/frame.js @@ -18,17 +18,17 @@ function renderSkia(canvas, rp) { const outOfBounds = !!(tick && tick.op && tick.op.outOfBounds); // #494 if (scene) { for (let bi = 0; bi < scene.boundaries.length; bi++) - strokePtsSk(canvas, scene.boundaries[bi], true, outOfBounds ? SKP.boundaryOut + strokeFixedPtsSk(canvas, scene.boundaries[bi], true, outOfBounds ? SKP.boundaryOut : (scene.boundaryInner && scene.boundaryInner[bi]) ? SKP.boundaryInner : SKP.boundary); // Headland line shows only when the headland is ON (the bottom-nav headland button). if (scene.headland && tick && tick.tools && tick.tools.headlandOn) - strokePtsSk(canvas, scene.headland, true, SKP.headland); + strokeFixedPtsSk(canvas, scene.headland, true, SKP.headland); drawExtraGuidelinesSk(canvas); // faint adjacent passes (under the bold lines) - for (const l of scene.recordedPaths || []) strokePtsSk(canvas, l, false, SKP.recPath); // #110 - for (const l of scene.contourStrips || []) strokePtsSk(canvas, l, false, SKP.contourStrip); + for (const l of scene.recordedPaths || []) strokeFixedPtsSk(canvas, l, false, SKP.recPath); // #110 + for (const l of scene.contourStrips || []) strokeFixedPtsSk(canvas, l, false, SKP.contourStrip); drawContourRefSk(canvas); - if (scene.nextTrack) strokePtsSk(canvas, scene.nextTrack, false, SKP.next); - if (scene.uTurnPath) strokePtsSk(canvas, scene.uTurnPath, false, SKP.uturn); + if (scene.nextTrack) strokeFixedPtsSk(canvas, scene.nextTrack, false, SKP.next); + if (scene.uTurnPath) strokeFixedPtsSk(canvas, scene.uTurnPath, false, SKP.uturn); // Purple reference (extended across the field). The reference is the edge between passes // (PassGeometry), so pass 0 lies half a width from it; only a track driven on the line // itself (boundary-follow) puts the magenta guidance on top of it, and then just the one @@ -36,15 +36,12 @@ function renderSkia(canvas, rp) { drawToolBandSk(canvas); // where the tool will land, under the lines const _onRef = scene.guidanceLine && scene.tracks.some(t => onPolyline(scene.guidanceLine, t.points)); if (!_onRef) for (const tr of scene.tracks) - strokePtsSk(canvas, extendAbLine(tr.points), false, SKP.reference); + strokeFixedPtsSk(canvas, extendAbLine(tr.points), false, SKP.reference); // Magenta current pass, extended across the field. The host's DisplayLine is only ~track // length (a 2-point offset AB); extendAbLine spans it full-field, so an offset pass isn't a // short stub. Curves are >2 points and already host-extended, so they pass through unchanged. - if (scene.guidanceLine) { - const gl = extendAbLine(scene.guidanceLine); - strokePtsSk(canvas, gl, false, SKP.guidanceOutline); - strokePtsSk(canvas, gl, false, SKP.guidance); - } + if (scene.guidanceLine) + strokeFixedPtsSk(canvas, extendAbLine(scene.guidanceLine), false, [SKP.guidanceOutline, SKP.guidance]); drawActiveAbPointsSk(canvas); drawFlagsSk(canvas, scene.flags); } diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/map-draw.js b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/map-draw.js index 93e43666a..21cbabbf0 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/map-draw.js +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/map-draw.js @@ -20,10 +20,78 @@ function onPolyline(a, b) { } return false; } +// `paint` may be a list of paints, drawn in order on the one path (a shadow, then the line). function strokePtsSk(canvas, pts, close, paint) { if (!pts || pts.length < 2) return; strokePtsSk3D(canvas, pts, close, paint); } +// strokePtsSk for a line that is never changed in place: the Scene's (the host replaces its +// arrays, the page never edits them) or one made from it once. A long one is drawn from its copy +// without the points that add nothing to the picture (drawnPts). Not for a line being drawn or +// edited on the page (its points move or grow in place, and the copy would not follow). +function strokeFixedPtsSk(canvas, pts, close, paint) { + if (!pts || pts.length < 2) return; + strokePtsSk3D(canvas, drawnPts(pts), close, paint); +} +// A long line is drawn from a copy without the points that add nothing to the picture. A curve +// from the host carries a point about every metre: a boundary curve round a 2 km fence has 2,249, +// and its 20 extra guidelines up to 2,200 each, most of them on the straight stretches between +// the fence's corners. Projecting and stroking all of them on every frame took ~110 ms a frame on +// an older PC (Intel HD 4000): the map fell to 7 frames a second, the host's frames waited behind +// the drawing (the screen froze) and the page missed its presence heartbeat (AutoSteer off, +// "presence lost (deadman)"). The copy keeps every point more than 2 mm off the line between its +// neighbours (Douglas-Peucker; 0.4 px at the closest zoom, 200 px/m), so the line looks the same; +// it is made once per array (a WeakMap on the array itself). +const SIMPLIFY_MIN_PTS = 64, SIMPLIFY_TOL_M = 0.002; +const _drawnPts = new WeakMap(); +function drawnPts(pts) { + if (pts.length < SIMPLIFY_MIN_PTS) return pts; + let s = _drawnPts.get(pts); + if (!s) { s = simplifyPolyline(pts, SIMPLIFY_TOL_M); _drawnPts.set(pts, s); } + return s; +} +// Douglas-Peucker: which of the points (xs[i], ys[i]) to keep so that none of the others lies more +// than `tol` from the chord of the stretch it is in; the first and last are always kept. +// Iterative, so a long line can't overflow the stack. +function dpKeep(xs, ys, tol) { + const n = xs.length, keep = new Uint8Array(n); + keep[0] = 1; keep[n - 1] = 1; + const stack = [0, n - 1]; + while (stack.length) { + const b = stack.pop(), a = stack.pop(); + if (b - a < 2) continue; + const ax = xs[a], ay = ys[a], dx = xs[b] - ax, dy = ys[b] - ay, len = Math.hypot(dx, dy); + let worst = -1, wi = -1; + for (let i = a + 1; i < b; i++) { + const d = len > 1e-9 ? Math.abs(dx * (ys[i] - ay) - dy * (xs[i] - ax)) / len + : Math.hypot(xs[i] - ax, ys[i] - ay); // a closed stretch: its end is its start + if (d > worst) { worst = d; wi = i; } + } + if (worst > tol) { keep[wi] = 1; stack.push(a, wi, wi, b); } + } + return keep; +} +// {e, n} points without those within `tol` metres of the line through their neighbours. +function simplifyPolyline(pts, tol) { + const n = pts.length, xs = new Float64Array(n), ys = new Float64Array(n); + for (let i = 0; i < n; i++) { xs[i] = pts[i].e; ys[i] = pts[i].n; } + const keep = dpKeep(xs, ys, tol), out = []; + for (let i = 0; i < n; i++) if (keep[i]) out.push(pts[i]); + return out; +} +// The same on a screen polyline ([x, y] px), on every frame: a curve's points a metre apart are +// a pixel or less apart when the map is zoomed out, and Skia strokes every one of them (the +// most of a frame's time with a long curve's extra guidelines). A quarter of a pixel is not seen. +const SCREEN_SIMPLIFY_MIN_PTS = 32, SCREEN_SIMPLIFY_TOL_PX = 0.25; +function simplifyScreen(sub) { + const n = sub.length; + if (n < SCREEN_SIMPLIFY_MIN_PTS) return sub; + const xs = new Float64Array(n), ys = new Float64Array(n); + for (let i = 0; i < n; i++) { xs[i] = sub[i][0]; ys[i] = sub[i][1]; } + const keep = dpKeep(xs, ys, SCREEN_SIMPLIFY_TOL_PX), out = []; + for (let i = 0; i < n; i++) if (keep[i]) out.push(sub[i]); + return out; +} // Extend a 2-point AB line far past both ends so the reference line spans the field instead of // being a short stub (issue #37). Curves (>2 points) pass through unchanged. The viewport clip // in strokePtsSk3D bounds the draw cost of the now-long line. AB_EXTEND covers any field. @@ -77,6 +145,22 @@ function clipScreenPolyline(pts, margin) { return out; } function strokePtsSk3D(canvas, pts, close, paint) { + const cmds = []; + lineCmds(pts, close, cmds); + strokeCmdsSk(canvas, cmds, paint); +} +// One path from path commands, stroked with each paint in turn. +function strokeCmdsSk(canvas, cmds, paint) { + if (!cmds.length) return; + const path = CK.Path.MakeFromCmds(cmds); + if (!path) return; + if (Array.isArray(paint)) for (const p of paint) canvas.drawPath(path, p); + else canvas.drawPath(path, paint); + path.delete(); +} +// The path commands of a line's parts in front of the camera and on the screen, added to `cmds` +// (a contour each), so several lines can go in one path. +function lineCmds(pts, close, cmds) { const EPS = 1.0; const n = pts.length; const wOf = (p) => pw(p.e, p.n); @@ -90,13 +174,11 @@ function strokePtsSk3D(canvas, pts, close, paint) { // Clip the front-facing screen run to the viewport before stroking — bounds a dashed // line's dash flattening (and stroke fill) to on-screen length, the tilt FPS fix. if (run.length >= 2) { - for (const sub of clipScreenPolyline(run, 48)) { - if (sub.length < 2) continue; - const cmds = []; + for (const clipped of clipScreenPolyline(run, 48)) { + if (clipped.length < 2) continue; + const sub = simplifyScreen(clipped); for (let i = 0; i < sub.length; i++) cmds.push(i === 0 ? CK.MOVE_VERB : CK.LINE_VERB, sub[i][0], sub[i][1]); - const path = CK.Path.MakeFromCmds(cmds); - if (path) { canvas.drawPath(path, paint); path.delete(); } } } run = []; @@ -186,11 +268,15 @@ function drawExtraGuidelinesSk(canvas) { if (!line || line.length < 2) return; const toolW = toolWidthM(); if (toolW < 0.1 || toolW * pxPerM < 3) return; // zoom gate: skip when passes < ~3 px apart + // All the lines in one path, drawn once with the shadow and once with the green: Skia strokes a + // path that bends through a mask the size of the path on the screen, and a boundary curve's + // guidelines each go round the field, a screen-sized mask each, forty a frame with ten each side. + const cmds = []; for (const gl of scene.guideLines || []) { const pts = extendAbLine(gl); - strokePtsSk(canvas, pts, false, SKP.extraGuideShadow); - strokePtsSk(canvas, pts, false, SKP.extraGuide); + if (pts && pts.length >= 2) lineCmds(drawnPts(pts), false, cmds); } + strokeCmdsSk(canvas, cmds, [SKP.extraGuideShadow, SKP.extraGuide]); } // Live boundary draw-on-map preview — the polygon vertices tapped on the // satellite imagery so far. Closed white outline + outlined dots; client-side only. @@ -224,12 +310,39 @@ function drawAbDrawPreviewSk(canvas) { // AgOpenGPS's shadow on the current line (CABLine.DrawABLines): the tool's width along the pass // shown, so you see where the tool will land. The host sends the pass's centre (the tool offset // already in it); on a curve it follows the pass, corners rounded as the tractor drives them. One -// path of quads along it, skipping any behind the camera, so a tilted view draws no ghost. +// path of quads along it, skipping any behind the camera, so a tilted view draws no ghost. Each +// edge point is projected once, and quads off the screen are left out. function drawToolBandSk(canvas) { const c = scene && scene.toolPass; const w = toolWidthM(); if (!c || c.length < 2 || w < 0.1 || !(tick && tick.guidanceActive)) return; - let pts = extendAbLine(c); + const { L, R } = toolBandEdges(c, w), n = L.length; + const ls = new Array(n), rs = new Array(n); + for (let i = 0; i < n; i++) // left empty: behind the camera + if (pw(L[i].e, L[i].n) >= 1.0 && pw(R[i].e, R[i].n) >= 1.0) { ls[i] = w2s(L[i].e, L[i].n); rs[i] = w2s(R[i].e, R[i].n); } + const m = 48, x0 = -m, y0 = -m, x1 = vw + m, y1 = vh + m; + const cmds = []; + for (let i = 0; i + 1 < n; i++) { + if (!ls[i] || !ls[i + 1]) continue; + const q = [ls[i], ls[i + 1], rs[i + 1], rs[i]]; + if (q.every(p => p[0] < x0) || q.every(p => p[0] > x1) || q.every(p => p[1] < y0) || q.every(p => p[1] > y1)) continue; // off the screen + q.forEach((p, k) => cmds.push(k ? CK.LINE_VERB : CK.MOVE_VERB, p[0], p[1])); + cmds.push(CK.CLOSE_VERB); + } + if (cmds.length) { + const path = CK.Path.MakeFromCmds(cmds); + if (path) { canvas.drawPath(path, SKP.toolBand); path.delete(); } + } + strokeFixedPtsSk(canvas, L, false, SKP.toolBandEdge); + strokeFixedPtsSk(canvas, R, false, SKP.toolBandEdge); +} +// The band's two edges, made once per pass the host sends (and tool width), not on every frame; +// a curve's from its drawn copy (drawnPts), so a long pass doesn't put thousands of quads on +// every frame. +let _bandSrc = null, _bandW = 0, _bandEdges = null; +function toolBandEdges(c, w) { + if (c === _bandSrc && w === _bandW) return _bandEdges; + let pts = drawnPts(extendAbLine(c)); if (pts.length === 2) { // a straight line: pieces, so the far ones can be skipped const [a, b] = pts, len = Math.hypot(b.e - a.e, b.n - a.n), k = Math.max(1, Math.ceil(len / 20)); pts = Array.from({ length: k + 1 }, (_, i) => ({ e: a.e + (b.e - a.e) * i / k, n: a.n + (b.n - a.n) * i / k })); @@ -242,19 +355,8 @@ function drawToolBandSk(canvas) { L[i] = { e: pts[i].e - re * half, n: pts[i].n - rn * half }; R[i] = { e: pts[i].e + re * half, n: pts[i].n + rn * half }; } - const cmds = []; - for (let i = 0; i + 1 < n; i++) { - const q = [L[i], L[i + 1], R[i + 1], R[i]]; - if (q.some(p => pw(p.e, p.n) < 1.0)) continue; // behind the camera - q.forEach((p, k) => { const xy = w2s(p.e, p.n); cmds.push(k ? CK.LINE_VERB : CK.MOVE_VERB, xy[0], xy[1]); }); - cmds.push(CK.CLOSE_VERB); - } - if (cmds.length) { - const path = CK.Path.MakeFromCmds(cmds); - if (path) { canvas.drawPath(path, SKP.toolBand); path.delete(); } - } - strokePtsSk(canvas, L, false, SKP.toolBandEdge); - strokePtsSk(canvas, R, false, SKP.toolBandEdge); + _bandSrc = c; _bandW = w; + return _bandEdges = { L, R }; } // The active line's A and B, as AgOpenGPS marks them: a red A and a cyan B at its ends (labels // in renderAbDotLabels), so you see which way A→B runs. Not while a line is being made. @@ -285,7 +387,7 @@ function drawTramLinesSk(canvas) { const tramOn = tick && tick.tools && tick.tools.tramMode > 0; if (!fbTramOpen && !tramOn) return; SKP.tram.setStrokeWidth(Math.max(2, 0.3 * pxPerM)); - for (const line of scene.tramLines) if (line.length >= 2) strokePtsSk(canvas, line, false, SKP.tram); + for (const line of scene.tramLines) if (line.length >= 2) strokeFixedPtsSk(canvas, line, false, SKP.tram); } // Field Builder Headland editor — draw each segment's offset line + overshoot extensions // (yellow = contributes to the closed headland, red = doesn't yet) ONLY while the Headland From 7454586fd67ec471d27115eff8248fc58da1f329 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 19:18:36 +0000 Subject: [PATCH 02/24] Headland distance box: no held value with the headland off The pipeline holds the last headland distance so the box doesn't vanish between crossing the headland line and the U-turn. It held it with the headland turned off too: there is no distance then (AgOpenGPS draws the box only with the headland on, and the host sends none), so the last one stayed on the screen, frozen while the tractor drove on, until the headland was turned on again. A tester's box stood at 173 m for 55 s on Tapada da Casa. The hold now needs the headland on, and the held value is dropped when there is none to show (headland off, no headland line). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDDYnkAJhx49ZdYtRAu --- .../Pipeline/GpsPipelineService.cs | 12 ++++- .../Pipeline/HeadlandToggleTests.cs | 46 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs b/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs index 7dd663af3..2a8129149 100644 --- a/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs +++ b/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs @@ -1471,17 +1471,25 @@ private void ProcessCycle(GpsData data) out headlandDist, out headlandWarning); // Hold last valid distance so the HUD doesn't disappear in gaps - // (e.g., between crossing the headland line and entering the U-turn arc) + // (e.g., between crossing the headland line and entering the U-turn arc). + // Only with the headland on: off, there is no distance (AgOpenGPS draws none), and the + // held one stayed on the screen, frozen while the tractor drove on, until the headland + // was turned on again (a tester's box stood at 173 m for 55 s, Tapada da Casa). if (headlandDist != null) { _lastHeadlandDistance = headlandDist; _lastHeadlandWarning = headlandWarning; } - else if (_lastHeadlandDistance != null && headlandLine != null) + else if (headlandOn && _lastHeadlandDistance != null && headlandLine != null) { headlandDist = _lastHeadlandDistance; headlandWarning = _lastHeadlandWarning; } + else + { + _lastHeadlandDistance = null; + _lastHeadlandWarning = false; + } // ── (11) RTK quality sounds ───────────────────────────────────── if (CheckRtkQualityChange(data.FixQuality) && autoSteerEngaged) diff --git a/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs b/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs index 05ccdc159..74713fa43 100644 --- a/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs @@ -36,6 +36,7 @@ public class HeadlandToggleTests private GpsService _gpsService = null!; private GpsPipelineService _pipeline = null!; private List _results = null!; + private ToolPositionService _toolPosition = null!; [SetUp] public void SetUp() @@ -52,7 +53,7 @@ public void SetUp() _gpsService = new GpsService(); _gpsService.Start(); - var toolPosition = new ToolPositionService(config); + var toolPosition = _toolPosition = new ToolPositionService(config); var coverage = new CoverageMapService(config); var sectionControl = new SectionControlService(toolPosition, coverage, config); var autoSteer = new AutoSteerService(new TrackGuidanceService(), @@ -129,6 +130,49 @@ public void HeadlandDistanceHud_FollowsTheToggle(bool headlandOn) Assert.That(Last.HeadlandProximityDistance.HasValue, Is.EqualTo(headlandOn)); } + // A tester turned the headland off mid-pass: the box kept the last distance, frozen while the + // tractor drove on, until the headland was on again (Tapada da Casa, 173 m for 55 s). + [Test] + public void HeadlandDistanceHud_TurnedOffWhileDriving_ShowsNoHeldDistance() + { + _pipeline.SetHeadlandOn(true); + _pipeline.SetBoundary(Boundary(200)); + _pipeline.SetHeadlandLine(Square(50)); + DriveTool(0, 0); + Assert.That(Last.HeadlandProximityDistance, Is.EqualTo(50).Within(0.01)); + + _pipeline.SetHeadlandOn(false); + DriveTool(0, 10); + Assert.That(Last.HeadlandProximityDistance, Is.Null, "headland off: no distance, not the last one held"); + + _pipeline.SetHeadlandOn(true); + DriveTool(0, 20); + Assert.That(Last.HeadlandProximityDistance, Is.EqualTo(30).Within(0.01), "on again: the distance from here"); + } + + // The hold is still there for what it is for: past the headland line, heading out, the ray + // ahead meets no headland line and the box keeps its last distance instead of vanishing. + [Test] + public void HeadlandDistanceHud_PastTheLineHeadingOut_KeepsTheLastDistance() + { + _pipeline.SetHeadlandOn(true); + _pipeline.SetBoundary(Boundary(200)); + _pipeline.SetHeadlandLine(Square(50)); + DriveTool(0, 49); + Assert.That(Last.HeadlandProximityDistance, Is.EqualTo(1).Within(0.01)); + + DriveTool(0, 60); + Assert.That(Last.HeadlandProximityDistance, Is.EqualTo(1).Within(0.01)); + } + + /// The control loop's tool update (heading north, the tool on the pivot), then one GPS cycle. + private void DriveTool(double e, double n) + { + ConfigurationStore.Instance.Tool.HitchLength = 0; + _toolPosition.Update(new Vec3(e, n, 0), 0); + Cycle(); + } + [TestCase(true, 2)] // tool in the headland band → raise [TestCase(false, 0)] // headland off → lift off (AgOpenGPS) public void HydraulicLift_IsOffWithTheHeadland(bool headlandOn, int expected) From e294ca94207d9c11c0dd0dd4222d64584ff00a2d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 21:50:11 +0000 Subject: [PATCH 03/24] Closed curve passes: no step back at the seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A closed curve's last point repeats its first, so the offset moved the seam's point twice: along the last stretch's normal and along the first's. On the inside of the seam's bend the second copy lay 2·d·tan(θ/2) behind the first (0.21 m on a 26 m lap of a round field): the pass ran on past the seam and back, its closing stretch pointing backwards. A tractor engaged 0.4 m off the line at the seam was steered to a goal behind it and turned round. On the inside the repeated point is now left out, so the pass goes from the last stretch's offset straight to the first's, as at any inside bend; on the outside it starts the arc round the seam, as before. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDDmANdNEsud9B3UMsp --- .../Guidance/CurveProcessing.cs | 16 ++ .../ClosedPassSeamTests.cs | 90 +++++++++ .../Pipeline/ClosedPassSeamPipelineTests.cs | 188 ++++++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs create mode 100644 Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs diff --git a/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs b/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs index ef03e38b8..21ec33aa8 100644 --- a/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs +++ b/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs @@ -527,6 +527,10 @@ private static (double Start, double End) OpeningRunOn(IReadOnlyList point private static bool FacesOtherEnd(double de, double dn, double toE, double toN, double dist) => dist > 1e-9 && (de * toE + dn * toN) / dist > Math.Cos(Math.PI / 6); + // The same place (a loop's closing point repeating its first). + private static bool SamePoint(Vec3 a, Vec3 b) + => Math.Abs(a.Easting - b.Easting) <= 1e-6 && Math.Abs(a.Northing - b.Northing) <= 1e-6; + /// /// Creates a clean offset curve and returns information about whether points were removed. /// Uses AgOpenGPS collision detection: if an offset point is within offset² distance @@ -570,8 +574,20 @@ public static (List Points, double PercentRemoved, bool StartKept, bool En // looks only at the points near each offset point instead of all of them. var near = refCount > NearGridMinPoints ? new PointGrid(originalPoints, Math.Abs(offsetDistance)) : null; + // A loop's last point repeats its first, so the seam's point is offset twice: along the + // last stretch's normal and along the first's. On the inside of the bend there the second + // copy lies behind the first: the pass ran on past the seam and back (0.2 m on a 26 m lap), + // its closing stretch pointing backwards, and a tractor a little off the line at the seam + // was steered to a goal behind it and turned round. There it is left out, and the pass + // goes from the last stretch's offset straight to the first's, as at any inside bend; on + // the outside it starts the arc round the seam (below). + bool seamInside = closed && refCount > 2 + && SamePoint(originalPoints[0], originalPoints[refCount - 1]) + && Math.IEEERemainder(originalPoints[0].Heading - originalPoints[refCount - 2].Heading, 2 * Math.PI) * offsetDistance > 0; + for (int i = 0; i < refCount; i++) { + if (seamInside && i == refCount - 1) break; var pt = originalPoints[i]; // Calculate offset point position diff --git a/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs b/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs new file mode 100644 index 000000000..805100e4c --- /dev/null +++ b/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs @@ -0,0 +1,90 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Guidance; + +namespace AgOpenWeb.Models.Tests; + +/// +/// A closed track's pass at its seam. The loop's last point repeats its first, and the +/// offset moved that point twice: once along the last stretch's normal, once along the first's. +/// On the inside of the bend there the second copy lies behind the first, so the pass ran past the +/// seam and back (0.2 m on a 26 m lap): its closing stretch pointed backwards, and a tractor a little +/// off the line at the seam was steered to a goal behind it and turned round. +/// +[TestFixture] +public class ClosedPassSeamTests +{ + // A round field of 30 m radius, its fence clockwise from the west point: right of travel is in. + private static List Round() + { + const int n = 120; + return CurveProcessing.DensifyLoop(Enumerable.Range(0, n).Select(i => + { + double a = 2 * Math.PI * i / n; + return new Vec3(-30 * Math.Cos(a), 30 * Math.Sin(a), 0); + }).ToList()); + } + + private static readonly Vec3[] Square = { new(-50, -50, 0), new(-50, 50, 0), new(50, 50, 0), new(50, -50, 0) }; + + // The largest change of heading from one stretch of the loop to the next, the seam included + // (degrees). A stretch that doubles back turns by about 180°. + private static double SharpestTurnRound(IReadOnlyList pass) + { + var pts = pass.ToList(); + Assert.That(pts[0].Easting, Is.EqualTo(pts[^1].Easting).Within(1e-9), "closed at the seam"); + Assert.That(pts[0].Northing, Is.EqualTo(pts[^1].Northing).Within(1e-9), "closed at the seam"); + pts.Add(pts[1]); + double worst = 0; + for (int i = 1; i + 1 < pts.Count; i++) + { + double h1 = Math.Atan2(pts[i].Easting - pts[i - 1].Easting, pts[i].Northing - pts[i - 1].Northing); + double h2 = Math.Atan2(pts[i + 1].Easting - pts[i].Easting, pts[i + 1].Northing - pts[i].Northing); + worst = Math.Max(worst, Math.Abs(Math.IEEERemainder(h2 - h1, 2 * Math.PI)) * 180 / Math.PI); + } + return worst; + } + + [Test] + public void An_inner_lap_of_a_round_field_does_not_double_back_at_the_seam([Values(4.0, 12.0, 20.0)] double d) + { + var pass = CurveProcessing.CreateOffsetCurveWithInfo(Round(), d, closed: true).Points; + Assert.That(SharpestTurnRound(pass), Is.LessThan(10), "a lap of 10 m or more turns a few degrees a metre"); + } + + [Test] + public void An_outer_lap_of_a_round_field_does_not_double_back_at_the_seam([Values(4.0, 12.0)] double d) + { + var pass = CurveProcessing.CreateOffsetCurveWithInfo(Round(), -d, closed: true).Points; + Assert.That(SharpestTurnRound(pass), Is.LessThan(10)); + } + + [Test] + public void The_pass_steered_does_not_double_back_at_the_seam([Values(4.0, 12.0)] double d) + { + var pass = CurveProcessing.CurvePass(Round(), d, closed: true, minTurnRadius: 8).Points; + Assert.That(SharpestTurnRound(pass), Is.LessThan(10)); + } + + // The square's seam is on a corner: the offset leaves out the points round the inside of it. + [Test] + public void An_inner_lap_of_a_square_field_does_not_double_back_at_its_seam_corner([Values(3.0, 9.0, 21.0)] double d) + { + var pass = CurveProcessing.CreateOffsetCurveWithInfo(CurveProcessing.DensifyLoop(Square), d, closed: true).Points; + Assert.That(SharpestTurnRound(pass), Is.LessThan(120), "no stretch doubles back"); + } + + [Test] + public void Every_point_of_an_inner_lap_keeps_its_distance_from_the_fence([Values(4.0, 12.0)] double d) + { + var pass = CurveProcessing.CreateOffsetCurveWithInfo(Round(), d, closed: true).Points; + foreach (var p in pass) + Assert.That(Math.Sqrt(p.Easting * p.Easting + p.Northing * p.Northing), Is.EqualTo(30 - d).Within(0.05)); + } +} diff --git a/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs b/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs new file mode 100644 index 000000000..3c94e7d79 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs @@ -0,0 +1,188 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using AgOpenWeb.Models; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Guidance; +using AgOpenWeb.Models.State; +using AgOpenWeb.Services; +using AgOpenWeb.Services.AutoSteer; +using AgOpenWeb.Services.Coverage; +using AgOpenWeb.Services.Interfaces; +using AgOpenWeb.Services.Pipeline; +using AgOpenWeb.Services.Section; +using AgOpenWeb.Services.Tool; +using AgOpenWeb.Services.Track; +using AgOpenWeb.Services.YouTurn; +using Microsoft.Extensions.Logging.Abstractions; +using NSubstitute; +using NUnit.Framework; + +namespace AgOpenWeb.Services.Tests.Pipeline; + +/// +/// AutoSteer at the seam of a boundary curve's lap, through the whole cycle. The lap ran +/// on 0.2 m past its seam and back (), so a +/// tractor engaged a little off the line there was steered to a goal behind it and turned round. +/// +[TestFixture] +[NonParallelizable] // ConfigurationStore is a singleton. +public class ClosedPassSeamPipelineTests +{ + private const double Wheelbase = 2.5; + private const double SpeedKmh = 8; + + private readonly Wgs84 _origin = new(43.7128, -74.006); + private LocalPlane _plane = null!; + private GpsService _gpsService = null!; + private GpsPipelineService _pipeline = null!; + private List _results = null!; + + [SetUp] + public void SetUp() + { + ConfigurationStore.SetInstance(new ConfigurationStore()); + var config = ConfigurationStore.Instance; + config.Vehicle.AntennaPivot = 0; + config.Vehicle.AntennaOffset = 0; + config.Vehicle.AntennaHeight = 0; + config.Vehicle.Wheelbase = Wheelbase; + config.Tool.Width = 8; + config.NumSections = 1; + config.Tool.SetSectionWidth(0, 800); + config.Tool.Overlap = 0; + config.Tool.IsToolFrontFixed = false; + config.Tool.IsToolRearFixed = true; + config.Tool.IsToolTrailing = false; + config.Tool.IsToolTBT = false; + config.Tool.HitchLength = -1.5; + config.Guidance.UTurnRadius = 8; + + _gpsService = new GpsService(); + _gpsService.Start(); + var toolPosition = new ToolPositionService(config); + var coverage = new CoverageMapService(config); + var sectionControl = new SectionControlService(toolPosition, coverage, config); + var autoSteer = new AutoSteerService(new TrackGuidanceService(), + Substitute.For(), _gpsService, config); + var headingFusion = Substitute.For(); + headingFusion.FuseHeading(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(ci => ci.ArgAt(0)); + _pipeline = new GpsPipelineService( + _gpsService, toolPosition, new TrackGuidanceService(), + sectionControl, coverage, autoSteer, + new YouTurnGuidanceService(), + new YouTurnStateMachine( + new YouTurnCreationService(NullLogger.Instance, + new Geometry.PolygonOffsetService(), config), + new YouTurnPathingService(NullLogger.Instance, config), + NullLogger.Instance, config), + Substitute.For(), + new PipelineIntents(), + headingFusion, + NullLogger.Instance, + config, + new PositionEstimator()); + + _plane = new LocalPlane(_origin, new SharedFieldProperties()); + _pipeline.SetLocalPlane(_plane); + _pipeline.SynchronousMode = true; + _pipeline.Start(); + _results = new List(); + _pipeline.CycleCompleted += r => _results.Add(r); + } + + [TearDown] + public void TearDown() + { + _pipeline.Stop(); + _gpsService.Stop(); + } + + // A round field of 30 m radius, its fence clockwise from the west point (the seam): the boundary + // curve's first lap (pass 0) runs 4 m in, round a 26 m circle; at the seam it heads north. + private const double FieldRadius = 30; + private const double LapRadius = 26; + + private void Open() + { + const int n = 120; + var fence = Enumerable.Range(0, n).Select(i => + { + double a = 2 * Math.PI * i / n; + return new Vec3(-FieldRadius * Math.Cos(a), FieldRadius * Math.Sin(a), 0); + }).ToList(); + _pipeline.SetBoundary(new Boundary + { + OuterBoundary = new BoundaryPolygon + { + Points = fence.Select(p => new BoundaryPoint(p.Easting, p.Northing, 0)).ToList(), + IsHard = true, + }, + }); + _pipeline.SetActiveTrack(new Models.Track.Track + { + Name = "Boundary Curve", + Points = CurveProcessing.DensifyLoop(fence), + Type = Models.Track.TrackType.Curve, + IsClosed = true, + }, passNumber: 0, nudgeOffset: 0); + _pipeline.SetAutoSteerEngaged(true); + } + + private GpsCycleResult Cycle(double e, double n, double headingRad) + { + var w = _plane.ConvertGeoCoordToWgs84(new GeoCoord(n, e)); + _gpsService.UpdateGpsData(new GpsData + { + CurrentPosition = new Position + { + Latitude = w.Latitude, Longitude = w.Longitude, + Heading = headingRad * 180.0 / Math.PI, Speed = SpeedKmh / 3.6, // m/s + }, + FixQuality = 4, + IsValid = true, + }); + return _results[^1]; + } + + // AutoSteer turned on with the tractor metres past the seam and + // metres outside the lap (- inside), heading along it: it drives on round + // the lap (40 m) and stays on it, never turning round. + [Test] + public void AutoSteer_engaged_at_the_seam_drives_on_round_the_lap( + [Values(-3.0, -1.0, 0.0, 0.5, 2.0)] double along, [Values(-0.4, 0.4)] double off) + { + Open(); + double a0 = along / LapRadius, r0 = LapRadius + off; + double e = -r0 * Math.Cos(a0), n = r0 * Math.Sin(a0), h = a0, step = SpeedKmh / 3.6 * 0.1; + double worstHeading = 0, worstOff = 0; + int cycles = (int)(40 / step); + for (int i = 0; i < cycles; i++) + { + var r = Cycle(e, n, h); + Assert.That(r.AutoSteerDisengagedThisCycle, Is.False, r.DisengageReason); + double steer = r.Guidance!.SteerAngle * Math.PI / 180.0; + h += step / Wheelbase * Math.Tan(steer); + e += Math.Sin(h) * step; + n += Math.Cos(h) * step; + double a = Math.Atan2(n, -e); // where round the lap: its heading there is a + worstHeading = Math.Max(worstHeading, Math.Abs(Math.IEEERemainder(h - a, 2 * Math.PI)) * 180 / Math.PI); + worstOff = Math.Max(worstOff, Math.Abs(Math.Sqrt(e * e + n * n) - LapRadius)); + } + double gone = Math.IEEERemainder(Math.Atan2(n, -e) - a0, 2 * Math.PI) * LapRadius; + TestContext.Out.WriteLine($"driven {gone:F1} m round the lap, worst {worstHeading:F0}° off it, {worstOff:F2} m off it"); + Assert.Multiple(() => + { + Assert.That(gone, Is.GreaterThan(35), "on round the lap"); + Assert.That(worstHeading, Is.LessThan(30), "never turned round"); + Assert.That(worstOff, Is.LessThan(1.0)); + }); + } +} From be920f7ae8d56402fb5cf07345bb59f35bf4054b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 23:28:55 +0000 Subject: [PATCH 04/24] Closed curve passes: the arc round the seam goes through the collision test The arc that closes a loop at its seam (#450) was the one arc whose points skipped the collision test every other arc point goes through. With the seam at the tip of a narrow slot into the field (its sides closer than twice the offset), the test removed the pass's points along both sides of the slot, and the untested arc put the whole way round the tip into the pass: out across the fence beyond it (8.7 m on a 12 m lap in the test, 8.4 m on the job harness's Couto Ribeira lap 2), back across the pass, so the pass crossed itself. AutoSteer engaged near the crossing could take the other leg and turn round. Now the seam's arc is tested like the others, and the pass at the seam is the same as anywhere else on the loop. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDDmANdNEsud9B3UMsp --- .../Guidance/CurveProcessing.cs | 9 +++- .../ClosedPassSeamTests.cs | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs b/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs index 21ec33aa8..f9ed68ac6 100644 --- a/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs +++ b/Shared/AgOpenWeb.Models/Guidance/CurveProcessing.cs @@ -661,11 +661,16 @@ public static (List Points, double PercentRemoved, bool StartKept, bool En // A loop's first and last points are offset along different sides of the seam corner: // join them, or the pass (and its guidelines) stop short there (#450). Round the join - // on the outside of the seam's bend, as at any other. + // on the outside of the seam's bend, as at any other, with the arc's points through the + // same collision test: untested, a seam at the tip of a narrow slot into the field (its + // sides closer than twice the offset) put the whole arc round the tip into the pass, out + // across the fence beyond it (8.7 m on a 12 m lap), and the pass crossed itself coming + // back. if (closed && offsetPoints.Count > 2) { if (refCount > 2) - AddArc(offsetPoints, originalPoints[0], originalPoints[refCount - 2].Heading, originalPoints[0].Heading, offsetDistance); + AddArc(offsetPoints, originalPoints[0], originalPoints[refCount - 2].Heading, originalPoints[0].Heading, offsetDistance, + (e, n) => near != null ? !near.AnyCloser(originalPoints, e, n, distSqAway) : !AnyCloser(originalPoints, e, n, distSqAway)); var first = offsetPoints[0]; var last = offsetPoints[^1]; if (Math.Abs(first.Easting - last.Easting) > 1e-6 || Math.Abs(first.Northing - last.Northing) > 1e-6) diff --git a/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs b/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs index 805100e4c..a942e18d5 100644 --- a/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs +++ b/Tests/AgOpenWeb.Models.Tests/ClosedPassSeamTests.cs @@ -80,6 +80,53 @@ public void An_inner_lap_of_a_square_field_does_not_double_back_at_its_seam_corn Assert.That(SharpestTurnRound(pass), Is.LessThan(120), "no stretch doubles back"); } + // A field with a 4 m slot into it from the north, its tip 2 m from the south fence, the + // loop's seam at the tip: the closing arc round the tip was put in untested, out across the + // south fence (8.7 m on the 12 m lap), and the lap crossed itself coming back. + private static List SlotFromItsTip() => CurveProcessing.DensifyLoop(new List + { + new(96, 2, 0), new(100, 2, 0), new(100, 120, 0), new(200, 120, 0), + new(200, 0, 0), new(0, 0, 0), new(0, 120, 0), new(96, 120, 0), + }); + + // Segments i and j (not neighbours) of the line cross each other. + private static int SelfCrossings(IReadOnlyList p) + { + int count = 0, n = p.Count - 1; + for (int i = 0; i < n; i++) + for (int j = i + 10; j < n; j++) + { + if (i < 10 && j >= n - 10 + i) continue; // neighbours across the seam + double r1 = p[i + 1].Easting - p[i].Easting, r2 = p[i + 1].Northing - p[i].Northing; + double s1 = p[j + 1].Easting - p[j].Easting, s2 = p[j + 1].Northing - p[j].Northing; + double den = r1 * s2 - r2 * s1; + if (Math.Abs(den) < 1e-12) continue; + double t = ((p[j].Easting - p[i].Easting) * s2 - (p[j].Northing - p[i].Northing) * s1) / den; + double u = ((p[j].Easting - p[i].Easting) * r2 - (p[j].Northing - p[i].Northing) * r1) / den; + if (t >= 0 && t <= 1 && u >= 0 && u <= 1) count++; + } + return count; + } + + [Test] + public void A_lap_whose_seam_is_at_the_tip_of_a_slot_stays_in_the_field_and_does_not_cross_itself([Values(4.0, 12.0, 20.0)] double d) + { + var loop = SlotFromItsTip(); + foreach (var pass in new[] + { + CurveProcessing.CreateOffsetCurveWithInfo(loop, d, closed: true).Points, + CurveProcessing.CurvePass(loop, d, closed: true, minTurnRadius: 8).Points, + }) + { + Assert.That(SelfCrossings(pass), Is.Zero, "the lap does not cross itself"); + // Like any lap of a field narrower than twice its offset, it may join across the slot + // (the pipeline leaves that stretch out, ClosedPassFitTests), never across the fence. + foreach (var p in pass) + Assert.That(GeometryMath.IsPointInPolygon(loop, p) || (p.Easting > 96 && p.Easting < 100 && p.Northing > 2), Is.True, + FormattableString.Invariant($"({p.Easting:F1}, {p.Northing:F1}) is out across the fence")); + } + } + [Test] public void Every_point_of_an_inner_lap_keeps_its_distance_from_the_fence([Values(4.0, 12.0)] double d) { From 5396285ce686682ff092c6256b8c02df14cc9ff9 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:25:15 -0600 Subject: [PATCH 05/24] compare-reports: read the findings tables of the web client and working day reports Those two harnesses list findings as a table, not the numbered list the parser read, so a fix-vs-base compare of them said '0 new, 0 gone, 0 in both' with findings on both sides. Columns are found by their header; the scenarios column gives the count. Co-Authored-By: Claude Opus 5.5 --- Tools/combine-job-reports.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Tools/combine-job-reports.py b/Tools/combine-job-reports.py index 5bd4e142b..ebfdf8bc6 100755 --- a/Tools/combine-job-reports.py +++ b/Tools/combine-job-reports.py @@ -7,7 +7,8 @@ combine-job-reports.py OUT.md LABEL=REPORT.md [LABEL=REPORT.md ...] Each report starts with "## Findings (each kind once, most serious first)": numbered entries -"N. **Severity** `CODE` ×k: text" with " Seen in: J1, J2" under them. The jobs share one +"N. **Severity** `CODE` ×k: text" with " Seen in: J1, J2" under them (or a table, "| Code | +Severity | ...", in the web client and working day reports). The jobs share one engine, so one cause shows up in several reports under the same code; the combined report lists each code once, with every harness and job it was seen in and the first harness's text, most serious first. The full reports stay as their own artifacts. @@ -18,6 +19,27 @@ ORDER = ["Safety", "DataLoss", "Bug", "Inconsistency", "Improvement", "Harness"] # Some harnesses put the kind after the severity: "**Bug** (app defect) `CODE`" (dump replay). ENTRY = re.compile(r"^\d+\. \*\*(?P[A-Za-z]+)\*\* (?:\([^)]*\) )?`(?P[A-Z0-9-]+)` ×(?P\d+): (?P.*)$") +# The web client and working day harnesses list them as a table instead: "| Code | Severity | ..." +# with the text in the last column ("First seen as" or "Finding") and, in the web client's, +# the scenarios it was seen in ("Scenarios"). Cells escape "|" as "\|". +CELL_SPLIT = re.compile(r"(? Date: Fri, 13 Nov 2026 05:00:24 -0600 Subject: [PATCH 06/24] field.geojson: a format version, and what this build doesn't read is kept - The metadata feature carries formatVersion (1; none reads as 1). Adding a role or a property doesn't change it; a change of meaning raises it. - A field in a newer format is read but never written: every save of field.geojson leaves such a file alone, and opening it tells the operator changes aren't saved. - Each part (field metadata, boundaries, headland, tracks, flags, headland lines, background) keeps the properties of its feature that this build doesn't read and writes them back; a property it reads is never kept as unknown. Members of the collection and of features beyond type/geometry/properties are kept too. - FIELD_FILE_FORMATS.md: the version and the rules. Before: a newer build's (or another tool's) property on a track, boundary or flag was dropped the next time this build saved that part. Co-Authored-By: Claude Opus 5.5 --- Docs/FIELD_FILE_FORMATS.md | 12 +- Shared/AgOpenWeb.Models/BoundaryPolygon.cs | 4 + Shared/AgOpenWeb.Models/Field.cs | 4 + Shared/AgOpenWeb.Models/Field/Flag.cs | 4 + Shared/AgOpenWeb.Models/FieldBackground.cs | 4 + .../AgOpenWeb.Models/GeoJson/GeoJsonModels.cs | 11 ++ .../AgOpenWeb.Models/Guidance/HeadlandLine.cs | 4 + Shared/AgOpenWeb.Models/Track/Track.cs | 4 + .../GeoJson/GeoJsonFieldService.cs | 117 +++++++++-- Shared/AgOpenWeb.ViewModels/MainViewModel.cs | 5 + .../FieldFormatVersionTests.cs | 183 ++++++++++++++++++ 11 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 Tests/AgOpenWeb.Services.Tests/FieldFormatVersionTests.cs diff --git a/Docs/FIELD_FILE_FORMATS.md b/Docs/FIELD_FILE_FORMATS.md index 8eae47641..9b2f36ac6 100644 --- a/Docs/FIELD_FILE_FORMATS.md +++ b/Docs/FIELD_FILE_FORMATS.md @@ -67,7 +67,7 @@ Their points are `[longitude, latitude, heading]`: drop the third value, which i | Role | Geometry | Count | Properties | | --- | --- | --- | --- | -| `metadata` | Point at the origin | Exactly 1, first | `name`, `originLatitude`, `originLongitude`, `convergence`, `areaHectares`, `createdDate`, `lastModifiedDate` | +| `metadata` | Point at the origin | Exactly 1, first | `formatVersion`, `name`, `originLatitude`, `originLongitude`, `convergence`, `areaHectares`, `createdDate`, `lastModifiedDate` | | `outer-boundary` | Polygon, 1 ring, closed, with headings | 0 or 1 | `isDriveThrough`, `isHard`, `areaHectares` | | `inner-boundary` | Polygon, as above | 0 or more | `isDriveThrough`, `isHard`, `areaHectares` | | `headland` | Polygon, as above | 0 or 1 | `isDriveThrough`, `isHard`, `areaHectares` (only geometry is read) | @@ -89,7 +89,14 @@ Property details: - **`headland-line`:** headland guidance paths built from the boundary. `moveDistance` in metres; `mode` and `aPointIndex` are AgOpenWeb builder state, preserve them as read. - **Mercator bounds:** EPSG:3857 metres of the image, present only when the image was captured from a web-map tile service. -A reader must ignore roles and properties it doesn't know, and a writer must keep them: AgOpenWeb saves each part on its own and leaves other features untouched. +A reader must ignore roles and properties it doesn't know, and a writer must keep them: AgOpenWeb saves each part on its own and leaves other features untouched, and writes a part's properties it doesn't read back as they were (a newer build's, another tool's). Members of a feature other than `type`, `geometry` and `properties` (an `id`, a `bbox`) are kept on the features AgOpenWeb doesn't rewrite; new data goes in `properties`. + +### Format version + +- **`formatVersion`:** integer, on the `metadata` feature; a file without one is version `1`. This document describes version `1`. +- **Adding** a role or a property does not change the version: an older reader ignores it and keeps it. +- **The version goes up** only when a change alters what something already in the file means (a property's unit or reference point, a role's geometry). Such a change says here how the earlier version is read. +- **A file in a newer version than the reader knows** is read as far as it understands it and is never written: AgOpenWeb opens such a field, tells the operator that changes to its boundaries, tracks, flags and headland are not saved, and writes nothing to its `field.geojson`. Another tool should do the same. ### Example @@ -104,6 +111,7 @@ The metadata and boundary come from a real field (ring shortened to 3 points); t "geometry": { "type": "Point", "coordinates": [-87.1802554, 32.59058587] }, "properties": { "role": "metadata", + "formatVersion": 1, "name": "Test1", "originLatitude": 32.59058587, "originLongitude": -87.1802554, diff --git a/Shared/AgOpenWeb.Models/BoundaryPolygon.cs b/Shared/AgOpenWeb.Models/BoundaryPolygon.cs index ce579d0c7..4b60bef25 100644 --- a/Shared/AgOpenWeb.Models/BoundaryPolygon.cs +++ b/Shared/AgOpenWeb.Models/BoundaryPolygon.cs @@ -16,6 +16,10 @@ namespace AgOpenWeb.Models; /// public class BoundaryPolygon { + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + /// /// Boundary points (Easting, Northing, Heading in local coordinates) /// Minimum 3 points required for valid polygon diff --git a/Shared/AgOpenWeb.Models/Field.cs b/Shared/AgOpenWeb.Models/Field.cs index 1190ebbea..e49018ff7 100644 --- a/Shared/AgOpenWeb.Models/Field.cs +++ b/Shared/AgOpenWeb.Models/Field.cs @@ -13,6 +13,10 @@ namespace AgOpenWeb.Models; /// public class Field { + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); public string Name { get; set; } = string.Empty; diff --git a/Shared/AgOpenWeb.Models/Field/Flag.cs b/Shared/AgOpenWeb.Models/Field/Flag.cs index 5b21f7962..310bfe314 100644 --- a/Shared/AgOpenWeb.Models/Field/Flag.cs +++ b/Shared/AgOpenWeb.Models/Field/Flag.cs @@ -14,6 +14,10 @@ public enum FlagColor public class Flag : ObservableObject { + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + private FlagColor _flagColor; private string _name = ""; private string _notes = ""; diff --git a/Shared/AgOpenWeb.Models/FieldBackground.cs b/Shared/AgOpenWeb.Models/FieldBackground.cs index cebc29f78..37975bb09 100644 --- a/Shared/AgOpenWeb.Models/FieldBackground.cs +++ b/Shared/AgOpenWeb.Models/FieldBackground.cs @@ -18,6 +18,10 @@ public sealed record FieldBackground( { /// The image file name AgOpenWeb uses in a field folder. public const string DefaultImageFile = "background.png"; + + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; init; } } /// Web-Mercator (EPSG:3857) bounds of an image, in metres. diff --git a/Shared/AgOpenWeb.Models/GeoJson/GeoJsonModels.cs b/Shared/AgOpenWeb.Models/GeoJson/GeoJsonModels.cs index 8cf3e5f11..84a90dadf 100644 --- a/Shared/AgOpenWeb.Models/GeoJson/GeoJsonModels.cs +++ b/Shared/AgOpenWeb.Models/GeoJson/GeoJsonModels.cs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Collections.Generic; +using System.Text.Json; using System.Text.Json.Serialization; namespace AgOpenWeb.Models.GeoJson; @@ -18,6 +19,10 @@ public class GeoJsonFeatureCollection [JsonPropertyName("features")] public List Features { get; set; } = new(); + + /// Other members (a "bbox", another tool's), kept as they were. + [JsonExtensionData] + public Dictionary? Other { get; set; } } /// @@ -33,6 +38,11 @@ public class GeoJsonFeature [JsonPropertyName("properties")] public Dictionary Properties { get; set; } = new(); + + /// Other members (an "id", a "bbox"), kept as they were on a feature this build + /// does not rewrite. New data of AgOpenWeb's goes in . + [JsonExtensionData] + public Dictionary? Other { get; set; } } /// @@ -74,6 +84,7 @@ public static class GeoJsonTypes public static class FieldPropertyKeys { public const string Role = "role"; + public const string FormatVersion = "formatVersion"; public const string Name = "name"; public const string TrackType = "trackType"; public const string IsClosed = "isClosed"; diff --git a/Shared/AgOpenWeb.Models/Guidance/HeadlandLine.cs b/Shared/AgOpenWeb.Models/Guidance/HeadlandLine.cs index 656c522f9..9b1a12d88 100644 --- a/Shared/AgOpenWeb.Models/Guidance/HeadlandLine.cs +++ b/Shared/AgOpenWeb.Models/Guidance/HeadlandLine.cs @@ -37,6 +37,10 @@ public HeadlandLine() /// public class HeadlandPath { + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + /// /// Track points (position + heading) /// diff --git a/Shared/AgOpenWeb.Models/Track/Track.cs b/Shared/AgOpenWeb.Models/Track/Track.cs index 58789e7c7..3e1339241 100644 --- a/Shared/AgOpenWeb.Models/Track/Track.cs +++ b/Shared/AgOpenWeb.Models/Track/Track.cs @@ -56,6 +56,10 @@ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + /// The properties of this part's feature in field.geojson that this build does not + /// read (a newer build's, another tool's), written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + /// /// Track name for display and file storage. /// diff --git a/Shared/AgOpenWeb.Services/GeoJson/GeoJsonFieldService.cs b/Shared/AgOpenWeb.Services/GeoJson/GeoJsonFieldService.cs index 7fa99633f..392ffe51f 100644 --- a/Shared/AgOpenWeb.Services/GeoJson/GeoJsonFieldService.cs +++ b/Shared/AgOpenWeb.Services/GeoJson/GeoJsonFieldService.cs @@ -25,6 +25,48 @@ public partial class GeoJsonFieldService { private const string FileName = "field.geojson"; + /// + /// The version of the format this build writes (the metadata feature's "formatVersion"; a + /// file without one is version 1). A new role or a new property doesn't change it: a reader + /// ignores what it doesn't know and a writer keeps it (the parts' UnknownProperties). Raise + /// it only when a change alters what something already in the file means. A build that + /// finds a higher version reads the field but writes nothing to it (), + /// so it can't overwrite what it doesn't understand. + /// + public const int CurrentFormatVersion = 1; + + // The properties each role's reader reads (and its builder writes). Every other property of + // a feature is kept in the part's UnknownProperties and written back. + private static readonly HashSet MetadataKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.FormatVersion, FieldPropertyKeys.Name, + FieldPropertyKeys.OriginLatitude, FieldPropertyKeys.OriginLongitude, FieldPropertyKeys.Convergence, + FieldPropertyKeys.AreaHectares, FieldPropertyKeys.CreatedDate, FieldPropertyKeys.LastModifiedDate, + }; + private static readonly HashSet BoundaryKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.IsDriveThrough, FieldPropertyKeys.IsHard, FieldPropertyKeys.AreaHectares, + }; + private static readonly HashSet TrackKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.Name, FieldPropertyKeys.TrackType, FieldPropertyKeys.IsClosed, + FieldPropertyKeys.NoPassOffset, FieldPropertyKeys.NudgeDistance, FieldPropertyKeys.IsVisible, + }; + private static readonly HashSet FlagKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.Name, FieldPropertyKeys.Color, FieldPropertyKeys.Id, FieldPropertyKeys.Notes, + }; + private static readonly HashSet HeadlandLineKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.Name, FieldPropertyKeys.MoveDistance, FieldPropertyKeys.Mode, + FieldPropertyKeys.APointIndex, + }; + private static readonly HashSet BackgroundKeys = new() + { + FieldPropertyKeys.Role, FieldPropertyKeys.Image, FieldPropertyKeys.MercatorMinX, FieldPropertyKeys.MercatorMaxX, + FieldPropertyKeys.MercatorMinY, FieldPropertyKeys.MercatorMaxY, + }; + private static readonly JsonSerializerOptions SerializerOptions = new() { WriteIndented = true, @@ -100,13 +142,15 @@ public static void Save(Models.Field field, IReadOnlyList? t try { existing = ReadCollection(path); } catch (Exception ex) when (ex is InvalidDataException or JsonException) { } } + if (existing != null && FormatVersionOf(existing) > CurrentFormatVersion) + return; // a newer build's field: read, never written (CurrentFormatVersion) if (existing != null) features.AddRange(existing.Features.Where(f => { var role = GetStringProp(f, FieldPropertyKeys.Role) ?? ""; return !FieldRoles.Contains(role) && !(tracks != null && role == FeatureRoles.Track); })); - Write(path, new GeoJsonFeatureCollection { Features = features }); + Write(path, new GeoJsonFeatureCollection { Features = features, Other = existing?.Other }); } } @@ -151,6 +195,8 @@ private static void ReplaceRole(string fieldDirectory, string role, Func CurrentFormatVersion) + return; // a newer build's field: read, never written (CurrentFormatVersion) var geo = ProjectionOf(fc); fc.Features.RemoveAll(f => GetStringProp(f, FieldPropertyKeys.Role) == role); fc.Features.AddRange(build(geo)); @@ -178,6 +224,47 @@ private static GeoJsonFeatureCollection ReadCollection(string path) ?? throw new InvalidDataException("Failed to deserialize GeoJSON"); } + /// The format version of the field's file (1 when it has none, or no file). + public static int FormatVersion(string fieldDirectory) + { + var path = Path.Combine(fieldDirectory, FileName); + if (!File.Exists(path)) return CurrentFormatVersion; + try { return FormatVersionOf(ReadCollection(path)); } + catch (Exception ex) when (ex is InvalidDataException or JsonException or IOException) { return CurrentFormatVersion; } + } + + /// + /// The field was written by a newer build in a format this one doesn't fully understand: it + /// is read and can be worked, but nothing is written to its field.geojson. + /// + public static bool IsNewerFormat(string fieldDirectory) => FormatVersion(fieldDirectory) > CurrentFormatVersion; + + private static int FormatVersionOf(GeoJsonFeatureCollection fc) + { + var meta = fc.Features.FirstOrDefault(f => GetStringProp(f, FieldPropertyKeys.Role) == FeatureRoles.Metadata); + if (meta == null || !HasProp(meta, FieldPropertyKeys.FormatVersion)) return 1; + return Math.Max(1, GetIntProp(meta, FieldPropertyKeys.FormatVersion)); + } + + /// A feature's properties outside , or null if none. + private static IReadOnlyDictionary? Unknown(GeoJsonFeature f, HashSet known) + { + Dictionary? extra = null; + foreach (var (key, value) in f.Properties) + if (!known.Contains(key)) + (extra ??= new Dictionary())[key] = value; + return extra; + } + + /// Add a part's unknown properties back; a property the builder wrote wins. + private static Dictionary WithUnknown(Dictionary props, IReadOnlyDictionary? unknown) + { + if (unknown != null) + foreach (var (key, value) in unknown) + props.TryAdd(key, value); + return props; + } + private static GeoJsonFeature MetadataOf(GeoJsonFeatureCollection fc) => fc.Features.FirstOrDefault(f => GetStringProp(f, FieldPropertyKeys.Role) == FeatureRoles.Metadata) ?? throw new InvalidDataException("GeoJSON missing metadata feature"); @@ -220,6 +307,7 @@ public static (Models.Field field, List tracks) Load(string DirectoryPath = fieldDirectory, Origin = new Position { Latitude = originLat, Longitude = originLon }, Convergence = GetDoubleProp(metaFeature, FieldPropertyKeys.Convergence), + UnknownProperties = Unknown(metaFeature, MetadataKeys), }; var nameProp = GetStringProp(metaFeature, FieldPropertyKeys.Name); @@ -331,9 +419,10 @@ private static GeoJsonFeature BuildMetadataFeature(Models.Field field) Type = GeoJsonTypes.Point, Coordinates = new[] { field.Origin.Longitude, field.Origin.Latitude } }, - Properties = new Dictionary + Properties = WithUnknown(new Dictionary { [FieldPropertyKeys.Role] = FeatureRoles.Metadata, + [FieldPropertyKeys.FormatVersion] = CurrentFormatVersion, [FieldPropertyKeys.Name] = field.Name, [FieldPropertyKeys.OriginLatitude] = field.Origin.Latitude, [FieldPropertyKeys.OriginLongitude] = field.Origin.Longitude, @@ -341,7 +430,7 @@ private static GeoJsonFeature BuildMetadataFeature(Models.Field field) [FieldPropertyKeys.AreaHectares] = field.TotalArea, [FieldPropertyKeys.CreatedDate] = field.CreatedDate.ToString("O"), [FieldPropertyKeys.LastModifiedDate] = field.LastModifiedDate.ToString("O"), - } + }, field.UnknownProperties) }; } @@ -355,13 +444,13 @@ private static GeoJsonFeature BuildBoundaryFeature(Projection geo, BoundaryPolyg Type = GeoJsonTypes.Polygon, Coordinates = new[] { ring } }, - Properties = new Dictionary + Properties = WithUnknown(new Dictionary { [FieldPropertyKeys.Role] = role, [FieldPropertyKeys.IsDriveThrough] = polygon.IsDriveThrough, [FieldPropertyKeys.IsHard] = polygon.IsHard, [FieldPropertyKeys.AreaHectares] = polygon.AreaHectares, - } + }, polygon.UnknownProperties) }; } @@ -375,7 +464,7 @@ private static GeoJsonFeature BuildTrackFeature(Projection geo, Models.Track.Tra Type = GeoJsonTypes.LineString, Coordinates = coords.Select(c => (object)c).ToArray() }, - Properties = new Dictionary + Properties = WithUnknown(new Dictionary { [FieldPropertyKeys.Role] = FeatureRoles.Track, [FieldPropertyKeys.Name] = track.Name, @@ -384,7 +473,7 @@ private static GeoJsonFeature BuildTrackFeature(Projection geo, Models.Track.Tra [FieldPropertyKeys.NoPassOffset] = track.NoPassOffset, [FieldPropertyKeys.NudgeDistance] = track.NudgeDistance, [FieldPropertyKeys.IsVisible] = track.IsVisible, - } + }, track.UnknownProperties) }; } @@ -411,7 +500,7 @@ private static GeoJsonFeature BuildBackgroundFeature(FieldBackground bg) Type = GeoJsonTypes.Polygon, Coordinates = new[] { new object[] { new[] { w, n }, new[] { e, n }, new[] { e, s }, new[] { w, s }, new[] { w, n } } }, }, - Properties = props, + Properties = WithUnknown(props, bg.UnknownProperties), }; } @@ -451,7 +540,7 @@ private static GeoJsonFeature BuildFlagFeature(Projection geo, Flag flag) return new GeoJsonFeature { Geometry = new GeoJsonGeometry { Type = GeoJsonTypes.Point, Coordinates = new[] { lon, lat } }, - Properties = props, + Properties = WithUnknown(props, flag.UnknownProperties), }; } @@ -464,14 +553,14 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP Type = GeoJsonTypes.LineString, Coordinates = geo.ToGeoJsonCoordinates(path.TrackPoints).Select(c => (object)c).ToArray(), }, - Properties = new Dictionary + Properties = WithUnknown(new Dictionary { [FieldPropertyKeys.Role] = FeatureRoles.HeadlandLine, [FieldPropertyKeys.Name] = path.Name, [FieldPropertyKeys.MoveDistance] = path.MoveDistance, [FieldPropertyKeys.Mode] = path.Mode, [FieldPropertyKeys.APointIndex] = path.APointIndex, - } + }, path.UnknownProperties) }; } @@ -506,6 +595,7 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP } polygon.UpdateBounds(); + polygon.UnknownProperties = Unknown(feature, BoundaryKeys); return polygon; } @@ -535,6 +625,7 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP NoPassOffset = GetBoolProp(feature, FieldPropertyKeys.NoPassOffset), NudgeDistance = GetDoubleProp(feature, FieldPropertyKeys.NudgeDistance), IsVisible = GetBoolPropDefault(feature, FieldPropertyKeys.IsVisible, true), + UnknownProperties = Unknown(feature, TrackKeys), }; } @@ -553,6 +644,7 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP GetStringProp(feature, FieldPropertyKeys.Name) is { Length: > 0 } name ? name : $"Flag {id}"); if (GetStringProp(feature, FieldPropertyKeys.Notes) is { Length: > 0 } notes) flag.Notes = notes; + flag.UnknownProperties = Unknown(feature, FlagKeys); return flag; } @@ -568,6 +660,7 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP MoveDistance = GetDoubleProp(feature, FieldPropertyKeys.MoveDistance), Mode = GetIntProp(feature, FieldPropertyKeys.Mode), APointIndex = GetIntProp(feature, FieldPropertyKeys.APointIndex), + UnknownProperties = Unknown(feature, HeadlandLineKeys), }; } @@ -584,7 +677,7 @@ private static GeoJsonFeature BuildHeadlandPathFeature(Projection geo, HeadlandP return new FieldBackground(image, coords.Max(c => c[1]), coords.Min(c => c[0]), coords.Min(c => c[1]), coords.Max(c => c[0]), - mercator); + mercator) { UnknownProperties = Unknown(feature, BackgroundKeys) }; } // --------------------------------------------------------------- diff --git a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs index 7d1ad7e0e..b0661ee38 100644 --- a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs +++ b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs @@ -1991,6 +1991,11 @@ private async Task OpenFieldCoreAsync(string fieldPath, string fieldName) FieldFullyLoaded?.Invoke(field); StatusMessage = $"Opened field: {fieldName}"; + + // A newer AgOpenWeb's field: this build reads it but writes nothing to it, so it + // can't overwrite what it doesn't understand (GeoJsonFieldService.CurrentFormatVersion). + if (Services.GeoJson.GeoJsonFieldService.IsNewerFormat(fieldPath)) + ReportFailure("This field was saved by a newer AgOpenWeb: changes to its boundaries, tracks, flags and headland are not saved. Update AgOpenWeb to edit it."); } catch (Exception ex) { diff --git a/Tests/AgOpenWeb.Services.Tests/FieldFormatVersionTests.cs b/Tests/AgOpenWeb.Services.Tests/FieldFormatVersionTests.cs new file mode 100644 index 000000000..775f8d875 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/FieldFormatVersionTests.cs @@ -0,0 +1,183 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json.Nodes; +using AgOpenWeb.Models; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Guidance; +using AgOpenWeb.Services.GeoJson; +using TrackModel = AgOpenWeb.Models.Track.Track; + +namespace AgOpenWeb.Services.Tests; + +/// +/// field.geojson is versioned and extensible, so a field saved by one build is safe in another +/// (Docs/FIELD_FILE_FORMATS.md): a newer build's added properties and roles survive an older +/// build's saves, and a field in a newer format version is read but never written. +/// +[TestFixture] +public class FieldFormatVersionTests +{ + private string _dir = null!; + private string FilePath => Path.Combine(_dir, "field.geojson"); + + [SetUp] + public void SetUp() + { + _dir = Path.Combine(Path.GetTempPath(), $"agopenweb_fmt_{Guid.NewGuid():N}"); + Directory.CreateDirectory(_dir); + } + + [TearDown] + public void TearDown() + { + try { Directory.Delete(_dir, true); } catch { } + } + + private Field NewField() => new() + { + Name = "F", + DirectoryPath = _dir, + Origin = new Position { Latitude = 52.0, Longitude = 5.0 }, + Boundary = new Boundary { OuterBoundary = Square(0, 100), InnerBoundaries = { Square(40, 50) } }, + }; + + private static BoundaryPolygon Square(double min, double max) + { + var p = new BoundaryPolygon(); + foreach (var (e, n) in new[] { (min, min), (max, min), (max, max), (min, max) }) + p.Points.Add(new BoundaryPoint(e, n, 0)); + p.UpdateBounds(); + return p; + } + + /// A field with every part this build writes. + private void SaveWholeField() + { + GeoJsonFieldService.Save(NewField(), tracks: null); + GeoJsonFieldService.SaveTracks(_dir, new[] { TrackModel.FromABLine("A", new Vec3(10, 10, 0), new Vec3(10, 90, 0)) }); + GeoJsonFieldService.SaveFlags(_dir, new[] { new Flag(20, 30, FlagColor.Blue, 7, "Rock") { Notes = "big one" } }); + GeoJsonFieldService.SaveHeadlandLine(_dir, new HeadlandLine + { + Tracks = { new HeadlandPath { Name = "H", MoveDistance = 12, TrackPoints = { new Vec3(5, 5, 0), new Vec3(95, 5, 0) } } }, + }); + GeoJsonFieldService.SaveBackground(_dir, new FieldBackground("background.png", 52.001, 4.999, 51.999, 5.001, null)); + } + + private JsonObject ReadJson() => JsonNode.Parse(File.ReadAllText(FilePath))!.AsObject(); + + private void WriteJson(JsonObject o) => File.WriteAllText(FilePath, o.ToJsonString()); + + private static IEnumerable Features(JsonObject o) => o["features"]!.AsArray().Select(f => f!.AsObject()); + + private static JsonObject Props(JsonObject feature) => feature["properties"]!.AsObject(); + + private static string Role(JsonObject feature) => Props(feature)["role"]!.GetValue(); + + /// Every part's own properties as this build's load and save leave them. + private static Dictionary Marks(JsonObject o) => + Features(o).Where(f => Props(f)["newThing"] != null) + .ToDictionary(f => Role(f) + (Props(f)["name"]?.ToString() ?? ""), f => Props(f)["newThing"]!.ToJsonString()); + + /// The app's own saves of each part, after loading it. + private void LoadAndSaveEveryPart() + { + var (field, _) = GeoJsonFieldService.Load(_dir); + GeoJsonFieldService.Save(field, tracks: null); + GeoJsonFieldService.SaveTracks(_dir, GeoJsonFieldService.LoadTracks(_dir)); + GeoJsonFieldService.SaveFlags(_dir, GeoJsonFieldService.LoadFlags(_dir)); + GeoJsonFieldService.SaveHeadlandLine(_dir, GeoJsonFieldService.LoadHeadlandLine(_dir)); + GeoJsonFieldService.SaveBackground(_dir, GeoJsonFieldService.LoadBackground(_dir)); + } + + [Test] + public void A_field_is_written_with_its_format_version_and_one_without_reads_as_version_1() + { + GeoJsonFieldService.Save(NewField(), tracks: null); + var meta = Features(ReadJson()).Single(f => Role(f) == "metadata"); + Assert.That(Props(meta)["formatVersion"]!.GetValue(), Is.EqualTo(GeoJsonFieldService.CurrentFormatVersion)); + + var o = ReadJson(); + Props(Features(o).Single(f => Role(f) == "metadata")).Remove("formatVersion"); + WriteJson(o); + Assert.That(GeoJsonFieldService.FormatVersion(_dir), Is.EqualTo(1)); + Assert.That(GeoJsonFieldService.IsNewerFormat(_dir), Is.False); + } + + [Test] + public void A_newer_builds_properties_and_roles_survive_this_builds_saves() + { + SaveWholeField(); + var o = ReadJson(); + int k = 0; + foreach (var f in Features(o)) + Props(f)["newThing"] = new JsonObject { ["v"] = ++k, ["s"] = "x" }; // a property on every part + o["features"]!.AsArray().Add(new JsonObject // a role this build doesn't know + { + ["type"] = "Feature", + ["id"] = "zone-1", + ["geometry"] = new JsonObject { ["type"] = "Point", ["coordinates"] = new JsonArray(5.0, 52.0) }, + ["properties"] = new JsonObject { ["role"] = "rate-zone", ["rate"] = 120 }, + }); + o["bbox"] = new JsonArray(4.9, 51.9, 5.1, 52.1); // a member of the collection + WriteJson(o); + var before = Marks(ReadJson()); + Assume.That(before, Has.Count.EqualTo(Features(ReadJson()).Count() - 1), "every part but the new role is marked"); + + LoadAndSaveEveryPart(); + + var after = ReadJson(); + Assert.Multiple(() => + { + Assert.That(Marks(after), Is.EquivalentTo(before), "each part keeps its own unknown property"); + var zone = Features(after).SingleOrDefault(f => Role(f) == "rate-zone"); + Assert.That(zone, Is.Not.Null, "the unknown role is kept"); + Assert.That(zone?["id"]?.GetValue(), Is.EqualTo("zone-1"), "with its feature members"); + Assert.That(Props(zone!)["rate"]!.GetValue(), Is.EqualTo(120)); + Assert.That(after["bbox"]?.AsArray().Count, Is.EqualTo(4), "the collection's members are kept"); + }); + } + + [Test] + public void An_unknown_property_never_brings_back_one_this_build_removed() + { + SaveWholeField(); + var o = ReadJson(); + Props(Features(o).Single(f => Role(f) == "flag"))["newThing"] = 1; + WriteJson(o); + + var flag = GeoJsonFieldService.LoadFlags(_dir).Single(); + flag.Notes = ""; // the operator clears the notes: the builder writes none + GeoJsonFieldService.SaveFlags(_dir, new[] { flag }); + + var props = Props(Features(ReadJson()).Single(f => Role(f) == "flag")); + Assert.Multiple(() => + { + Assert.That(props.ContainsKey("notes"), Is.False, "a property this build reads is never kept as unknown"); + Assert.That(props["newThing"]!.GetValue(), Is.EqualTo(1)); + }); + } + + [Test] + public void A_field_in_a_newer_format_is_read_but_never_written() + { + SaveWholeField(); + var o = ReadJson(); + Props(Features(o).Single(f => Role(f) == "metadata"))["formatVersion"] = GeoJsonFieldService.CurrentFormatVersion + 1; + WriteJson(o); + var bytes = File.ReadAllText(FilePath); + + Assert.That(GeoJsonFieldService.IsNewerFormat(_dir), Is.True); + var (field, tracks) = GeoJsonFieldService.Load(_dir); + Assert.That(field.Boundary?.OuterBoundary?.Points, Has.Count.EqualTo(4), "it is read"); + Assert.That(tracks, Has.Count.EqualTo(1)); + + LoadAndSaveEveryPart(); + GeoJsonFieldService.SaveTracks(_dir, Array.Empty()); + field.Boundary = new Boundary(); + GeoJsonFieldService.Save(field, tracks: null); + + Assert.That(File.ReadAllText(FilePath), Is.EqualTo(bytes), "no save changed the file"); + } +} From 7e16881e2b8c8d2c4d84be4af9eaff7e72b95050 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 05:09:23 -0600 Subject: [PATCH 07/24] job.json and coverage: versioned like field.geojson - job.json keeps members this build doesn't read and writes them back; a status it doesn't know reads as InProgress (it used to make the job unreadable, so it dropped out of the job list). - A job whose job.json or coverage manifest is in a newer version is listed but never written: its coverage isn't loaded or saved, and the operator is told to start a new job. Before, a newer manifest failed validation, was set aside as damaged, and the next save replaced it and deleted the tiles it didn't know. - FIELD_FILE_FORMATS.md: the rules for job.json and the manifest. Co-Authored-By: Claude Opus 5.5 --- Docs/FIELD_FILE_FORMATS.md | 4 +- Shared/AgOpenWeb.Models/Job/Job.cs | 4 + .../Coverage/CoverageMapService.cs | 19 +++ .../Coverage/CoverageTileStore.cs | 33 +++- .../Fields/JobJsonService.cs | 64 +++++++- Shared/AgOpenWeb.ViewModels/MainViewModel.cs | 4 + .../JobFormatVersionTests.cs | 148 ++++++++++++++++++ 7 files changed, 267 insertions(+), 9 deletions(-) create mode 100644 Tests/AgOpenWeb.Services.Tests/JobFormatVersionTests.cs diff --git a/Docs/FIELD_FILE_FORMATS.md b/Docs/FIELD_FILE_FORMATS.md index 9b2f36ac6..a02184b36 100644 --- a/Docs/FIELD_FILE_FORMATS.md +++ b/Docs/FIELD_FILE_FORMATS.md @@ -175,6 +175,8 @@ A job's worked area is stored as square binary tiles on the field's plane grid: CamelCase JSON: `schemaVersion` (1), `id` (GUID), `fieldName`, `taskName` (same as the folder name), `workType`, `notes`, `startedAt`, `endedAt`, `lastOpenedAt` (ISO 8601), `status` (`"InProgress"`, `"Done"` or `"Abandoned"`), `distanceTraveledMeters`, `areaWorkedHectares`, `uTurnCount`. A `jobs//` folder without `job.json` is not listed as a job. +`schemaVersion` follows the rules of `formatVersion` (section 3): members a reader doesn't know are kept by a writer, and the version goes up only for a change of meaning. A job whose `job.json` or coverage `manifest.json` is in a newer version than the reader knows is listed, but its coverage is not read and nothing is written to the job (AgOpenWeb tells the operator to start a new job). A `status` the reader doesn't know reads as `"InProgress"`. + ### manifest.json PascalCase JSON. A previous good copy is kept as `manifest.json.bak`. @@ -193,7 +195,7 @@ PascalCase JSON. A previous good copy is kept as `manifest.json.bak`. - `DisplayCellSize`: metres per display pixel. It names the display folder: `s` + round(cell × 10⁶), e.g. 0.35 m → `s350000`. - `TotalWorkedArea`: square metres. -- `MinE` … `MaxN`: the coverage grid's bounds in plane metres. A manifest is valid only if `SchemaVersion` is 1, `DisplayCellSize` > 0, `MaxE` > `MinE` and `MaxN` > `MinN`. +- `MinE` … `MaxN`: the coverage grid's bounds in plane metres. A manifest is valid only if `SchemaVersion` is 1, `DisplayCellSize` > 0, `MaxE` > `MinE` and `MaxN` > `MinN`. A manifest (or its `.bak`) with a higher `SchemaVersion` is a newer format, not damage: it is never set aside, and the coverage folder is neither read nor written. ### Tile addressing diff --git a/Shared/AgOpenWeb.Models/Job/Job.cs b/Shared/AgOpenWeb.Models/Job/Job.cs index 0c8499625..30cf46aae 100644 --- a/Shared/AgOpenWeb.Models/Job/Job.cs +++ b/Shared/AgOpenWeb.Models/Job/Job.cs @@ -14,6 +14,10 @@ namespace AgOpenWeb.Models.Job; /// public class Job { + /// The members of its job.json that this build does not read (a newer build's), + /// written back as they were (FIELD_FILE_FORMATS.md). + public IReadOnlyDictionary? UnknownProperties { get; set; } + /// /// Stable identifier. Coverage and section-log files key off this on /// disk so renaming doesn't break references. diff --git a/Shared/AgOpenWeb.Services/Coverage/CoverageMapService.cs b/Shared/AgOpenWeb.Services/Coverage/CoverageMapService.cs index a26524eb7..8e1830799 100644 --- a/Shared/AgOpenWeb.Services/Coverage/CoverageMapService.cs +++ b/Shared/AgOpenWeb.Services/Coverage/CoverageMapService.cs @@ -1713,6 +1713,7 @@ public void ResetUserArea() private bool _displayFullSave = true; private string? _savedDirectory; // job folder the tiles on disk match (null: unknown) + private string? _newerFormatDirectory; // a newer build's coverage folder: never written (CoverageTileStore.IsNewerFormat) private string? _legacyFilesDirectory; // job imported from coverage_*.bin or Sections.txt: delete them after its first tiled save // Serialises saves: the autosave can still be running when the field-close save starts, @@ -1755,7 +1756,11 @@ public void SaveToFile(string fieldDirectory) lock (_saveLock) { string dir = Path.GetFullPath(fieldDirectory); + if (dir == _newerFormatDirectory) + return; var store = new CoverageTileStore(dir); + if (store.IsNewerFormat) + return; // a newer build's coverage: this build would replace its manifest and tiles TileSave save; lock (_coverageLock) { @@ -1953,6 +1958,20 @@ public void LoadFromFile(string fieldDirectory) var store = new CoverageTileStore(dir); bool hasDetectionBits, hasSectionDisplay, fromTiles = false, fromOldFiles = false; + // A newer build's coverage: shown as none and never saved, so it stays as that build left it. + _newerFormatDirectory = null; + if (store.IsNewerFormat) + { + ClearAll(); + lock (_coverageLock) + { + _savedDirectory = null; + _legacyFilesDirectory = null; + } + _newerFormatDirectory = dir; + return; + } + var manifest = store.ManifestExists || Directory.Exists(store.DetectionDir) ? store.ReadManifest() ?? RecoverManifest(store) : null; diff --git a/Shared/AgOpenWeb.Services/Coverage/CoverageTileStore.cs b/Shared/AgOpenWeb.Services/Coverage/CoverageTileStore.cs index faa84aa84..75476137d 100644 --- a/Shared/AgOpenWeb.Services/Coverage/CoverageTileStore.cs +++ b/Shared/AgOpenWeb.Services/Coverage/CoverageTileStore.cs @@ -71,9 +71,36 @@ public static string TileFileName(int tx, int ty) => // ---------- manifest ---------- + /// + /// The manifest version this build writes. A coverage folder in a newer version is never + /// read or written by this build (): reading it would set the + /// manifest aside as damaged, and the next save would replace it and delete the tiles it + /// doesn't know. + /// + public const int CurrentSchemaVersion = 1; + + /// The manifest (or its backup) was written by a newer build. + public bool IsNewerFormat => + ManifestVersionOf(ManifestPath) > CurrentSchemaVersion + || ManifestVersionOf(ManifestPath + AtomicJsonFile.BackupSuffix) > CurrentSchemaVersion; + + private static int ManifestVersionOf(string path) + { + if (!File.Exists(path)) return CurrentSchemaVersion; + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(path)); + if (doc.RootElement.ValueKind == JsonValueKind.Object + && doc.RootElement.TryGetProperty(nameof(Manifest.SchemaVersion), out var v) && v.TryGetInt32(out int n)) + return n; + } + catch (Exception ex) when (ex is JsonException or IOException) { } + return CurrentSchemaVersion; + } + public sealed class Manifest { - public int SchemaVersion { get; set; } = 1; + public int SchemaVersion { get; set; } = CurrentSchemaVersion; public double DisplayCellSize { get; set; } public double TotalWorkedArea { get; set; } public double MinE { get; set; } @@ -87,8 +114,10 @@ public void WriteManifest(Manifest manifest) => public Manifest? ReadManifest() { + if (IsNewerFormat) + return null; // never set a newer build's manifest aside as damaged var result = AtomicJsonFile.Read(ManifestPath, JsonOptions, - m => m.SchemaVersion == 1 && m.DisplayCellSize > 0 && m.MaxE > m.MinE && m.MaxN > m.MinN); + m => m.SchemaVersion == CurrentSchemaVersion && m.DisplayCellSize > 0 && m.MaxE > m.MinE && m.MaxN > m.MinN); return result.Loaded ? result.Value : null; } diff --git a/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs b/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs index a864cf875..eaa36c704 100644 --- a/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs +++ b/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs @@ -23,6 +23,12 @@ public static class JobJsonService private const string JobsFolder = "jobs"; private const string JobFileName = "job.json"; + /// + /// The job.json version this build writes. A new member doesn't change it (a reader ignores + /// it, a writer keeps it: ); a change of meaning raises it. + /// A job in a newer version, or whose coverage is (), + /// is listed but never written: . + /// public const int CurrentSchemaVersion = 1; private static readonly JsonSerializerOptions SerializerOptions = new() @@ -79,9 +85,38 @@ public static void Save(Job job, string fieldDirectory) var dir = JobDirectory(fieldDirectory, job.TaskName); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + var path = Path.Combine(dir, JobFileName); + if (SchemaVersionOf(path) > CurrentSchemaVersion) + return; // a newer build's job: read, never written + var dto = JobDto.FromJob(job); var json = JsonSerializer.Serialize(dto, SerializerOptions); - File.WriteAllText(Path.Combine(dir, JobFileName), json); + File.WriteAllText(path, json); + } + + /// + /// The job was saved by a newer build, its job.json or its coverage, in a format this one + /// doesn't fully understand: it is listed, but its coverage isn't loaded and nothing is + /// written to it, so this build can't overwrite what it doesn't understand. + /// + public static bool IsNewerFormat(string fieldDirectory, string taskName) => + SchemaVersionOf(JobFilePath(fieldDirectory, taskName)) > CurrentSchemaVersion + || new Coverage.CoverageTileStore(JobDirectory(fieldDirectory, taskName)).IsNewerFormat; + + /// A job.json's schemaVersion; 1 when it has none, or there is no readable file. + private static int SchemaVersionOf(string path) + { + if (!File.Exists(path)) return CurrentSchemaVersion; + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(path)); + foreach (var p in doc.RootElement.EnumerateObject()) + if (string.Equals(p.Name, "schemaVersion", StringComparison.OrdinalIgnoreCase) + && p.Value.TryGetInt32(out int v)) + return Math.Max(1, v); + } + catch (Exception ex) when (ex is JsonException or IOException or InvalidOperationException) { } + return CurrentSchemaVersion; } /// @@ -139,11 +174,17 @@ private sealed class JobDto public DateTime? StartedAt { get; set; } public DateTime? EndedAt { get; set; } public DateTime? LastOpenedAt { get; set; } - public JobStatus? Status { get; set; } + // Read leniently: a status a newer build added must not make the job unreadable (it would + // drop out of the list). Such a job is never written, so the stand-in is never saved. + public JsonElement? Status { get; set; } public double DistanceTraveledMeters { get; set; } public double AreaWorkedHectares { get; set; } public int UTurnCount { get; set; } + /// Members this build doesn't read, kept as they were. + [JsonExtensionData] + public Dictionary? Other { get; set; } + public static JobDto FromJob(Job j) => new() { SchemaVersion = CurrentSchemaVersion, @@ -155,10 +196,11 @@ private sealed class JobDto StartedAt = j.StartedAt, EndedAt = j.EndedAt, LastOpenedAt = j.LastOpenedAt, - Status = j.Status, + Status = JsonSerializer.SerializeToElement(j.Status, SerializerOptions), DistanceTraveledMeters = j.DistanceTraveledMeters, AreaWorkedHectares = j.AreaWorkedHectares, - UTurnCount = j.UTurnCount + UTurnCount = j.UTurnCount, + Other = j.UnknownProperties?.ToDictionary(p => p.Key, p => p.Value), }; public Job ToJob(string fieldName, string taskName) => new() @@ -171,10 +213,20 @@ private sealed class JobDto StartedAt = StartedAt ?? DateTime.Now, EndedAt = EndedAt, LastOpenedAt = LastOpenedAt ?? DateTime.Now, - Status = Status ?? JobStatus.InProgress, + Status = ReadStatus(Status), DistanceTraveledMeters = DistanceTraveledMeters, AreaWorkedHectares = AreaWorkedHectares, - UTurnCount = UTurnCount + UTurnCount = UTurnCount, + UnknownProperties = Other, }; + + private static JobStatus ReadStatus(JsonElement? e) + { + if (e is { ValueKind: JsonValueKind.String } s && Enum.TryParse(s.GetString(), true, out var named)) + return named; + if (e is { ValueKind: JsonValueKind.Number } n && n.TryGetInt32(out int i) && Enum.IsDefined(typeof(JobStatus), i)) + return (JobStatus)i; + return JobStatus.InProgress; + } } } diff --git a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs index b0661ee38..a3d54d445 100644 --- a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs +++ b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs @@ -1911,6 +1911,10 @@ private async Task OpenFieldCoreAsync(string fieldPath, string fieldName) { _coverageMapService.LoadFromFile(fieldPath, activeJob.TaskName); _logger.LogDebug($"[Coverage] Loaded coverage from {fieldPath} job={activeJob.TaskName}"); + // A newer AgOpenWeb's job: its coverage isn't read, and nothing is written to it + // (JobJsonService.IsNewerFormat), so the work done now is not kept. + if (Services.Fields.JobJsonService.IsNewerFormat(fieldPath, activeJob.TaskName)) + ReportFailure("This job was saved by a newer AgOpenWeb: its coverage is not shown and work done in it is not saved. Start a new job, or update AgOpenWeb."); } else { diff --git a/Tests/AgOpenWeb.Services.Tests/JobFormatVersionTests.cs b/Tests/AgOpenWeb.Services.Tests/JobFormatVersionTests.cs new file mode 100644 index 000000000..48487e680 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/JobFormatVersionTests.cs @@ -0,0 +1,148 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System.Security.Cryptography; +using System.Text.Json.Nodes; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Job; +using AgOpenWeb.Services.Coverage; +using AgOpenWeb.Services.Fields; + +namespace AgOpenWeb.Services.Tests; + +/// +/// job.json and the coverage manifest are versioned like field.geojson (Docs/FIELD_FILE_FORMATS.md): +/// what a newer build adds survives this build's saves, and a job in a newer format is listed +/// but never written, its coverage neither read nor replaced. +/// +[TestFixture, NonParallelizable] // ConfigurationStore is a singleton +public class JobFormatVersionTests +{ + private string _fieldDir = null!; + private ConfigurationStore _store = null!; + private const string Task = "2026-11-13_Spraying"; + + [SetUp] + public void SetUp() + { + _fieldDir = Path.Combine(Path.GetTempPath(), $"agopenweb_jobfmt_{Guid.NewGuid():N}"); + Directory.CreateDirectory(_fieldDir); + _store = new ConfigurationStore(); + ConfigurationStore.SetInstance(_store); + } + + [TearDown] + public void TearDown() + { + try { Directory.Delete(_fieldDir, true); } catch { } + } + + private string JobDir => JobJsonService.JobDirectory(_fieldDir, Task); + private string JobFile => JobJsonService.JobFilePath(_fieldDir, Task); + private string ManifestFile => Path.Combine(JobDir, "coverage", "manifest.json"); + + private Job NewJob() => new() { FieldName = Path.GetFileName(_fieldDir), TaskName = Task, WorkType = "Spraying" }; + + private void EditJson(string path, Action edit) + { + var o = JsonNode.Parse(File.ReadAllText(path))!.AsObject(); + edit(o); + File.WriteAllText(path, o.ToJsonString()); + } + + /// Every file under the job, with a hash of its content. + private Dictionary Snapshot() => + Directory.GetFiles(JobDir, "*", SearchOption.AllDirectories).ToDictionary( + f => Path.GetRelativePath(JobDir, f), + f => Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(f)))); + + private CoverageMapService Coverage() + { + var svc = new CoverageMapService(_store); + svc.SetFieldBounds(-100, 100, -100, 100); + return svc; + } + + private static void PaintStrip(CoverageMapService svc, double e0, double e1, double n0, double n1) + { + svc.StartMapping(0, new Vec2(e0, n0), new Vec2(e1, n0)); + for (double n = n0 + 1; n <= n1; n += 1) + svc.AddCoveragePoint(0, new Vec2(e0, n), new Vec2(e1, n)); + svc.StopMapping(0); + } + + [Test] + public void A_newer_builds_job_members_survive_this_builds_save() + { + JobJsonService.Save(NewJob(), _fieldDir); + EditJson(JobFile, o => o["implementId"] = "boom-24"); + + var job = JobJsonService.Load(_fieldDir, Task)!; + job.Notes = "windy"; + JobJsonService.Save(job, _fieldDir); + + var saved = JsonNode.Parse(File.ReadAllText(JobFile))!.AsObject(); + Assert.Multiple(() => + { + Assert.That(saved["implementId"]?.GetValue(), Is.EqualTo("boom-24")); + Assert.That(saved["notes"]?.GetValue(), Is.EqualTo("windy")); + }); + } + + [Test] + public void A_newer_job_is_listed_but_never_written() + { + JobJsonService.Save(NewJob(), _fieldDir); + EditJson(JobFile, o => { o["schemaVersion"] = JobJsonService.CurrentSchemaVersion + 1; o["status"] = "Paused"; }); + var bytes = File.ReadAllText(JobFile); + + var listed = JobJsonService.LoadAll(_fieldDir); + Assert.That(listed.Select(j => j.TaskName), Is.EqualTo(new[] { Task }), "a status this build doesn't know doesn't hide the job"); + Assert.That(JobJsonService.IsNewerFormat(_fieldDir, Task), Is.True); + + var job = listed[0]; + job.Status = JobStatus.Done; + JobJsonService.Save(job, _fieldDir); + Assert.That(File.ReadAllText(JobFile), Is.EqualTo(bytes)); + } + + [Test] + public void Coverage_in_a_newer_format_is_neither_read_nor_replaced() + { + JobJsonService.Save(NewJob(), _fieldDir); + var first = Coverage(); + PaintStrip(first, 0, 10, 0, 20); + first.SaveToFile(JobDir); + Assume.That(File.Exists(ManifestFile)); + EditJson(ManifestFile, o => o["SchemaVersion"] = CoverageTileStore.CurrentSchemaVersion + 1); + File.WriteAllText(Path.Combine(JobDir, "coverage", "d", "newer.tile"), "a tile this build doesn't know"); + var before = Snapshot(); + + Assert.That(JobJsonService.IsNewerFormat(_fieldDir, Task), Is.True, "the job counts as newer by its coverage"); + + var svc = Coverage(); + svc.LoadFromFile(JobDir); + Assert.That(svc.TotalWorkedArea, Is.EqualTo(0), "its coverage isn't read"); + + PaintStrip(svc, 20, 30, 0, 20); + svc.SaveToFile(JobDir); + + Assert.That(Snapshot(), Is.EqualTo(before), "nothing was set aside, replaced or deleted"); + } + + [Test] + public void The_current_versions_are_not_newer() + { + JobJsonService.Save(NewJob(), _fieldDir); + var svc = Coverage(); + PaintStrip(svc, 0, 10, 0, 20); + svc.SaveToFile(JobDir); + + Assert.That(JobJsonService.IsNewerFormat(_fieldDir, Task), Is.False); + var reopened = Coverage(); + reopened.LoadFromFile(JobDir); + Assert.That(reopened.TotalWorkedArea, Is.GreaterThan(0)); + } +} From 9b19ea48502f81a2c306e909b2fc9a0a7b734267 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 14:39:40 +0000 Subject: [PATCH 08/24] Log each lateral move, and the AB line's heading only when it changes A bug report showed the guidance line one pass over with the tractor standing still, and nothing in its log said why: a lateral move (the lateral buttons, the nudge flyout's Pass buttons) was not logged. Each one is now logged ("[Guidance] Lateral move right: pass 19R -> 20R"), and one ignored while a U-turn is driven too. The YouTurn "AB Line: abHeading" debug line was written on every 30th count of the U-turn counter: in that report it was 1273 of the 2000 lines a bug report keeps, so the log reached back only 21 minutes. It is now written when the heading changes (19 lines a minute -> 1 in AbHeadingLogTests). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDGCt1N7D8a1xv5HtTx --- CLAUDE.md | 1 + .../Pipeline/GpsPipelineService.cs | 14 ++- .../YouTurn/YouTurnStateMachine.cs | 9 +- .../Pipeline/LateralMoveTests.cs | 40 +++++- .../YouTurn/AbHeadingLogTests.cs | 115 ++++++++++++++++++ 5 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 Tests/AgOpenWeb.Services.Tests/YouTurn/AbHeadingLogTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index a14147061..663ceea95 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -466,6 +466,7 @@ and `untrack`). The map's area is set by `setMapArea(l, r)`; what that means for 4. **A client shows something a fresh connection doesn't**: compare the incremental stream with the full snapshot (coverage deltas vs `Snapshot()`) 5. **Bug Report Dump** (main menu) zips settings, state, the field, the active job's coverage tiles and the last 5 minutes of the GPS log (10 Hz; includes the sentence type, its pre-fusion heading and the receiver's differential age); with NTRIP in use, `ntrip_rtcm.txt` lists the RTCM messages the caster sent by type and `ntrip_session.txt` what the connection did and why it failed (no login in it; `system_info.txt` has an `NTRIP:` line with the same outcome). `system_info.txt` ends with each browser's GPU renderer, texture limit, canvas, frame rate and draw time (`diag.client`, `ClientDiagnostics`), flagged `SOFTWARE RENDERING` when the renderer is the CPU (llvmpipe, SwiftShader): such a device will not draw the map smoothly whatever the app does 6. **iOS Release builds hang in CI**: Use Debug configuration (Release triggers AOT compilation that hangs on runners). Simulator builds run on the interpreter (`MtouchInterpreter=all` in the csproj), so they skip AOT altogether; device builds keep it +7. **The line moved to another pass**: a lateral move (the lateral buttons, the nudge flyout's Pass buttons) is logged (`[Guidance] Lateral move right: pass 19R -> 20R`, or ignored while a U-turn is driven); the AB line's debug heading is logged only when it changes, so a dump's 2000 log lines reach further back ## Code Style diff --git a/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs b/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs index d73eb83ee..018be5b23 100644 --- a/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs +++ b/Shared/AgOpenWeb.Services/Pipeline/GpsPipelineService.cs @@ -577,7 +577,13 @@ private void ProcessCycle(GpsData data) int delta = intents.GuidanceSnap.Value ? (_guidanceWorking.IsHeadingSameWay ? -1 : 1) : (_guidanceWorking.IsHeadingSameWay ? 1 : -1); + int from = _guidanceWorking.HowManyPathsAway; _guidanceWorking.HowManyPathsAway += delta; + // Logged: the line moves a whole pass with no turn, and a tap on the lateral button is + // easy to miss. A bug report of the line on the next pass with the tractor standing still + // had nothing in its log to show what moved it. + _logger.LogInformation("[Guidance] Lateral move {Side}: pass {From} -> {To}", + intents.GuidanceSnap.Value ? "left" : "right", PassName(from), PassName(from + delta)); // The nudge stays: a lateral move is a pass, not a reset (AgOpenGPS BuildManualYouLateral). _trackGuidanceState = null; @@ -589,6 +595,9 @@ private void ProcessCycle(GpsData data) // Mirrors the direction-override re-arm in YouTurnStateMachine. YouTurnStateMachine.DiscardPlannedTurn(_youTurn); } + else if (intents.GuidanceSnap.HasValue) + _logger.LogInformation("[Guidance] Lateral move {Side} ignored: a U-turn is being driven", + intents.GuidanceSnap.Value ? "left" : "right"); // Phase D D5. Nudge accumulates (multiple clicks between drains sum). // Heading-same-way flips the sign so "left" always means left from the // driver's seat regardless of track direction. Reset wins if both @@ -1688,10 +1697,13 @@ private static GuidanceWorkingState WithLineLabel(GuidanceWorkingState g, int? l { int pass = landingPass ?? g.HowManyPathsAway; bool sameWay = landingPass.HasValue ? !g.IsHeadingSameWay : g.IsHeadingSameWay; - g.CurrentLineLabel = (sameWay ? "↑" : "↓") + (pass >= 0 ? $"{pass + 1}R" : $"{-pass}L"); + g.CurrentLineLabel = (sameWay ? "↑" : "↓") + PassName(pass); return g; } + /// The pass as its label names it: pass 0 is 1R, pass −1 is 1L. + private static string PassName(int pass) => pass >= 0 ? $"{pass + 1}R" : $"{-pass}L"; + private static GuidanceSnapshot BuildGuidanceSnapshot( GuidanceWorkingState src, Models.Track.Track? displayTrack, diff --git a/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs b/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs index 5324bc505..e152d00eb 100644 --- a/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs +++ b/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs @@ -64,6 +64,8 @@ public sealed class YouTurnStateMachine // _missedSounded: a missed turn sounds once (#570). Not re-armed by a new plan, which a // missed turn is followed by at once: only when a turn starts or U-turn is switched off. private bool _creationFailSounded, _approachAlarmed, _missedSounded; + /// The AB line heading last logged (rad), so its debug line is written when it changes. + private double _loggedAbHeading = double.NaN; private readonly YouTurnPathingService _pathing; private readonly PolygonIndexCache _headlandIndex = new(); // the zone test, every GPS cycle private readonly ILogger _logger; @@ -145,8 +147,13 @@ public YouTurnEffects Tick(in TickContext ctx, GuidanceWorkingState guidance, Yo double abDx = trackPointB.Easting - trackPointA.Easting; double abDy = trackPointB.Northing - trackPointA.Northing; abHeading = Math.Atan2(abDx, abDy); - if (turn.YouTurnCounter % 30 == 0) + // Only when it changes: written every 30th count, it was 1273 of the 2000 lines a bug + // report keeps (a simulator report), so the report reached back only 21 minutes. + if (!(Math.Abs(abHeading - _loggedAbHeading) < 1e-3)) + { + _loggedAbHeading = abHeading; _logger.LogDebug("[YouTurn] AB Line: abHeading={Deg:F1}°", abHeading * 180 / Math.PI); + } } // Is the vehicle heading the same way as the AB line (forward) or reverse? diff --git a/Tests/AgOpenWeb.Services.Tests/Pipeline/LateralMoveTests.cs b/Tests/AgOpenWeb.Services.Tests/Pipeline/LateralMoveTests.cs index 7719bfc59..bcf14602f 100644 --- a/Tests/AgOpenWeb.Services.Tests/Pipeline/LateralMoveTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Pipeline/LateralMoveTests.cs @@ -2,6 +2,7 @@ // Copyright (C) 2024-2026 AgOpenWeb Contributors // SPDX-License-Identifier: Apache-2.0 +using System; using System.Collections.Generic; using AgOpenWeb.Models; using AgOpenWeb.Models.Base; @@ -17,6 +18,7 @@ using AgOpenWeb.Services.Tool; using AgOpenWeb.Services.Track; using AgOpenWeb.Services.YouTurn; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; @@ -35,6 +37,17 @@ public class LateralMoveTests private GpsService _gpsService = null!; private GpsPipelineService _pipeline = null!; private List _results = null!; + private CaptureLogger _log = null!; + + private sealed class CaptureLogger : ILogger + { + public readonly List Messages = new(); + public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance; + public bool IsEnabled(LogLevel logLevel) => true; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? ex, + Func formatter) => Messages.Add($"{logLevel}: {formatter(state, ex)}"); + private sealed class NullScope : IDisposable { public static readonly NullScope Instance = new(); public void Dispose() { } } + } [SetUp] public void SetUp() @@ -74,7 +87,7 @@ public void SetUp() Substitute.For(), _intents, headingFusion, - NullLogger.Instance, + _log = new CaptureLogger(), config, new PositionEstimator()); @@ -125,6 +138,31 @@ public void Moving_a_pass_keeps_the_nudge() }); } + // The line moves a whole pass with no turn, and a tap on the lateral button is easy to miss: the + // log a bug report carries says so (a report of the line on the next pass with the tractor + // standing still had nothing in its log to show what moved it). + [Test] + public void A_lateral_move_is_logged() + { + var track = Models.Track.Track.FromABLine("AB", new Vec3(0, -100, 0), new Vec3(0, 100, 0)); + _pipeline.SetActiveTrack(track, passNumber: 0, nudgeOffset: 0); + _pipeline.SetAutoSteerEngaged(true); + Cycle(); + Assume.That(_log.Messages, Has.None.Contains("Lateral move"), "nothing logged before a move"); + + _intents.RequestGuidanceSnap(left: false); + Cycle(); + _intents.RequestGuidanceSnap(left: true); + Cycle(); + Cycle(); + + Assert.That(_log.Messages.FindAll(m => m.Contains("Lateral move")), Is.EqualTo(new[] + { + "Information: [Guidance] Lateral move right: pass 1R -> 2R", + "Information: [Guidance] Lateral move left: pass 2R -> 1R", + })); + } + // The pass label, as AgOpenGPS's DrawTrackInfo: an arrow for the way you drive along the line // (↑ A→B, ↓ B→A) and the pass, counted from 1 either side of the reference line. [TestCase(0.0, 0, "↑1R")] diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/AbHeadingLogTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/AbHeadingLogTests.cs new file mode 100644 index 000000000..d1aeeeda6 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/AbHeadingLogTests.cs @@ -0,0 +1,115 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using AgOpenWeb.Models; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Pipeline; +using AgOpenWeb.Services.Geometry; +using AgOpenWeb.Services.YouTurn; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; + +namespace AgOpenWeb.Services.Tests.YouTurn; + +/// +/// The AB line's debug heading was logged on every 30th count of the U-turn counter: in a +/// simulator bug report it was 1273 of the 2000 log lines a report keeps, so the report reached +/// back only 21 minutes. It is logged when the heading changes. +/// +[TestFixture] +[NonParallelizable] // ConfigurationStore is a singleton. +public class AbHeadingLogTests +{ + private sealed class CaptureLogger : ILogger + { + public readonly List Messages = new(); + public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance; + public bool IsEnabled(LogLevel logLevel) => true; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? ex, + Func formatter) => Messages.Add(formatter(state, ex)); + private sealed class NullScope : IDisposable { public static readonly NullScope Instance = new(); public void Dispose() { } } + } + + private CaptureLogger _log = null!; + private YouTurnStateMachine _sm = null!; + private Boundary _boundary = null!; + private List _cultivated = null!; + + [SetUp] + public void SetUp() + { + ConfigurationStore.SetInstance(new ConfigurationStore()); + var c = ConfigurationStore.Instance; + c.Vehicle.Wheelbase = 2.5; + c.Vehicle.MaxSteerAngle = 35; + c.Tool.Overlap = 0; + c.NumSections = 1; + c.Tool.SetSectionWidth(0, 400); + c.Guidance.UTurnRadius = 4.0; + c.Guidance.UTurnExtension = 16.0; + c.Guidance.UTurnDistanceFromBoundary = 2.0; + c.Guidance.UTurnStyle = 0; + + var offset = new PolygonOffsetService(); + _log = new CaptureLogger(); + _sm = new YouTurnStateMachine( + new YouTurnCreationService(NullLogger.Instance, offset, c), + new YouTurnPathingService(NullLogger.Instance, c), + _log, c); + + var outer = new List { new(-50, -50), new(50, -50), new(50, 50), new(-50, 50) }; + _boundary = new Boundary + { + OuterBoundary = new BoundaryPolygon + { + Points = outer.Select(p => new BoundaryPoint(p.Easting, p.Northing, 0)).ToList(), + }, + }; + _cultivated = offset.CalculatePointHeadings(new List + { + new(-40, -40), new(40, -40), new(40, 40), new(-40, 40), + }); + } + + private YouTurnStateMachine.TickContext Ctx(Models.Track.Track track) => new( + // Pass 0 (x = 2) heading north, 15 m before the north end: a turn is planned. + new Position { Easting = 2, Northing = 25, Heading = 0, Speed = 2.78 }, + track, _boundary, _cultivated, + UTurnSkipRows: 0, IsSkipWorkedMode: false, + HeadlandCalculatedWidth: 10.0, HeadlandDistance: 10.0, + IsAlternateSkipMode: false); + + private int AbHeadingLines() => _log.Messages.Count(m => m.StartsWith("[YouTurn] AB Line: abHeading=", StringComparison.Ordinal)); + + [Test] + public void The_AB_heading_is_logged_once_while_it_stays_the_same() + { + var track = Models.Track.Track.FromABLine("AB", new Vec3(0, -100, 0), new Vec3(0, 100, 0)); + var guidance = new GuidanceWorkingState { HowManyPathsAway = 0, IsHeadingSameWay = true }; + var turn = new YouTurnWorkingState { IsEnabled = true }; + + // A minute at 10 Hz, as the pipeline ticks: the counter goes up before each tick. + for (int i = 0; i < 600; i++) + { + turn.YouTurnCounter++; + _sm.Tick(Ctx(track), guidance, turn); + } + Assert.That(turn.TurnPath, Is.Not.Null, "the scenario: a turn is planned"); + Assert.That(AbHeadingLines(), Is.EqualTo(1)); + + // Another line, another heading: logged again, once. + var angled = Models.Track.Track.FromABLine("AB2", new Vec3(-20, -100, 0), new Vec3(20, 100, 0)); + for (int i = 0; i < 600; i++) + { + turn.YouTurnCounter++; + _sm.Tick(Ctx(angled), guidance, turn); + } + Assert.That(AbHeadingLines(), Is.EqualTo(2)); + } +} From 0608668ab9cdcfbe95131e1df8495a6d2e6738d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 14:39:48 +0000 Subject: [PATCH 09/24] U-turn tried again off a hard fence stays at the end of the pass When the implement would cross a hard outer boundary in the turn, CreateTurnPath tries again with the whole fence's turn line moved in (extraSetback). Where the fence comes in beside the pass, the moved line also crosses the pass there, long before the end, and FindCurveTurnPoint took that first crossing ahead: on a real field an 8 m front boom near the fence at the far end turned 260 m before the end of its pass, and the strip it left on the next pass was never sprayed. The first try's end (the pass's first point outside the turn area) is passed to the tries that follow (YouTurnCreationInput.TurnLineEndIndex); they walk back from it to the moved line's crossing nearest to it (TurnLineEndOnRetry) instead of searching ahead from the tractor. TurnSetbackStaysAtEndTests: an 8 m boom ahead of the pivot and one behind it, the fence coming in to 5 m from the pass for 30 m: the turn's top was at y -23.5 / -22.0, beside the narrowing; now 92.8 / 93.8, as on the same field without it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VKEBDGCt1N7D8a1xv5HtTx --- CLAUDE.md | 1 + .../YouTurn/YouTurnCreationInput.cs | 9 ++ .../YouTurn/YouTurnCreationOutput.cs | 6 + .../YouTurnCreationService.Orchestration.cs | 6 + .../YouTurn/YouTurnCreationService.cs | 44 +++++- .../YouTurn/TurnSetbackStaysAtEndTests.cs | 135 ++++++++++++++++++ 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index a14147061..2f724c7d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -525,6 +525,7 @@ turns from the on-screen buttons) with paths built by `YouTurnCreationService`: - While a turn is driven (`IsExecuting`) the pass shown is the one it lands on (`GpsPipelineService.TurnTargetPass`): the highlighted line, the tool's band, the guidelines and the pass label, as AgOpenGPS, which moves to the next pass when the turn starts (`CYouTurn.YouTurnTrigger`). The pass worked (`HowManyPathsAway`: the worked-pass mark, the snake order, a turn backed out of, #588) still moves on in `CompleteTurn`. - A turn that loops (the next pass closer than twice the radius) is moved in to stay inside the turn line, as AgOpenGPS; when the headland is narrower than the loop is deep it leaves its pass inside the headland line, still working, and the status says "Headland too narrow for this U-turn" with how far before the line (`TurnStartInsideHeadlandLine`). - The turn is built from the headland crossing **ahead** of the tractor (`FindTrackHeadlandIntersectionAhead`; one behind only past the last crossing). When no real turn fits, the simple fallback (a semicircle) is used only if every point of it stays in the field, outside obstacles a turn may not drive through (`LeavesField`, #698); otherwise no turn and the creation-failed sound, as AgOpenGPS (which has no fallback). A hard boundary refuses a turn that would start under or behind the tractor. +- A turn tried again with the turn line moved in, to keep the implement off a hard fence (`CreateTurnPath`'s setback loop, which moves the whole fence in), is seated back from the end of the pass the first try found: walking back from that end, at the moved line's crossing nearest to it (`YouTurnCreationInput.TurnLineEndIndex`, `TurnLineEndOnRetry`). Moved in, the turn line also crossed the pass where the fence comes in beside it, and that first crossing ahead was taken: an 8 m boom near a hard fence turned 260 m before the end of its pass, and the strip it left on the next pass was never worked. - The next pass must have room at this end: at least 20 m of it inside the cultivated area back from the end ahead (`IsPathLandable` / `LandingLength`), not just any piece of it inside the field; otherwise "End of field reached - no room on the next pass at this end" (#712). Driving on across a headland gap to the next piece is not done (an enhancement). - Skip worked passes (the snake order): when the order's next pass has no room at this end, or its turn does not build, the turn goes onto the nearest pass still to do that has room, first on the side the order is going, then on the other side (one turn built per side; a pass at least the skip away before a nearer one; none further than twice the skip's step, else "End of field reached" and the operator decides), never against the side the operator picked with the U-turn arrow (`TurnOntoNearestOpenPass`; AgOpenGPS's `GetNextNotWorkedTrack` only steps outward on the turn's side past worked tracks, so the other side, passes nearer than the skip and the room and build checks are ours); the order is planned again from where it lands. An end where no turn was made is remembered (`_failedEnd`) and not tried again on every cycle until the pass, the direction of travel, the nudge, the order, the field or headland, the skip, the U-turn arrow or a turn setting changes. The order is also planned again when the headland line or the outer boundary changes (`SnakeFieldStamp`). In the headland the snake looks for its next turn only heading out of the field (no cultivated ground ahead on the pass): a turn that ends in the headland leaves the tractor facing the field on its new pass, which is not the end of that pass. - An open curve whose ends face each other (a boundary curve made the long way round, A and B with an opening between them): A and B are the line's ends, no run-on past them, and the opening is a virtual boundary (`CurveEndOpening`, a strip of twice the U-turn radius plus the distance from boundary past A and B): the U-turn turns there into the next pass as at the field's edge. A turn that would hand over past the end of the next pass's line (the inner passes, whose two ends meet in a V, with a skip) is not made (`TurnLanding.PastLineEnd`). Past an open curve's end with no turn to take over, AutoSteer goes off ("past end of curve", AgOpenGPS `CABCurve`) instead of steering back (#694). diff --git a/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationInput.cs b/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationInput.cs index 15cd619e4..dbe992b16 100644 --- a/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationInput.cs +++ b/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationInput.cs @@ -39,6 +39,15 @@ public class YouTurnCreationInput /// public int CurrentLocationIndex { get; set; } + /// + /// The pass's first point outside the turn area on the plan's first try, when a turn is tried + /// again with the turn lines moved in (the clearance from a hard fence, a landing on a bend): + /// the turn is then seated back from that end, at the moved turn line's crossing nearest to + /// it. Null on the first try. Moved in, the fence's turn line can cross the pass much earlier + /// where the fence comes in beside it, and the turn was made there. + /// + public int? TurnLineEndIndex { get; set; } + /// /// For curves: the BASE (path 0) reference curve points with headings. /// is the current pass — this base track offset diff --git a/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationOutput.cs b/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationOutput.cs index 55026c81f..8451970cc 100644 --- a/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationOutput.cs +++ b/Shared/AgOpenWeb.Models/YouTurn/YouTurnCreationOutput.cs @@ -63,5 +63,11 @@ public class YouTurnCreationOutput /// entry leg was prepended. Null when the path has no entry leg. /// public Vec3? ArcStart { get; set; } + + /// + /// The pass's first point (index in ) outside + /// the turn area at the end the turn was made at; -1 when not found. + /// + public int TurnLineEndIndex { get; set; } = -1; } } diff --git a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs index 4f96b6476..7f3220032 100644 --- a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs +++ b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs @@ -121,6 +121,9 @@ public TurnPathResult CreateTurnPath( // A turn seated back to clear a hard boundary must still start this far ahead of the tractor. const double MinArcStartAhead = 2.0; double extraSetback = 0.0; + // Where the pass leaves the turn area on the first try: a try with the turn lines moved in + // seats the turn back from there (YouTurnCreationInput.TurnLineEndIndex). + int? turnLineEnd = null; while (true) { @@ -134,8 +137,11 @@ public TurnPathResult CreateTurnPath( _logger.LogWarning("[YouTurn] Failed to build creation input - no boundary available?"); return new TurnPathResult(null, false); } + if (extraSetback > 0) input.TurnLineEndIndex = turnLineEnd; var output = CreateTurn(input); + if (turnLineEnd == null && output.Success && output.TurnLineEndIndex >= 0) + turnLineEnd = output.TurnLineEndIndex; if (output.Success && output.TurnPath != null && output.TurnPath.Count > 10) { diff --git a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.cs b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.cs index 512f48c3c..75ea9dbf3 100644 --- a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.cs +++ b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.cs @@ -55,6 +55,7 @@ public YouTurnCreationService( private double pointSpacing; private double iE, iN; // Line intersection results private int semiCircleIndex = -1; + private int turnLineEndIndex = -1; // the pass's first point outside the turn area at the end turned at private bool isOutOfBounds = false; private bool isOutSameCurve = false; private Vec3? arcStart; @@ -69,6 +70,7 @@ public YouTurnCreationOutput CreateTurn(YouTurnCreationInput input) // Store input for helper methods _currentInput = input; + turnLineEndIndex = -1; // Validate input if (input.MakeUTurnCounter < 4) @@ -126,6 +128,7 @@ public YouTurnCreationOutput CreateTurn(YouTurnCreationInput input) output.InClosestTurnPoint = inClosestTurnPt.ClosePt; output.OutClosestTurnPoint = outClosestTurnPt.ClosePt; output.ArcStart = arcStart; + output.TurnLineEndIndex = turnLineEndIndex; } else { @@ -1134,7 +1137,20 @@ private bool FindCurveTurnPoint(YouTurnCreationInput input, bool useAlternateHea bool loop = input.TrackMode == 32 || input.TrackMode == 64; // bndCurve or waterPivot - for (j = input.CurrentLocationIndex; j > 0 && j < input.GuidancePoints.Count; j += count) + // Tried again with the turn lines moved in: seated back from the end the first try found, + // at the moved line's crossing nearest to it (see TurnLineEndIndex). + if (TurnLineEndOnRetry(input, count) is int end) + { + closestTurnPt.CurveIndex = end - count; + closestTurnPt.TurnLineNum = turnNum = Math.Clamp(input.IsPointInsideTurnArea(input.GuidancePoints[end]), + 0, input.BoundaryTurnLines.Count - 1); + turnLineEndIndex = end; + j = -1; // no search + } + else + j = input.CurrentLocationIndex; + + for (; j > 0 && j < input.GuidancePoints.Count; j += count) { if (j < 0) { @@ -1170,6 +1186,7 @@ private bool FindCurveTurnPoint(YouTurnCreationInput input, bool useAlternateHea closestTurnPt.CurveIndex = j - count; closestTurnPt.TurnLineNum = boundaryIndex; turnNum = boundaryIndex; + turnLineEndIndex = j; break; } } @@ -1239,6 +1256,31 @@ private bool FindCurveTurnPoint(YouTurnCreationInput input, bool useAlternateHea return closestTurnPt.TurnLineIndex != -1 && closestTurnPt.CurveIndex != -1; } + /// + /// On a try with the turn lines moved in (): + /// walking back from the end the first try found towards the tractor, the first point of the + /// run of points outside the moved turn area that reaches that end. Null on a first try, or + /// when that end is not ahead on this pass or not outside the moved turn area. + /// The moved line only seats the turn further back. Where the fence comes in beside the pass + /// (a narrowing of the field the pass runs past, inside the turn line all the way), the line + /// moved in crosses the pass there, and the first crossing ahead was that one: a field with an + /// 8 m boom near a hard fence at the far end had its turn line moved in 5 m, and the turn was + /// made 260 m before the end, beside the narrowing, landing on the next pass 16 m after its + /// edge entered the crop, which was never worked. + /// + private static int? TurnLineEndOnRetry(YouTurnCreationInput input, int count) + { + if (input.TurnLineEndIndex is not int end) return null; + var pts = input.GuidancePoints; + int from = input.CurrentLocationIndex; + if (end <= 0 || end >= pts.Count || (end - from) * count <= 0) return null; + if (input.IsPointInsideTurnArea(pts[end]) == 0) return null; + int j = end; + while (j != from && j - count > 0 && j - count < pts.Count && input.IsPointInsideTurnArea(pts[j - count]) != 0) + j -= count; + return j; + } + private List MoveTurnInsideTurnLine(YouTurnCreationInput input, List uTurnList, double head, bool deleteSecondHalf, bool invertHeading) { // Step 1 make array out of the list so that we can modify the position diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs new file mode 100644 index 000000000..386c059d8 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs @@ -0,0 +1,135 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using System.Linq; +using AgOpenWeb.Models; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Pipeline; +using AgOpenWeb.Services.Geometry; +using AgOpenWeb.Services.YouTurn; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; + +namespace AgOpenWeb.Services.Tests.YouTurn; + +/// +/// A turn tried again with the turn line moved in, to keep the implement off a hard fence, is +/// seated back from the end of the pass the first try found. Moved in, the fence's turn line also +/// crossed the pass where the fence comes in beside it, and the first crossing ahead was that +/// one: on a real field a front 8 m boom near the fence at the far end moved the turn line in +/// 5 m, the turn was made 260 m before the end, beside the narrowing, and landed on the next pass +/// 16 m after its edge entered the crop, which was never sprayed. A rear-mounted implement +/// gives the same. +/// +[TestFixture, NonParallelizable] // ConfigurationStore is a singleton. +public class TurnSetbackStaysAtEndTests +{ + private const double FenceNorth = 100; + + // The east fence comes in to x = 9 from y = -15 to 15, with 45° sides: the pass x = 4 runs + // past it, the implement's east end (x = 8) a metre inside. + private static List Fence(bool bulge) => bulge + ? new() { new(-50, -100), new(50, -100), new(50, -56), new(9, -15), new(9, 15), new(50, 56), new(50, FenceNorth), new(-50, FenceNorth) } + : new() { new(-50, -100), new(50, -100), new(50, FenceNorth), new(-50, FenceNorth) }; + + private static YouTurnCreationService NewCreation(bool front) + { + ConfigurationStore.SetInstance(new ConfigurationStore()); + var c = ConfigurationStore.Instance; + // A front sprayer: an 8 m boom fixed 4.5 m ahead of the pivot; or an 8 m implement fixed + // 1.5 m behind it. + const double width = 8; + c.Vehicle.Wheelbase = 2.9; + c.Tool.Width = width; + c.Tool.Overlap = 0; + c.Tool.Offset = 0; + c.Tool.HitchLength = front ? 4.5 : 1.5; + c.Tool.IsToolFrontFixed = front; + c.Tool.IsToolRearFixed = !front; + c.Tool.IsToolTrailing = false; + c.Tool.IsToolTBT = false; + c.NumSections = 1; + c.Tool.SetSectionWidth(0, (int)(width * 100)); + c.Guidance.UTurnDistanceFromBoundary = 2.0; + c.Guidance.UTurnRadius = 8.0; + c.Guidance.UTurnExtension = 20.0; + c.Guidance.UTurnStyle = 0; + return new YouTurnCreationService(NullLogger.Instance, new PolygonOffsetService(), c); + } + + private static Boundary Field(bool bulge, bool hard) => new() + { + OuterBoundary = new BoundaryPolygon + { + Points = Fence(bulge).Select(p => new BoundaryPoint(p.Easting, p.Northing, 0)).ToList(), + IsHard = hard, + }, + }; + + /// The fence moved in by : the x where it crosses y = 0. + private static double BulgeTurnLineX(double d) + { + var line = new PolygonOffsetService().CreateInwardOffset(Fence(bulge: true), d)!; + double best = double.MaxValue; + for (int i = 0; i < line.Count; i++) + { + var a = line[i]; + var b = line[(i + 1) % line.Count]; + if ((a.Northing - 0) * (b.Northing - 0) > 0 || a.Northing == b.Northing) continue; + double x = a.Easting + (0 - a.Northing) / (b.Northing - a.Northing) * (b.Easting - a.Easting); + if (x > 0 && x < best) best = x; + } + return best; + } + + private static YouTurnCreationService.TurnPathResult TurnFromTheSouth(YouTurnCreationService creation, Boundary field) + { + // Pass 0 (x = 4) north from y = -70, the next pass (x = -4) to the left. + const double y = -70; + var turnLine = new PolygonOffsetService().CreateInwardOffset( + field.OuterBoundary!.Points.Select(p => new Vec2(p.Easting, p.Northing)).ToList(), 2.0)! + .Select(p => new Vec3(p.Easting, p.Northing, 0)).ToList(); + var track = AgOpenWeb.Models.Track.Track.FromABLine("AB", new Vec3(0, -200, 0), new Vec3(0, 200, 0)); + var guidance = new GuidanceWorkingState { IsHeadingSameWay = true, HowManyPathsAway = 0 }; + var turn = new YouTurnWorkingState { IsTurnLeft = true, DistanceToHeadland = FenceNorth - 2 - y }; + return creation.CreateTurnPath(new Position { Easting = 4, Northing = y, Heading = 0 }, track, + headingRadians: 0, abHeading: 0, field, turnLine, guidance, turn, + uTurnSkipRows: 0, headlandCalculatedWidth: 12, headlandDistance: FenceNorth - 2 - y); + } + + [TestCase(true, TestName = "A turn seated back from a hard fence stays at the end of the pass (front boom)")] + [TestCase(false, TestName = "A turn seated back from a hard fence stays at the end of the pass (rear implement)")] + public void A_turn_seated_back_from_a_hard_fence_stays_at_the_end_of_the_pass(bool front) + { + Assert.Multiple(() => + { + Assert.That(BulgeTurnLineX(2.0), Is.GreaterThan(4.5), "the scenario: the turn line passes the pass by"); + Assert.That(BulgeTurnLineX(6.5), Is.LessThan(4.0), "the scenario: moved in 4.5 m more, it crosses the pass"); + }); + + // Without the hard fence the turn is made at the end, against the turn line; with it the + // implement swinging round is kept off the fence by seating the turn back. + var soft = TurnFromTheSouth(NewCreation(front), Field(bulge: true, hard: false)); + var plain = TurnFromTheSouth(NewCreation(front), Field(bulge: false, hard: true)); + var turn = TurnFromTheSouth(NewCreation(front), Field(bulge: true, hard: true)); + + Assert.That(soft.Path, Is.Not.Null); + Assert.That(plain.Path, Is.Not.Null); + double softTop = soft.Path!.Max(p => p.Northing); + double plainTop = plain.Path!.Max(p => p.Northing); + TestContext.Out.WriteLine($"top of the turn: soft {softTop:F1}, hard {plainTop:F1}"); + Assert.That(plainTop, Is.LessThan(softTop - 1), "the scenario: the hard fence seats the turn back"); + + Assert.That(turn.Path, Is.Not.Null, "a turn"); + double top = turn.Path!.Max(p => p.Northing); + TestContext.Out.WriteLine($"top of the turn beside the bulge: {top:F1}, arc start {turn.ArcStart?.Northing:F1}"); + Assert.Multiple(() => + { + Assert.That(top, Is.EqualTo(plainTop).Within(0.5), "the same turn as without the bulge, at the end of the pass"); + Assert.That(turn.ArcStart!.Value.Northing, Is.GreaterThan(50), "not beside the bulge"); + }); + } +} From bc38e9d10b1cca9bc3666d8db13d450a66ae7def Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 13:55:14 -0600 Subject: [PATCH 10/24] Runner compare: every fixture's report, and an incomplete comparison fails The field job harness has six fixtures and they all wrote the one /out/report.md: the last one run overwrote the others, so a comparison of P1 or J7 read the turn-intervention report and said "0 new, 0 gone" about jobs it never saw. - Tests/ScenarioShared/ReportPath.cs, linked into every harness: AGOPENWEB_SCENARIO_REPORT (and AGOPENWEB_DAY_REPORT) may name a folder; each report goes in under its own name. - A field job fixture with none of AGOPENWEB_SCENARIO_ONLY's jobs writes no report (ignored). - run-harness.sh points the reports at /out/ (a ref from before keeps /out/report.md). - runner-compare.sh compares every report by name and exits 1 when a side did not pass, a report is on one side only, there is none, or an old ref ran several fixtures into one report.md. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- ...AgOpenWeb.DumpReplayScenarios.Tests.csproj | 1 + .../DumpReplayScenarioTests.cs | 3 +- .../AgOpenWeb.FieldJobScenarios.Tests.csproj | 1 + .../AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs | 7 ++- .../AgOpenWeb.FieldScenarios.Tests.csproj | 1 + .../FieldScenarioTests.cs | 3 +- .../AgOpenWeb.GpsNtripScenarios.Tests.csproj | 1 + .../GpsNtripScenarioTests.cs | 3 +- .../AgOpenWeb.IsobusScenarios.Tests.csproj | 1 + .../IsobusScenarioTests.cs | 3 +- ...penWeb.MachineModuleScenarios.Tests.csproj | 1 + .../MachineModuleScenarioTests.cs | 3 +- ...gOpenWeb.SteerWizardScenarios.Tests.csproj | 1 + .../SteerWizardScenarioTests.cs | 3 +- .../AgOpenWeb.UpdateScenarios.Tests.csproj | 1 + .../UpdateScenarioTests.cs | 3 +- ...gOpenWeb.VehicleToolScenarios.Tests.csproj | 1 + .../VehicleToolScenarioTests.cs | 3 +- .../AgOpenWeb.WebClientScenarios.Tests.csproj | 1 + .../WebClientScenarioTests.cs | 3 +- ...AgOpenWeb.WorkingDayScenarios.Tests.csproj | 1 + .../WorkingDayScenarioTests.cs | 3 +- Tests/ScenarioShared/ReportPath.cs | 34 ++++++++++++ Tools/harness-runner/README.md | 6 ++- Tools/harness-runner/run-harness.sh | 11 ++-- Tools/harness-runner/runner-compare.sh | 52 ++++++++++++++++--- 27 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 Tests/ScenarioShared/ReportPath.cs diff --git a/CLAUDE.md b/CLAUDE.md index a14147061..64dcc779b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -506,7 +506,7 @@ dotnet run --project TestRunner/TestRunner.csproj - `AgOpenWeb.WorkingDayScenarios.Tests` - an eight-hour working day in one process (fields, refills, lunch, a tablet and a phone that drop, stall and reconnect, the next morning): memory baselines, heap census, cycle and tick times, the deadman with a second browser; about 45 minutes (`working-day-scenarios.yml`) - `AgOpenWeb.DumpReplayScenarios.Tests` - the bug report dumps attached to public issues, replayed on this build: the reporter's settings, field, job and track, their GPS log fed back through the pipeline and compared with what their build did, then a machine of their shape taken over with AutoSteer and U-turns; the bug report service's dumps only in a local run (`AGOPENWEB_REPLAY_PRIVATE=1`), never in CI (`dump-replay-scenarios.yml`) -**Running the harnesses:** on the harness host, never on a development machine (`Tools/harness-runner`: a Docker image run on the `runner` context; `runner-compare.sh` runs a fix and its base side by side and lists the findings new, gone and in both, `Tools/compare-reports.py`). Every harness stops at the start when another process holds UDP 9999 or 8888 (`Tests/ScenarioShared/PortGuard.cs`, linked into each): an app left running shares the packets and the findings are noise. Before rerunning, read the saved report, pictures and traces; one confirming run per fix. +**Running the harnesses:** on the harness host, never on a development machine (`Tools/harness-runner`: a Docker image run on the `runner` context; `runner-compare.sh` runs a fix and its base side by side and lists the findings new, gone and in both for each report, `Tools/compare-reports.py`; it exits 1 when a side did not pass or a report is missing). Every harness stops at the start when another process holds UDP 9999 or 8888 (`Tests/ScenarioShared/PortGuard.cs`, linked into each): an app left running shares the packets and the findings are noise. Before rerunning, read the saved report, pictures and traces; one confirming run per fix. CI also runs `Tools/i18n-extract.py --check` so `en.json` stays current, `Tools/license-headers.py --check` for the licence headers, and `Tools/i18n-limits.py --check` (job `translations-fit`, only when a change touches `wwwroot/`, the check, its baseline or the workflow; otherwise skipped, which counts as passed) so every translated label fits the English layout. diff --git a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/AgOpenWeb.DumpReplayScenarios.Tests.csproj b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/AgOpenWeb.DumpReplayScenarios.Tests.csproj index 966445977..a1c743b7e 100644 --- a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/AgOpenWeb.DumpReplayScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/AgOpenWeb.DumpReplayScenarios.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/DumpReplayScenarioTests.cs b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/DumpReplayScenarioTests.cs index ba4bc332d..17d3f1c04 100644 --- a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/DumpReplayScenarioTests.cs +++ b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/DumpReplayScenarioTests.cs @@ -76,8 +76,7 @@ public async Task Replay_every_dump() } finally { - var report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "dump-replay-scenarios-report.md"); + var report = ReportPath.For("dump-replay-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); try { Directory.Delete(root, true); } catch { /* temp */ } diff --git a/Tests/AgOpenWeb.FieldJobScenarios.Tests/AgOpenWeb.FieldJobScenarios.Tests.csproj b/Tests/AgOpenWeb.FieldJobScenarios.Tests/AgOpenWeb.FieldJobScenarios.Tests.csproj index 17a612385..c2d38e1ce 100644 --- a/Tests/AgOpenWeb.FieldJobScenarios.Tests/AgOpenWeb.FieldJobScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.FieldJobScenarios.Tests/AgOpenWeb.FieldJobScenarios.Tests.csproj @@ -55,6 +55,7 @@ + diff --git a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs index 2a681ba00..be37c0ea4 100644 --- a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs +++ b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs @@ -32,12 +32,15 @@ public static async Task RunFixtureAsync(IEnumerable<(string Op, JobSpec Job)> j Func toMarkdown, CancellationToken ct) { string? only = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_ONLY"); - string path = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, reportName); + string path = ReportPath.For(reportName); var journal = new Journal(); var clock = System.Diagnostics.Stopwatch.StartNew(); void Progress(string what) => TestContext.Progress.WriteLine(F($"[{clock.Elapsed:hh\\:mm\\:ss}] {what}")); var wanted = jobs.Where(j => Want(only, j.Job.Code)).ToList(); + // None of AGOPENWEB_SCENARIO_ONLY's jobs is in this fixture: no report, which would read + // as a clean run of jobs that never ran. + if (wanted.Count == 0) + Assert.Ignore($"none of AGOPENWEB_SCENARIO_ONLY={only} is in this fixture"); for (int i = 0; i < wanted.Count; i++) { var (op, job) = wanted[i]; diff --git a/Tests/AgOpenWeb.FieldScenarios.Tests/AgOpenWeb.FieldScenarios.Tests.csproj b/Tests/AgOpenWeb.FieldScenarios.Tests/AgOpenWeb.FieldScenarios.Tests.csproj index ca82376bd..b6e68955b 100644 --- a/Tests/AgOpenWeb.FieldScenarios.Tests/AgOpenWeb.FieldScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.FieldScenarios.Tests/AgOpenWeb.FieldScenarios.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/Tests/AgOpenWeb.FieldScenarios.Tests/FieldScenarioTests.cs b/Tests/AgOpenWeb.FieldScenarios.Tests/FieldScenarioTests.cs index f9ea38467..ece810e3e 100644 --- a/Tests/AgOpenWeb.FieldScenarios.Tests/FieldScenarioTests.cs +++ b/Tests/AgOpenWeb.FieldScenarios.Tests/FieldScenarioTests.cs @@ -38,8 +38,7 @@ public async Task Boot() [OneTimeTearDown] public async Task WriteReport() { - var path = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "field-scenarios-report.md"); + var path = ReportPath.For("field-scenarios-report.md"); var md = _journal.ToMarkdown("Field scenarios: the careful and the careless operator", "Generated by Tests/AgOpenWeb.FieldScenarios.Tests. Every step is a command the web client sends; " + "after each one the field state is checked against the rules in FieldInvariants.cs. " + diff --git a/Tests/AgOpenWeb.GpsNtripScenarios.Tests/AgOpenWeb.GpsNtripScenarios.Tests.csproj b/Tests/AgOpenWeb.GpsNtripScenarios.Tests/AgOpenWeb.GpsNtripScenarios.Tests.csproj index 94bb203aa..1ff1f1eca 100644 --- a/Tests/AgOpenWeb.GpsNtripScenarios.Tests/AgOpenWeb.GpsNtripScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.GpsNtripScenarios.Tests/AgOpenWeb.GpsNtripScenarios.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/Tests/AgOpenWeb.GpsNtripScenarios.Tests/GpsNtripScenarioTests.cs b/Tests/AgOpenWeb.GpsNtripScenarios.Tests/GpsNtripScenarioTests.cs index 3b011bb7c..e94ef6c30 100644 --- a/Tests/AgOpenWeb.GpsNtripScenarios.Tests/GpsNtripScenarioTests.cs +++ b/Tests/AgOpenWeb.GpsNtripScenarios.Tests/GpsNtripScenarioTests.cs @@ -27,8 +27,7 @@ public async Task Careful_and_careless_operator() if (who is null or "careful") await CarefulOperator.RunAsync(journal, only); if (who is null or "careless") await CarelessOperator.RunAsync(journal, only); - string report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "gps-ntrip-scenarios-report.md"); + string report = ReportPath.For("gps-ntrip-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); foreach (var g in journal.Findings.Values.OrderBy(v => v.First.Severity)) diff --git a/Tests/AgOpenWeb.IsobusScenarios.Tests/AgOpenWeb.IsobusScenarios.Tests.csproj b/Tests/AgOpenWeb.IsobusScenarios.Tests/AgOpenWeb.IsobusScenarios.Tests.csproj index 42381aa3c..b294dde86 100644 --- a/Tests/AgOpenWeb.IsobusScenarios.Tests/AgOpenWeb.IsobusScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.IsobusScenarios.Tests/AgOpenWeb.IsobusScenarios.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/Tests/AgOpenWeb.IsobusScenarios.Tests/IsobusScenarioTests.cs b/Tests/AgOpenWeb.IsobusScenarios.Tests/IsobusScenarioTests.cs index 3d5e4c5b9..5ccad2fbf 100644 --- a/Tests/AgOpenWeb.IsobusScenarios.Tests/IsobusScenarioTests.cs +++ b/Tests/AgOpenWeb.IsobusScenarios.Tests/IsobusScenarioTests.cs @@ -27,8 +27,7 @@ public async Task Careful_and_careless_operator() if (who is null or "careful") await CarefulOperator.RunAsync(journal, only); if (who is null or "careless") await CarelessOperator.RunAsync(journal, only); - string report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "isobus-scenarios-report.md"); + string report = ReportPath.For("isobus-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); foreach (var g in journal.Findings.Values.OrderBy(v => v.First.Severity)) diff --git a/Tests/AgOpenWeb.MachineModuleScenarios.Tests/AgOpenWeb.MachineModuleScenarios.Tests.csproj b/Tests/AgOpenWeb.MachineModuleScenarios.Tests/AgOpenWeb.MachineModuleScenarios.Tests.csproj index 5c73393d7..fe5b90a4e 100644 --- a/Tests/AgOpenWeb.MachineModuleScenarios.Tests/AgOpenWeb.MachineModuleScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.MachineModuleScenarios.Tests/AgOpenWeb.MachineModuleScenarios.Tests.csproj @@ -47,6 +47,7 @@ + diff --git a/Tests/AgOpenWeb.MachineModuleScenarios.Tests/MachineModuleScenarioTests.cs b/Tests/AgOpenWeb.MachineModuleScenarios.Tests/MachineModuleScenarioTests.cs index fd97b58cc..78fb51423 100644 --- a/Tests/AgOpenWeb.MachineModuleScenarios.Tests/MachineModuleScenarioTests.cs +++ b/Tests/AgOpenWeb.MachineModuleScenarios.Tests/MachineModuleScenarioTests.cs @@ -27,8 +27,7 @@ public async Task Careful_and_careless_operator() if (who is null or "careful") await CarefulOperator.RunAsync(journal, only); if (who is null or "careless") await CarelessOperator.RunAsync(journal, only); - string report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "machine-module-scenarios-report.md"); + string report = ReportPath.For("machine-module-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); foreach (var g in journal.Findings.Values.OrderBy(v => v.First.Severity)) diff --git a/Tests/AgOpenWeb.SteerWizardScenarios.Tests/AgOpenWeb.SteerWizardScenarios.Tests.csproj b/Tests/AgOpenWeb.SteerWizardScenarios.Tests/AgOpenWeb.SteerWizardScenarios.Tests.csproj index 137a3cd57..ecabd8109 100644 --- a/Tests/AgOpenWeb.SteerWizardScenarios.Tests/AgOpenWeb.SteerWizardScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.SteerWizardScenarios.Tests/AgOpenWeb.SteerWizardScenarios.Tests.csproj @@ -44,6 +44,7 @@ + diff --git a/Tests/AgOpenWeb.SteerWizardScenarios.Tests/SteerWizardScenarioTests.cs b/Tests/AgOpenWeb.SteerWizardScenarios.Tests/SteerWizardScenarioTests.cs index 778712b41..ca00780cf 100644 --- a/Tests/AgOpenWeb.SteerWizardScenarios.Tests/SteerWizardScenarioTests.cs +++ b/Tests/AgOpenWeb.SteerWizardScenarios.Tests/SteerWizardScenarioTests.cs @@ -27,8 +27,7 @@ public async Task Careful_and_careless_operators() TestContext.Progress.WriteLine("scenario: " + name); await run(journal, name); } - var path = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "steer-wizard-report.md"); + var path = ReportPath.For("steer-wizard-report.md"); File.WriteAllText(path, journal.ToMarkdown("Steer wizard scenarios", "Every step of the steer wizard driven by a careful and a careless operator against an emulated AiO v4 steer module and a moving tractor. Generated by Tests/AgOpenWeb.SteerWizardScenarios.Tests.")); TestContext.Progress.WriteLine("report: " + path); diff --git a/Tests/AgOpenWeb.UpdateScenarios.Tests/AgOpenWeb.UpdateScenarios.Tests.csproj b/Tests/AgOpenWeb.UpdateScenarios.Tests/AgOpenWeb.UpdateScenarios.Tests.csproj index 7d958af47..23461005c 100644 --- a/Tests/AgOpenWeb.UpdateScenarios.Tests/AgOpenWeb.UpdateScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.UpdateScenarios.Tests/AgOpenWeb.UpdateScenarios.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Tests/AgOpenWeb.UpdateScenarios.Tests/UpdateScenarioTests.cs b/Tests/AgOpenWeb.UpdateScenarios.Tests/UpdateScenarioTests.cs index a73e7bf83..f2865c6ac 100644 --- a/Tests/AgOpenWeb.UpdateScenarios.Tests/UpdateScenarioTests.cs +++ b/Tests/AgOpenWeb.UpdateScenarios.Tests/UpdateScenarioTests.cs @@ -29,8 +29,7 @@ public async Task Upgrader_and_restarter() if (who is null or "restarter") await RestarterOperator.RunAsync(journal, only); if (who is null or "upgrader") await UpgraderOperator.RunAsync(journal, only); - string report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "update-scenarios-report.md"); + string report = ReportPath.For("update-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); foreach (var g in journal.Findings.Values.OrderBy(v => v.First.Severity)) diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/AgOpenWeb.VehicleToolScenarios.Tests.csproj b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/AgOpenWeb.VehicleToolScenarios.Tests.csproj index 94b144374..3500bc95c 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/AgOpenWeb.VehicleToolScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/AgOpenWeb.VehicleToolScenarios.Tests.csproj @@ -46,6 +46,7 @@ + diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/VehicleToolScenarioTests.cs b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/VehicleToolScenarioTests.cs index e739b5454..4c1089ce3 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/VehicleToolScenarioTests.cs +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/VehicleToolScenarioTests.cs @@ -33,8 +33,7 @@ public async Task Careful_and_careless_operator() if (Wants("sweep")) { Progress("settings sweep"); await SettingsSweep.RunAsync(journal, only, Progress); } Progress("done"); - string report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "vehicle-tool-scenarios-report.md"); + string report = ReportPath.For("vehicle-tool-scenarios-report.md"); File.WriteAllText(report, journal.ToMarkdown()); TestContext.Out.WriteLine("Report: " + report); foreach (var g in journal.Findings.Values.OrderBy(v => v.First.Severity)) diff --git a/Tests/AgOpenWeb.WebClientScenarios.Tests/AgOpenWeb.WebClientScenarios.Tests.csproj b/Tests/AgOpenWeb.WebClientScenarios.Tests/AgOpenWeb.WebClientScenarios.Tests.csproj index 26ddf87ee..08548b263 100644 --- a/Tests/AgOpenWeb.WebClientScenarios.Tests/AgOpenWeb.WebClientScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.WebClientScenarios.Tests/AgOpenWeb.WebClientScenarios.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Tests/AgOpenWeb.WebClientScenarios.Tests/WebClientScenarioTests.cs b/Tests/AgOpenWeb.WebClientScenarios.Tests/WebClientScenarioTests.cs index d8bf1ad02..52b73015e 100644 --- a/Tests/AgOpenWeb.WebClientScenarios.Tests/WebClientScenarioTests.cs +++ b/Tests/AgOpenWeb.WebClientScenarios.Tests/WebClientScenarioTests.cs @@ -18,8 +18,7 @@ public class WebClientScenarioTests public async Task Web_client_scenarios() { var clock = System.Diagnostics.Stopwatch.StartNew(); - var report = Environment.GetEnvironmentVariable("AGOPENWEB_SCENARIO_REPORT"); - if (string.IsNullOrEmpty(report)) report = Path.Combine(TestContext.CurrentContext.WorkDirectory, "web-client-scenarios-report.md"); + var report = ReportPath.For("web-client-scenarios-report.md"); var shots = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(report))!, "web-client-shots"); if (Directory.Exists(shots)) Directory.Delete(shots, true); var dataRoot = Path.Combine(Path.GetTempPath(), "aow-web-scenarios-" + Guid.NewGuid().ToString("N")[..8]); diff --git a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/AgOpenWeb.WorkingDayScenarios.Tests.csproj b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/AgOpenWeb.WorkingDayScenarios.Tests.csproj index 57ce34d4a..0216858bb 100644 --- a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/AgOpenWeb.WorkingDayScenarios.Tests.csproj +++ b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/AgOpenWeb.WorkingDayScenarios.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/WorkingDayScenarioTests.cs b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/WorkingDayScenarioTests.cs index fdf817fe9..36183f848 100644 --- a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/WorkingDayScenarioTests.cs +++ b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/WorkingDayScenarioTests.cs @@ -28,8 +28,7 @@ public async Task Working_day(CancellationToken ct) day.CountCensuses(); var report = Report.Markdown(day); - string path = Environment.GetEnvironmentVariable("AGOPENWEB_DAY_REPORT") - ?? Path.Combine(TestContext.CurrentContext.WorkDirectory, "working-day-scenarios-report.md"); + string path = ReportPath.For("working-day-scenarios-report.md", "AGOPENWEB_DAY_REPORT"); await File.WriteAllTextAsync(path, report, CancellationToken.None); if (Environment.GetEnvironmentVariable("AGOPENWEB_DAY_CSV") is { Length: > 0 } csv) await File.WriteAllTextAsync(csv, Report.Csv(day), CancellationToken.None); diff --git a/Tests/ScenarioShared/ReportPath.cs b/Tests/ScenarioShared/ReportPath.cs new file mode 100644 index 000000000..eaad8909f --- /dev/null +++ b/Tests/ScenarioShared/ReportPath.cs @@ -0,0 +1,34 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Linked into every scenario harness (). + +using System; +using System.IO; +using NUnit.Framework; + +/// +/// Where a harness writes its report. The variable (AGOPENWEB_SCENARIO_REPORT, the working day's +/// AGOPENWEB_DAY_REPORT) names the report's file, or a folder (ending in a slash, or one that +/// exists) that the report goes into under its own name; unset, the report goes to the test's +/// work folder under its own name. A folder keeps the reports of a project with several fixtures +/// apart: the field job harness has six, and with one file named for all of them the last fixture +/// run overwrote the others, so a Runner comparison read a report without the jobs it was run for +/// (2026-11-13). +/// +public static class ReportPath +{ + public static string For(string reportName, string variable = "AGOPENWEB_SCENARIO_REPORT") + { + string? v = Environment.GetEnvironmentVariable(variable); + if (string.IsNullOrEmpty(v)) + return Path.Combine(TestContext.CurrentContext.WorkDirectory, reportName); + if (v.EndsWith('/') || v.EndsWith('\\') || Directory.Exists(v)) + { + Directory.CreateDirectory(v); + return Path.Combine(v, reportName); + } + return v; + } +} diff --git a/Tools/harness-runner/README.md b/Tools/harness-runner/README.md index 9554e0034..8d44194cc 100644 --- a/Tools/harness-runner/README.md +++ b/Tools/harness-runner/README.md @@ -12,7 +12,11 @@ harnesses on a development machine, where the app and other runs share its ports volume, so the host needs no checkout: push first. The report, `build.log` and `test.log` go to `/out`. - `runner-compare.sh`: one harness on a fix and on its base side by side, the two `/out` folders - copied back, and `Tools/compare-reports.py` listing the findings new, gone and in both. + copied back, and `Tools/compare-reports.py` listing the findings new, gone and in both, for each + report by name (a project with several fixtures, the field job harness's six, writes one each: + `Tests/ScenarioShared/ReportPath.cs`; a fixture with none of `AGOPENWEB_SCENARIO_ONLY`'s jobs writes + none). It exits 1, with the reason marked `!!`, when a side did not pass, a report is on one side + only or there is none: a comparison that read the wrong report looked like a quiet run (2026-11-13). Each container has its own network, so runs side by side don't share UDP 9999/8888. They do share the CPU, and a starved run misses its watchdogs and reports it (a false `AUTOSTEER-DROPPED`, diff --git a/Tools/harness-runner/run-harness.sh b/Tools/harness-runner/run-harness.sh index 39de7adab..05a932623 100755 --- a/Tools/harness-runner/run-harness.sh +++ b/Tools/harness-runner/run-harness.sh @@ -6,7 +6,8 @@ # The code: the repo mounted read-only at /repo, or else GitHub through a mirror kept in the # /cache volume (refreshed under a lock, so parallel runs share it). is a branch, a commit, # or pr/ (a pull request's head, forks included). Reports go to /out -# (AGOPENWEB_SCENARIO_REPORT and the working day's AGOPENWEB_DAY_REPORT default to /out/report.md); +# (AGOPENWEB_SCENARIO_REPORT and the working day's AGOPENWEB_DAY_REPORT default to /out/, each report +# under its own name); # AGOPENWEB_* pass through with -e. set -euo pipefail ref="$1"; proj="$2"; shift 2 @@ -27,8 +28,12 @@ else fi git checkout -q "$ref" echo "== $(git log --oneline -1)" -export AGOPENWEB_SCENARIO_REPORT="${AGOPENWEB_SCENARIO_REPORT:-/out/report.md}" -export AGOPENWEB_DAY_REPORT="${AGOPENWEB_DAY_REPORT:-/out/report.md}" +# Each report goes to /out under its own name (Tests/ScenarioShared/ReportPath.cs), so a project +# with several fixtures (the field job harness's six) keeps them all. A ref from before that file +# writes the one /out/report.md, as it always did. +if [ -f Tests/ScenarioShared/ReportPath.cs ]; then out_report=/out/; else out_report=/out/report.md; fi +export AGOPENWEB_SCENARIO_REPORT="${AGOPENWEB_SCENARIO_REPORT:-$out_report}" +export AGOPENWEB_DAY_REPORT="${AGOPENWEB_DAY_REPORT:-$out_report}" # A failed build stops here with its errors. if ! dotnet build "$proj" -c "${CONFIG:-Debug}" --verbosity minimal > /out/build.log 2>&1; then grep -E "error" /out/build.log | sort -u | head -20 diff --git a/Tools/harness-runner/runner-compare.sh b/Tools/harness-runner/runner-compare.sh index 457249634..b564fea35 100755 --- a/Tools/harness-runner/runner-compare.sh +++ b/Tools/harness-runner/runner-compare.sh @@ -13,7 +13,8 @@ # Refs are branches, commits or pr/, fetched from GitHub on the host: push first. Every # AGOPENWEB_* variable set here, and CONFIG (default Release), go to both runs. The containers run # on the Docker context RUNNER_CONTEXT (default "runner"); their /out folders are copied to -# OUT_DIR (default ./harness-out/), and Tools/compare-reports.py writes compare.md there. +# OUT_DIR (default ./harness-out/), and Tools/compare-reports.py writes compare-.md there +# for each report. It exits 1 when a side did not pass, a report is on one side only, or there is none. set -euo pipefail [ $# -ge 3 ] || { sed -n 5,16p "$0"; exit 2; } fix="$1"; base="$2"; proj="$3"; shift 3 @@ -39,17 +40,52 @@ for side in fix base; do dk run -d --cpus "$cpus" --name "$name-$side" -v aow-cache:/cache "${envs[@]}" aow-harness:latest "$ref" "$proj" "$@" >/dev/null echo "== $side ($ref): $name-$side" done +problems=0 for side in fix base; do code=$(dk wait "$name-$side") mkdir -p "$out/$side" dk cp "$name-$side:/out/." "$out/$side" >/dev/null dk rm "$name-$side" >/dev/null - echo "== $side finished (exit $code): $(grep -hE 'Passed!|Failed!' "$out/$side/test.log" 2>/dev/null | head -1)" + result=$(grep -hE 'Passed!|Failed!' "$out/$side/test.log" 2>/dev/null | head -1 || true) + echo "== $side finished (exit $code): $result" + # A side whose tests failed may have stopped before its jobs ran: its report is no comparison. + case "$result" in + *Passed!*) ;; + *) echo "!! $side did not pass: read $out/$side/test.log before trusting the comparison"; problems=1 ;; + esac +done + +# Every report on either side, compared by name: a project with several fixtures writes one each +# (Tests/ScenarioShared/ReportPath.cs). A report on one side only is a problem, not a quiet run. +# A ref from before that wrote the one report.md; it is paired with the other side's report +# when that side has exactly one. +reports() { (cd "$out/$1" && ls *.md 2>/dev/null) || true; } +for side in fix base; do + other=base; [ $side = base ] && other=fix + mine=$(reports $side); theirs=$(reports $other) + [ "$mine" = report.md ] || continue + # Its fixtures all wrote that one file, the last one run over the others: only a run of one + # fixture (a --filter) left the report of the jobs asked for. + tests=$(grep -hoE 'Total: +[0-9]+' "$out/$side/test.log" 2>/dev/null | grep -oE '[0-9]+' | head -1 || true) + if [ "${tests:-0}" != 1 ]; then + echo "!! $side is from before per-fixture reports and ran ${tests:-no} tests into its one report.md: rerun with a --filter for one fixture" + problems=1 + elif [ -n "$theirs" ] && [ "$theirs" != report.md ] && [ "$(echo "$theirs" | wc -l | tr -d ' ')" = 1 ]; then + mv "$out/$side/report.md" "$out/$side/$theirs" + echo "== $side is from before per-fixture reports: its report.md is compared with $theirs" + fi +done +names=$( (reports fix; reports base) | sort -u) +[ -n "$names" ] || { echo "!! no report on either side; see $out/*/build.log and test.log"; problems=1; } +for r in $names; do + if [ ! -f "$out/base/$r" ] || [ ! -f "$out/fix/$r" ]; then + side=base; [ -f "$out/fix/$r" ] && side=fix + echo "!! $r: only on the $side side"; problems=1; continue + fi + cmp="$out/compare-${r%.md}.md" + python3 "$here/../compare-reports.py" "$out/base/$r" "$out/fix/$r" "$cmp" + echo "== $r" + sed -n 1,40p "$cmp" done -if [ -f "$out/base/report.md" ] && [ -f "$out/fix/report.md" ]; then - python3 "$here/../compare-reports.py" "$out/base/report.md" "$out/fix/report.md" "$out/compare.md" - sed -n 1,40p "$out/compare.md" -else - echo "A report is missing; see $out/*/build.log and test.log." -fi echo "== $out" +[ $problems -eq 0 ] || { echo "!! the comparison is incomplete: see the lines marked !! above"; exit 1; } From dad77f3e387cb4b2b40c97ba388d2be467ebe751 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 14:26:07 -0600 Subject: [PATCH 11/24] Job harness: a job that never got past its first pass is a finding (JOB-NEVER-TURNED) The operator taking over at a blocked U-turn was noted as what the app must do, and every coverage check passed on nothing sprayed: J7 and W2 sprayed 0 % in every nightly since at least 2026-11-11 with the run green. A takeover before any U-turn is now a Bug, unless the turn was refused (TURN-REFUSED already) or there was no room on the next pass (the end of the field). Co-Authored-By: Claude Opus 5.5 --- Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs | 11 +++++++++++ Tests/AgOpenWeb.FieldJobScenarios.Tests/README.md | 1 + 2 files changed, 12 insertions(+) diff --git a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs index be37c0ea4..4f4822b22 100644 --- a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs +++ b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Jobs.cs @@ -144,7 +144,18 @@ void flag(Severity sev, string code, string text) // ---- the field done if (r.EndedEarly) + { sc.Note(F($"the job ended early ({r.EndedBy}): {Pct(a.Sprayed, a.Work)} of the cultivated area sprayed by then")); + // The operator took over before any U-turn: the job never got past its first pass. A + // turn blocked (or missed) at the first end is noted as what the app said, and every + // coverage check passes on nothing sprayed, so J7 sprayed 0 % night after night with + // the run green (2026-11-13). A refused turn is TURN-REFUSED already; no room on the + // next pass is the end of the field. + if (r.TookOverAt >= 0 && r.Turns.Count == 0 && !r.ShortStopped + && !r.Problems.Any(p => p.Contains("refused the U-turn", StringComparison.Ordinal) + || p.Contains("had no room for a U-turn", StringComparison.Ordinal))) + flag(Severity.Bug, "JOB-NEVER-TURNED", F($"the job never got past its first pass: the operator took over before any U-turn ({r.EndedBy}), with {Pct(a.Sprayed, a.Work)} of the cultivated area sprayed.")); + } else if (!r.FieldDone) flag(Severity.Bug, "FIELD-NOT-FINISHED", F($"AgOpenWeb never reported the end of the field; the job stopped after {r.Seconds / 60:0} min with {Pct(a.Missed, a.Work)} of the cultivated area unsprayed. {string.Join(" | ", r.Problems)}")); else if (a.Missed - a.MissedBetweenPasses - a.MissedLeftInMiddle - a.MissedAtCorners > 0.05 * a.Work) diff --git a/Tests/AgOpenWeb.FieldJobScenarios.Tests/README.md b/Tests/AgOpenWeb.FieldJobScenarios.Tests/README.md index 75a13e8c7..97fcc63d4 100644 --- a/Tests/AgOpenWeb.FieldJobScenarios.Tests/README.md +++ b/Tests/AgOpenWeb.FieldJobScenarios.Tests/README.md @@ -45,6 +45,7 @@ its neighbours': |---|---| | Ground never sprayed between neighbouring passes, widest strip (wedges at the pass ends after AgOpenWeb said "Headland too narrow for this U-turn" are a note: the turn leaves its pass inside the headland line) | `MISSED-STRIPS` | | The field reported done with ground left (inside the worked passes, or on a side of the field / in passes skipped), or never reported done | `FIELD-LEFT-UNDONE`, `FIELD-NOT-FINISHED` | +| The operator took over before any U-turn (a turn blocked or missed at the first end): the job never got past its first pass, whatever the coverage checks say about nothing sprayed | `JOB-NEVER-TURNED` | | "End of field reached" said, then a U-turn onto another pass after all (the operator drives on) | `END-OF-FIELD-TOO-EARLY` | | Sprayed outside the boundary, inside an obstacle, inside the headland further past its line than a section's width (a section is in the headland only with both ends in it, as AgOpenGPS, so where a pass meets the line at an angle up to a section's width lands past it) | `SPRAYED-OUTSIDE-BOUNDARY`, `SPRAYED-IN-OBSTACLE`, `SPRAYED-IN-HEADLAND` | | AgOpenWeb's map painted where no valve was open, or blank where one was | `MAP-SHOWS-UNSPRAYED`, `SPRAYED-NOT-ON-MAP` | From b5d2d3e2372f8abdb1c826bf02863a5c4d6acaf7 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 15:18:37 -0600 Subject: [PATCH 12/24] RigGeometry: where the tool hangs on the tractor, worked out in one place Phase 1 of the hitch model (#547): today's model, the tool where it was. The tool position, the map's implement (the render pull), the U-turn's swept path, its tool geometry and the key of a remembered U-turn end each had their own copy of the hitch branch, and the copies differed on which flag won when a profile set two (front and rear, front and trailing). They all read RigGeometry now (Mount, AttachX, AlongTractor, ToolBehindPivot, TrailedToolCentre, Geometry, Stamp), with x along the tractor from the rear axle, + ahead (ISO 11783-10's device axes). Behaviour: none for a tool with one type flag (RigGeometryTests: all 16 flag combinations against the old formula). With no flag set the tool is the default rear-fixed one (the old copy put the tractor's hitch pin ahead of the axle). The settings panel's type and the status text show the mount in use (the status text now names TBT). The remembered U-turn end is also forgotten when the trailing hitch, pivot or tank distances or the mount change (it watched only some of them). A source scan keeps hitch branches out of the files that place the tool. Co-Authored-By: Claude Opus 5.5 --- .../Tool/ImplementSweptPath.cs | 8 +- Shared/AgOpenWeb.Models/Tool/RigGeometry.cs | 111 ++++++++++++++++ .../AgOpenWeb.RemoteServer/SceneProjector.cs | 11 +- .../ConfigurationService.cs | 11 +- .../Tool/ToolPositionService.cs | 98 +++------------ .../YouTurnCreationService.Orchestration.cs | 32 ++--- .../YouTurn/YouTurnStateMachine.cs | 6 +- Shared/AgOpenWeb.ViewModels/MainViewModel.cs | 18 +-- .../AgOpenGpsProfileImportTests.cs | 2 +- .../Tool/RigGeometryTests.cs | 118 ++++++++++++++++++ .../Tool/ToolPositionMatchesAgOpenGpsTests.cs | 2 +- 11 files changed, 280 insertions(+), 137 deletions(-) create mode 100644 Shared/AgOpenWeb.Models/Tool/RigGeometry.cs create mode 100644 Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs diff --git a/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs b/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs index a7b5a1464..e42420790 100644 --- a/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs +++ b/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs @@ -101,12 +101,8 @@ public static ImplementSweptPathResult Compute(IReadOnlyList pivotPath, To double halfWidth = geom.Width / 2.0; - // Hitch distance + sign: rigid tools measure axle→implement centre, - // trailing/TBT measure axle→tractor pin. Behind for everything except - // a front-fixed tool. - bool rigid = geom.Mount == ToolMount.FrontFixed || geom.Mount == ToolMount.RearFixed; - double hitchDistance = Math.Abs(rigid ? geom.ToolHitchLength : geom.VehicleHitchLength); - if (geom.Mount != ToolMount.FrontFixed) hitchDistance = -hitchDistance; + // The point the tool hangs on, as the tool position has it (RigGeometry). + double hitchDistance = RigGeometry.AttachX(geom); Vec3 lastToolPivot = default; Vec3 lastTank = default; diff --git a/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs b/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs new file mode 100644 index 000000000..b25e0ec05 --- /dev/null +++ b/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs @@ -0,0 +1,111 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; + +namespace AgOpenWeb.Models.Tool +{ + /// + /// Where the tool hangs on the tractor: the one place that turns the vehicle's and the tool's + /// settings into the tool's mount and the point it is attached at. The tool position (sections, + /// coverage), the map's implement, the U-turn's swept path and the settings that invalidate a + /// remembered U-turn all read it, so they cannot disagree (they each had their own copy, and + /// the copies differed on which flag won when a profile set two). + /// Distances follow ISO 11783-10's device axes: x along the tractor from the centre of the + /// rear axle, positive ahead (NonTracked/HITCH_MODEL_PLAN.md). + /// + public static class RigGeometry + { + /// + /// The tool's mount from its type flags. The flags are set together by + /// , but a profile sets them one by one, so more than + /// one can be on. As the tool position always read them: a fixed flag makes the tool fixed, + /// and it is ahead only with the front flag alone; otherwise TBT before trailing. None set + /// is a rear-fixed tool. + /// + public static ToolMount Mount(ToolConfig tool) + { + bool trailed = tool.IsToolTrailing || tool.IsToolTBT; + if (tool.IsToolFrontFixed && !tool.IsToolRearFixed && !trailed) return ToolMount.FrontFixed; + if (tool.IsToolFrontFixed || tool.IsToolRearFixed) return ToolMount.RearFixed; + if (tool.IsToolTBT) return ToolMount.TBT; + if (tool.IsToolTrailing) return ToolMount.Trailing; + return ToolMount.RearFixed; + } + + /// Front or rear fixed: the tool follows the tractor's heading. + public static bool IsRigid(ToolMount mount) => mount is ToolMount.FrontFixed or ToolMount.RearFixed; + + /// + /// The point the tool is attached at, along the tractor from the rear axle (+ ahead). + /// A fixed tool: its working point, Tool.HitchLength (axle → working point), + /// ahead for a front tool, behind for a rear one. A trailed or TBT tool: the tractor's + /// hitch pin, Vehicle.HitchLength, behind. Both are kept positive; the sign is + /// the mount's. + /// + public static double AttachX(ToolMount mount, double vehicleHitchLength, double toolHitchLength) + { + double d = Math.Abs(IsRigid(mount) ? toolHitchLength : vehicleHitchLength); + return mount == ToolMount.FrontFixed ? d : -d; + } + + /// + public static double AttachX(VehicleConfig vehicle, ToolConfig tool) => + AttachX(Mount(tool), vehicle.HitchLength, tool.HitchLength); + + /// + public static double AttachX(in ToolGeometry geom) => + AttachX(geom.Mount, geom.VehicleHitchLength, geom.ToolHitchLength); + + /// The point along the tractor (+ ahead) from . + public static Vec3 AlongTractor(Vec3 pivot, double heading, double x) => + new(pivot.Easting + Math.Sin(heading) * x, pivot.Northing + Math.Cos(heading) * x, heading); + + /// + /// How far a trailed tool's working centre is behind what pulls it (the hitch, or the TBT + /// tank): the trailing hitch length plus AgOpenGPS's pivot distance + /// (setTool_trailingToolToPivotLength, the Tool panel's Behind / Ahead): positive with the + /// tool behind its wheels, negative with it ahead. + /// + public static double ToolBehindPivot(ToolConfig tool) => tool.TrailingHitchLength + tool.TrailingToolToPivotLength; + + /// + /// A trailed or TBT tool's working centre, from the tractor's hitch and the headings of the + /// TBT tank and of the tool. + /// + public static (double Easting, double Northing) TrailedToolCentre( + double hitchEasting, double hitchNorthing, double tankHeading, double toolHeading, ToolConfig tool) + { + if (tool.IsToolTBT) + { + hitchEasting -= Math.Sin(tankHeading) * tool.TankTrailingHitchLength; + hitchNorthing -= Math.Cos(tankHeading) * tool.TankTrailingHitchLength; + } + double behind = ToolBehindPivot(tool); + return (hitchEasting - Math.Sin(toolHeading) * behind, hitchNorthing - Math.Cos(toolHeading) * behind); + } + + /// The implement's geometry for . + public static ToolGeometry Geometry(VehicleConfig vehicle, ToolConfig tool, double width) => new( + Mount: Mount(tool), + Width: width, + Offset: tool.Offset, + VehicleHitchLength: vehicle.HitchLength, + ToolHitchLength: tool.HitchLength, + TrailingHitchLength: tool.TrailingHitchLength, + TrailingToolToPivotLength: tool.TrailingToolToPivotLength, + TankTrailingHitchLength: tool.TankTrailingHitchLength, + Length: tool.Length); + + /// + /// A stamp of every setting that moves the tool relative to the tractor: a value worked out + /// from the tool's position (a U-turn tried and refused at this end) is stale once it changes. + /// + public static int Stamp(VehicleConfig vehicle, ToolConfig tool) => HashCode.Combine( + Mount(tool), vehicle.HitchLength, tool.HitchLength, tool.TrailingHitchLength, + tool.TrailingToolToPivotLength, tool.TankTrailingHitchLength, tool.Offset, tool.Length); + } +} diff --git a/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs b/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs index 4cb9bea11..9772f1754 100644 --- a/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs +++ b/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs @@ -810,7 +810,14 @@ public ConfigDto BuildConfig() var t = _config.Tool; var g = _config.Guidance; var m = _config.Machine; - int toolType = t.IsToolFrontFixed ? 0 : t.IsToolRearFixed ? 1 : t.IsToolTBT ? 2 : 3; + // The mount the tool position uses (RigGeometry), as the panel's type: 0 front, 1 rear, 2 TBT, 3 trailing. + int toolType = AgOpenWeb.Models.Tool.RigGeometry.Mount(t) switch + { + AgOpenWeb.Models.Tool.ToolMount.FrontFixed => 0, + AgOpenWeb.Models.Tool.ToolMount.RearFixed => 1, + AgOpenWeb.Models.Tool.ToolMount.TBT => 2, + _ => 3, + }; // Send all MaxSections widths/colours (not 16) so sections 17-64 get their data too (#41). var widths = new double[ToolConfig.MaxSections]; for (int i = 0; i < widths.Length; i++) widths[i] = t.GetSectionWidth(i); var colors = new int[ToolConfig.MaxSections]; for (int i = 0; i < colors.Length; i++) colors[i] = (int)t.GetSectionColor(i); @@ -925,7 +932,7 @@ public long ConfigFingerprint() h = h * 31 + d.GetHashCode(); // Tool / U-Turn / Tram / Machine (so Tool-dialog edits re-send the frame). var t = _config.Tool; var g = _config.Guidance; var mc = _config.Machine; - h = h * 31 + (t.IsToolFrontFixed ? 1 : t.IsToolRearFixed ? 2 : t.IsToolTBT ? 3 : 4); + h = h * 31 + (int)AgOpenWeb.Models.Tool.RigGeometry.Mount(t) + 1; h = h * 31 + t.HitchType + t.Zones * 31 + _config.NumSections * 7 + (t.IsSectionsNotZones ? 1 : 0) + (t.IsMultiColoredSections ? 2 : 0) + (t.IsSectionOffWhenOut ? 4 : 0) + (t.IsHeadlandSectionControl ? 8 : 0) + t.MinCoverage diff --git a/Shared/AgOpenWeb.Services/ConfigurationService.cs b/Shared/AgOpenWeb.Services/ConfigurationService.cs index 11cbd241e..5bf31c975 100644 --- a/Shared/AgOpenWeb.Services/ConfigurationService.cs +++ b/Shared/AgOpenWeb.Services/ConfigurationService.cs @@ -108,10 +108,13 @@ private static string FormatVehicle(ConfigurationStore store, bool isMetric) private static string FormatTool(ConfigurationStore store, bool isMetric) { var t = store.Tool; - string attach = t.IsToolFrontFixed ? "Front fixed" - : t.IsToolRearFixed ? "Rear fixed" - : t.IsToolTrailing ? "Trailing" - : "—"; + string attach = AgOpenWeb.Models.Tool.RigGeometry.Mount(t) switch + { + AgOpenWeb.Models.Tool.ToolMount.FrontFixed => "Front fixed", + AgOpenWeb.Models.Tool.ToolMount.RearFixed => "Rear fixed", + AgOpenWeb.Models.Tool.ToolMount.TBT => "TBT", + _ => "Trailing", + }; return // The sections' total, as guidance and coverage use; Tool.Width is a stored // default the section editor never updates (#345). diff --git a/Shared/AgOpenWeb.Services/Tool/ToolPositionService.cs b/Shared/AgOpenWeb.Services/Tool/ToolPositionService.cs index 2c8ed2974..3c01d80cf 100644 --- a/Shared/AgOpenWeb.Services/Tool/ToolPositionService.cs +++ b/Shared/AgOpenWeb.Services/Tool/ToolPositionService.cs @@ -6,6 +6,7 @@ using System.Threading; using AgOpenWeb.Models.Base; using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Tool; using AgOpenWeb.Services.Interfaces; namespace AgOpenWeb.Services.Tool; @@ -62,32 +63,6 @@ public class ToolPositionService : IToolPositionService private const double TOOL_JACKKNIFE = 1.9; private const double TANK_JACKKNIFE = 2.0; - /// - /// How far the tool's working centre is behind what pulls it (the hitch, or the TBT tank): - /// the trailing hitch length plus the pivot distance. The pivot distance is AgOpenGPS's - /// (setTool_trailingToolToPivotLength; the Tool panel's Behind / Ahead): positive with the - /// tool behind its wheels, negative with it ahead. It used to be subtracted, so a tool set - /// "Behind" (or imported from AgOpenGPS) worked ahead of its wheels, twice the distance off. - /// - internal static double ToolBehindPivot(ToolConfig tool) => tool.TrailingHitchLength + tool.TrailingToolToPivotLength; - - /// - /// A trailed or TBT tool's working centre, from the tractor's hitch and the headings of the - /// TBT tank and of the tool: the geometry gives the sections and the - /// coverage. The map's implement (MainViewModel's render pull, which dead-reckons the hitch - /// between fixes) is placed with it, so the map draws the tool where it works. - /// - public static (double Easting, double Northing) TrailedToolCentre( - double hitchEasting, double hitchNorthing, double tankHeading, double toolHeading, ToolConfig tool) - { - if (tool.IsToolTBT) - { - hitchEasting -= Math.Sin(tankHeading) * tool.TankTrailingHitchLength; - hitchNorthing -= Math.Cos(tankHeading) * tool.TankTrailingHitchLength; - } - double behind = ToolBehindPivot(tool); - return (hitchEasting - Math.Sin(toolHeading) * behind, hitchNorthing - Math.Cos(toolHeading) * behind); - } private static double AngleBetween(double a, double b) => Math.Abs(Math.PI - Math.Abs(Math.Abs(a - b) - Math.PI)); @@ -143,44 +118,16 @@ public void Update(Vec3 vehiclePivot, double vehicleHeading) var tool = _configStore.Tool; var vehicle = _configStore.Vehicle; - // Calculate hitch point on vehicle. - // Two distinct measurements feed this, chosen by tool type: - // - Rigid front/rear-fixed: Tool.HitchLength = axle -> implement working - // center (tool-dependent). - // - Trailing/TBT: Vehicle.HitchLength = axle -> tractor hitch pin - // (the trailer attach point). - // Stored positive; sign applied by direction (front ahead, rest behind). - double hitchDistance = (tool.IsToolFrontFixed || tool.IsToolRearFixed) - ? Math.Abs(tool.HitchLength) - : Math.Abs(vehicle.HitchLength); - if (tool.IsToolRearFixed || tool.IsToolTrailing || tool.IsToolTBT) - { - hitchDistance = -hitchDistance; // Behind the vehicle - } - // Front fixed keeps positive (ahead of vehicle) - - _hitchPosition = new Vec3( - vehiclePivot.Easting + Math.Sin(vehicleHeading) * hitchDistance, - vehiclePivot.Northing + Math.Cos(vehicleHeading) * hitchDistance, - vehicleHeading - ); + // The point the tool hangs on (RigGeometry): a fixed tool's working point, or the + // tractor's hitch pin for a trailed one. + var mount = RigGeometry.Mount(tool); + _hitchPosition = RigGeometry.AlongTractor(vehiclePivot, vehicleHeading, RigGeometry.AttachX(vehicle, tool)); - if (tool.IsToolFrontFixed || tool.IsToolRearFixed) - { - CalculateFixedToolPosition(vehiclePivot, vehicleHeading, tool); - } - else if (tool.IsToolTBT) + switch (mount) { - CalculateTBTToolPosition(vehicleHeading, tool); - } - else if (tool.IsToolTrailing) - { - CalculateTrailingToolPosition(vehicleHeading, tool); - } - else - { - // Default: treat as fixed rear - CalculateFixedToolPosition(vehiclePivot, vehicleHeading, tool); + case ToolMount.TBT: CalculateTBTToolPosition(vehicleHeading, tool); break; + case ToolMount.Trailing: CalculateTrailingToolPosition(vehicleHeading, tool); break; + default: CalculateFixedToolPosition(vehiclePivot, vehicleHeading, tool); break; } // No lateral offset here: the section spans carry it (SectionControlService, @@ -261,7 +208,7 @@ private void CalculateTrailingToolPosition(double vehicleHeading, ToolConfig too ); // Calculate tool center position (offset from pivot by TrailingToolToPivotLength) - double pivotOffset = ToolBehindPivot(tool); + double pivotOffset = RigGeometry.ToolBehindPivot(tool); _toolPosition = new Vec3( _hitchPosition.Easting - Math.Sin(_toolHeading) * pivotOffset, _hitchPosition.Northing - Math.Cos(_toolHeading) * pivotOffset, @@ -345,7 +292,7 @@ private void CalculateTBTToolPosition(double vehicleHeading, ToolConfig tool) ); // Tool center position - double pivotOffset = ToolBehindPivot(tool); + double pivotOffset = RigGeometry.ToolBehindPivot(tool); _toolPosition = new Vec3( _tankPosition.Easting - Math.Sin(_toolHeading) * pivotOffset, _tankPosition.Northing - Math.Cos(_toolHeading) * pivotOffset, @@ -371,7 +318,7 @@ private void SnapToolBehindVehicle(double vehicleHeading, ToolConfig tool) vehicleHeading ); - double pivotOffset = ToolBehindPivot(tool); + double pivotOffset = RigGeometry.ToolBehindPivot(tool); _toolPosition = new Vec3( _hitchPosition.Easting - Math.Sin(vehicleHeading) * pivotOffset, _hitchPosition.Northing - Math.Cos(vehicleHeading) * pivotOffset, @@ -404,7 +351,7 @@ private void SnapTBTBehindVehicle(double vehicleHeading, ToolConfig tool) vehicleHeading ); - double pivotOffset = ToolBehindPivot(tool); + double pivotOffset = RigGeometry.ToolBehindPivot(tool); _toolPosition = new Vec3( _tankPosition.Easting - Math.Sin(vehicleHeading) * pivotOffset, _tankPosition.Northing - Math.Cos(vehicleHeading) * pivotOffset, @@ -468,24 +415,9 @@ public void ResetTrailingState(Vec3 vehiclePivot, double vehicleHeading) var tool = _configStore.Tool; var vehicle = _configStore.Vehicle; - // Calculate hitch position - must match Update() sign convention and - // tool-type hitch reference: rigid tools use Tool.HitchLength (working - // center), trailing/TBT use Vehicle.HitchLength (tractor hitch pin). - double hitchDistance = (tool.IsToolFrontFixed || tool.IsToolRearFixed) - ? Math.Abs(tool.HitchLength) - : Math.Abs(vehicle.HitchLength); - if (tool.IsToolRearFixed || tool.IsToolTrailing || tool.IsToolTBT) - { - hitchDistance = -hitchDistance; - } - - _hitchPosition = new Vec3( - vehiclePivot.Easting + Math.Sin(vehicleHeading) * hitchDistance, - vehiclePivot.Northing + Math.Cos(vehicleHeading) * hitchDistance, - vehicleHeading - ); + _hitchPosition = RigGeometry.AlongTractor(vehiclePivot, vehicleHeading, RigGeometry.AttachX(vehicle, tool)); - if (tool.IsToolTBT) + if (RigGeometry.Mount(tool) == ToolMount.TBT) { SnapTBTBehindVehicle(vehicleHeading, tool); } diff --git a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs index 7f3220032..c5b9bd170 100644 --- a/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs +++ b/Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs @@ -111,10 +111,10 @@ public TurnPathResult CreateTurnPath( // Diagnostic: one line per turn build to see whether the clearance path is // armed at runtime and with what geometry (note actualWidth vs storedWidth). _logger.LogDebug( - "[YouTurn][HardBoundary] hardOuter={Hard} isHard={IsHard} validOuter={Valid} actualWidth={AW:F1} storedWidth={W:F1} rearFixed={RF} trailing={TR} length={L:F1} margin={M:F1}", + "[YouTurn][HardBoundary] hardOuter={Hard} isHard={IsHard} validOuter={Valid} actualWidth={AW:F1} storedWidth={W:F1} mount={Mount} length={L:F1} margin={M:F1}", hardOuter, boundary?.OuterBoundary?.IsHard, boundary?.OuterBoundary?.IsValid, - config.ActualToolWidth, config.Tool.Width, config.Tool.IsToolRearFixed, - config.Tool.IsToolTrailing, config.Tool.Length, clearanceMargin); + config.ActualToolWidth, config.Tool.Width, RigGeometry.Mount(config.Tool), + config.Tool.Length, clearanceMargin); const double SetbackStep = 0.5; const double MaxExtraSetback = 30.0; @@ -277,28 +277,10 @@ public TurnPathResult CreateTurnPath( /// Maps the runtime tool/vehicle config onto the pure /// used by the implement swept-path clearance check. /// - private static ToolGeometry BuildToolGeometry(ConfigurationStore config) - { - var tool = config.Tool; - ToolMount mount = - tool.IsToolFrontFixed ? ToolMount.FrontFixed : - tool.IsToolTBT ? ToolMount.TBT : - tool.IsToolTrailing ? ToolMount.Trailing : - ToolMount.RearFixed; // rear-fixed is the default - - return new ToolGeometry( - Mount: mount, - // Real implement width comes from the active sections (ActualToolWidth), - // not Tool.Width — the latter is a stored fallback (often the 6 m default). - Width: config.ActualToolWidth, - Offset: tool.Offset, - VehicleHitchLength: config.Vehicle.HitchLength, - ToolHitchLength: tool.HitchLength, - TrailingHitchLength: tool.TrailingHitchLength, - TrailingToolToPivotLength: tool.TrailingToolToPivotLength, - TankTrailingHitchLength: tool.TankTrailingHitchLength, - Length: tool.Length); - } + // Real implement width comes from the active sections (ActualToolWidth), not Tool.Width: + // the latter is a stored fallback (often the 6 m default). + private static ToolGeometry BuildToolGeometry(ConfigurationStore config) => + RigGeometry.Geometry(config.Vehicle, config.Tool, config.ActualToolWidth); /// /// True when the implement swept along stays clear of a diff --git a/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs b/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs index e152d00eb..67ef3f5ef 100644 --- a/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs +++ b/Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs @@ -1050,9 +1050,9 @@ private EndTried EndKey(in TickContext ctx, Models.Track.Track track, GuidanceWo var g = c.Guidance; int settings = HashCode.Combine( HashCode.Combine(g.UTurnRadius, g.UTurnDistanceFromBoundary, g.UTurnExtension, g.UTurnSmoothing, g.UTurnStyle), - HashCode.Combine(c.ActualToolWidth, c.Tool.Width, c.Tool.Overlap, c.Tool.Offset, c.Tool.Length, - c.Tool.IsToolTrailing, c.Tool.IsToolRearFixed), - HashCode.Combine(c.Vehicle.HitchLength, ctx.HeadlandCalculatedWidth, ctx.HeadlandDistance)); + HashCode.Combine(c.ActualToolWidth, c.Tool.Width, c.Tool.Overlap, + AgOpenWeb.Models.Tool.RigGeometry.Stamp(c.Vehicle, c.Tool)), + HashCode.Combine(ctx.HeadlandCalculatedWidth, ctx.HeadlandDistance)); return new EndTried(track, guidance.HowManyPathsAway, guidance.IsHeadingSameWay, guidance.NudgeOffset, turn.SnakeSequence, fieldStamp, ctx.UTurnSkipRows, turn.NextUTurnDirectionLeftOverride, settings); } diff --git a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs index a3d54d445..3d8cb2c3b 100644 --- a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs +++ b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs @@ -694,19 +694,13 @@ private void OnRenderPullTick(object? sender, EventArgs e) var p = _positionEstimator.GetPose(renderTs); var tool = ConfigStore.Tool; - // Rigid tools use Tool.HitchLength (axle -> working center); trailing/TBT use - // Vehicle.HitchLength (axle -> tractor hitch pin). Matches ToolPositionService. - double hitchDistance = (tool.IsToolFrontFixed || tool.IsToolRearFixed) - ? Math.Abs(tool.HitchLength) - : Math.Abs(ConfigStore.Vehicle.HitchLength); - if (tool.IsToolRearFixed || tool.IsToolTrailing || tool.IsToolTBT) - hitchDistance = -hitchDistance; - - double hitchE = p.Position.Easting + Math.Sin(p.Heading) * hitchDistance; - double hitchN = p.Position.Northing + Math.Cos(p.Heading) * hitchDistance; + // The point the tool hangs on, as the tool position has it (RigGeometry). + var hitch = AgOpenWeb.Models.Tool.RigGeometry.AlongTractor(new Vec3(p.Position.Easting, p.Position.Northing, p.Heading), p.Heading, + AgOpenWeb.Models.Tool.RigGeometry.AttachX(ConfigStore.Vehicle, tool)); + double hitchE = hitch.Easting, hitchN = hitch.Northing; double toolE, toolN, toolHeading; - if (tool.IsToolFrontFixed || tool.IsToolRearFixed) + if (AgOpenWeb.Models.Tool.RigGeometry.IsRigid(AgOpenWeb.Models.Tool.RigGeometry.Mount(tool))) { // Fixed tool follows the vehicle exactly — no Torriem state. toolHeading = p.Heading; @@ -721,7 +715,7 @@ private void OnRenderPullTick(object? sender, EventArgs e) // with the tool position's own geometry: the pivot distance's // sign as AgOpenGPS's, so "Behind" is drawn behind the wheels. toolHeading = _toolPositionService.ToolHeading; - (toolE, toolN) = AgOpenWeb.Services.Tool.ToolPositionService.TrailedToolCentre( + (toolE, toolN) = AgOpenWeb.Models.Tool.RigGeometry.TrailedToolCentre( hitchE, hitchN, _toolPositionService.TankPosition.Heading, toolHeading, tool); } diff --git a/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs b/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs index 5d7360c08..afcdc2ca0 100644 --- a/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs @@ -170,7 +170,7 @@ public void TankHitch_SavedNegativeByAgOpenGps_PutsTheTankBehindTheHitch() Assert.That(store.Tool.TankTrailingHitchLength, Is.EqualTo(1.8)); // Heading north, straight behind the tractor: the tool is 4.2 m south of the hitch. - var (easting, northing) = ToolPositionService.TrailedToolCentre(0, 0, 0.0, 0.0, store.Tool); + var (easting, northing) = AgOpenWeb.Models.Tool.RigGeometry.TrailedToolCentre(0, 0, 0.0, 0.0, store.Tool); Assert.That(easting, Is.EqualTo(0.0).Within(1e-9)); Assert.That(northing, Is.EqualTo(-4.2).Within(1e-9), "tank and tool behind the hitch (-0.6 with the tank ahead of it)"); } diff --git a/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs b/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs new file mode 100644 index 000000000..50705b4e7 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs @@ -0,0 +1,118 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Tool; +using AgOpenWeb.Services.Tool; +using NUnit.Framework; + +namespace AgOpenWeb.Services.Tests.Tool; + +/// +/// Where the tool hangs on the tractor is worked out in one place, +/// (phase 1 of NonTracked/HITCH_MODEL_PLAN.md: today's model, no change in where the tool is). +/// The tool position, the map's implement, the U-turn's swept path and its remembered-end key +/// each had their own copy, and the copies disagreed on which flag won when a profile set two. +/// +[TestFixture, NonParallelizable] // ConfigurationStore is a singleton. +public class RigGeometryTests +{ + // Every combination of the four tool-type flags (a profile sets them one by one). + private static IEnumerable Flags() => + Enumerable.Range(0, 16).Select(b => new TestCaseData((b & 1) != 0, (b & 2) != 0, (b & 4) != 0, (b & 8) != 0) + .SetName($"front {(b & 1) != 0}, rear {(b & 2) != 0}, trailing {(b & 4) != 0}, TBT {(b & 8) != 0}")); + + private static ToolConfig Tool(bool front, bool rear, bool trailing, bool tbt) => new() + { + IsToolFrontFixed = front, IsToolRearFixed = rear, IsToolTrailing = trailing, IsToolTBT = tbt, + HitchLength = 1.3, TrailingHitchLength = 2.5, TankTrailingHitchLength = 3.0, TrailingToolToPivotLength = 0.4, + }; + + // The tool position's hitch before RigGeometry (ToolPositionService.Update, develop 5babf3ad). + private static double OldAttachX(VehicleConfig v, ToolConfig t) + { + double d = (t.IsToolFrontFixed || t.IsToolRearFixed) ? Math.Abs(t.HitchLength) : Math.Abs(v.HitchLength); + if (t.IsToolRearFixed || t.IsToolTrailing || t.IsToolTBT) d = -d; + return d; + } + + [TestCaseSource(nameof(Flags))] + public void The_hitch_point_is_where_the_tool_position_had_it(bool front, bool rear, bool trailing, bool tbt) + { + var v = new VehicleConfig { HitchLength = 0.9 }; + var t = Tool(front, rear, trailing, tbt); + if (!front && !rear && !trailing && !tbt) + { + // No flag set: the old copy put the tractor's hitch pin ahead of the axle and a fixed + // tool on it; RigGeometry makes it the default rear-fixed tool. + Assert.That(RigGeometry.Mount(t), Is.EqualTo(ToolMount.RearFixed)); + Assert.That(RigGeometry.AttachX(v, t), Is.EqualTo(-1.3)); + return; + } + bool oldRigid = front || rear; + Assert.Multiple(() => + { + Assert.That(RigGeometry.AttachX(v, t), Is.EqualTo(OldAttachX(v, t))); + Assert.That(RigGeometry.IsRigid(RigGeometry.Mount(t)), Is.EqualTo(oldRigid)); + if (!oldRigid) + Assert.That(RigGeometry.Mount(t), Is.EqualTo(tbt ? ToolMount.TBT : ToolMount.Trailing)); + // The swept path's geometry gives the same point. + Assert.That(RigGeometry.AttachX(RigGeometry.Geometry(v, t, 6)), Is.EqualTo(OldAttachX(v, t))); + }); + } + + [TestCase("front", 1.3)] + [TestCase("rear", -1.3)] + [TestCase("trailing", -0.9)] + [TestCase("tbt", -0.9)] + public void The_tool_position_service_puts_the_hitch_there(string type, double x) + { + var store = new ConfigurationStore(); + ConfigurationStore.SetInstance(store); + store.Vehicle.HitchLength = 0.9; + store.Tool.HitchLength = 1.3; + store.Tool.SetToolType(type); + var svc = new ToolPositionService(store); + svc.Update(new Vec3(10, 20, 0), 0); // heading north + Assert.Multiple(() => + { + Assert.That(svc.HitchPosition.Easting, Is.EqualTo(10).Within(1e-9)); + Assert.That(svc.HitchPosition.Northing, Is.EqualTo(20 + x).Within(1e-9)); + }); + } + + // No hitch branch of its own outside RigGeometry: the files that place the tool read the + // hitch lengths and the type flags only through it. + [TestCase("Shared/AgOpenWeb.Services/Tool/ToolPositionService.cs")] + [TestCase("Shared/AgOpenWeb.ViewModels/MainViewModel.cs")] + [TestCase("Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs")] + [TestCase("Shared/AgOpenWeb.Services/YouTurn/YouTurnCreationService.Orchestration.cs")] + [TestCase("Shared/AgOpenWeb.Services/YouTurn/YouTurnStateMachine.cs")] + [TestCase("Shared/AgOpenWeb.RemoteServer/SceneProjector.cs")] + public void Places_the_tool_only_through_RigGeometry(string file) + { + var src = File.ReadAllText(Path.Combine(RepoRoot(), file)); + // The config DTOs carry the raw values to the settings panel; those lines are not geometry. + var lines = src.Split('\n').Where(l => !l.Contains("ConfigDto(") && !l.TrimStart().StartsWith("//") + && !l.TrimStart().StartsWith("///") && !l.Contains("foreach (var d in new[]")); + var bad = lines.Where(l => Regex.IsMatch(l, @"\.(HitchLength|ToolHitchLength|VehicleHitchLength)\b|IsTool(FrontFixed|RearFixed|Trailing|TBT)\b")) + .Select(l => l.Trim()).ToList(); + Assert.That(bad, Is.Empty, $"{file} works out the hitch itself; use RigGeometry"); + } + + private static string RepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AgOpenWeb.sln"))) + dir = dir.Parent; + Assert.That(dir, Is.Not.Null, "Could not locate repo root (AgOpenWeb.sln) from test base dir."); + return dir!.FullName; + } +} diff --git a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs index 0e4e4e5e9..60cddb489 100644 --- a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs @@ -157,7 +157,7 @@ private static void Drive(string mount, double pivotDistance, double turnRadius { // The map's implement (MainViewModel's render pull) is placed from the hitch and // the headings with TrailedToolCentre: it must land on the tool position, every step. - var (me, mn) = ToolPositionService.TrailedToolCentre(ours.HitchPosition.Easting, ours.HitchPosition.Northing, + var (me, mn) = AgOpenWeb.Models.Tool.RigGeometry.TrailedToolCentre(ours.HitchPosition.Easting, ours.HitchPosition.Northing, ours.TankPosition.Heading, ours.ToolHeading, c.Tool); double dm = Math.Sqrt(Math.Pow(me - ours.ToolPosition.Easting, 2) + Math.Pow(mn - ours.ToolPosition.Northing, 2)); if (dm > worstMap) { worstMap = dm; worstMapAt = step; } From 8ae2279e609ec598a77c1fa461fbf16d1077e699 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 16:30:23 -0600 Subject: [PATCH 13/24] Hitch model: the tractor's hitch points on the vehicle, a fixed tool's distance from its hitch #547, phase 2 of NonTracked/HITCH_MODEL_PLAN.md, after ISO 11783-10 (a device's connectors from its reference point, the tractor's rear axle; its elements from them). A fixed tool was placed by one number per tool, rear axle -> working point, so a tool profile was right only on the tractor it was measured on. - Vehicle: HitchLength (the drawbar pin, as before), ThreePointHitchLength (rear 3-point, the centre of the lower links) and FrontHitchLength (front 3-point; asked for only with a front tool). - Tool: WorkingPointFromHitch replaces HitchLength: the working point from its 3-point hitch, away from the tractor, negative on the tractor's side. RigGeometry chains them. - Profiles format 3. A format-2 tool is converted against the vehicle loaded with it so it stays exactly where it was (a format-2 vehicle's 3-point starts at its one hitch; a front tool gives a vehicle without a front hitch its value), and both files are saved at once. Format 3 still writes the axle -> working point as hitchLength for an older build; a profile from a newer build is not overwritten. - AgOpenGPS import: the tractor's 3-point and front hitch from the rear- and front-mounted implements saved last (as the drawbar from the trailed one), each fixed implement its AgOpenGPS distance less that hitch: every implement where AgOpenGPS has it. - Settings: Vehicle > Rear 3-point hitch, Drawbar pin, Front 3-point hitch (with a front tool); Tool > Hitch to working point, with the sum from the rear axle. - Harnesses send a fixed implement as the tractor's hitch with the implement at it (the same place); the careless operator's mounted-distance mistakes go to the 3-point hitch. Co-Authored-By: Claude Opus 5.5 --- .../Configuration/ConfigurationStore.cs | 6 + .../Configuration/ToolConfig.cs | 21 +- .../Configuration/VehicleConfig.cs | 27 +- .../Configuration/VehicleToolLimits.cs | 6 +- .../Tool/ImplementSweptPath.cs | 9 +- Shared/AgOpenWeb.Models/Tool/RigGeometry.cs | 42 +-- Shared/AgOpenWeb.RemoteServer/Contracts.cs | 6 +- .../AgOpenWeb.RemoteServer/SceneProjector.cs | 8 +- Shared/AgOpenWeb.RemoteServer/WireCodec.cs | 4 +- .../wwwroot/i18n/en.json | 8 +- .../AgOpenWeb.RemoteServer/wwwroot/index.html | 7 +- .../wwwroot/js/config-panels.js | 12 +- .../wwwroot/js/settings-panels.js | 3 + .../wwwroot/transport.js | 5 +- .../RemoteServerWiring.Helpers.cs | 4 +- .../ConfigurationService.cs | 24 +- Shared/AgOpenWeb.Services/DebugDumpService.cs | 10 +- .../Profile/AgOpenGpsProfileImport.cs | 24 +- .../Profile/AgOpenGpsSettings.cs | 27 +- .../Profile/HitchMigration.cs | 83 ++++++ .../Profile/ProfileJsonServiceV1.cs | 15 +- .../Profile/ToolProfileJsonService.cs | 23 +- .../Profile/VehicleProfileJsonService.cs | 17 +- .../AgOpenWeb.Services/ToolProfileService.cs | 2 +- .../VehicleProfileService.cs | 4 +- .../ConfigurationViewModel.cs | 9 +- .../MainViewModel.SettingsUnderAutoSteer.cs | 6 +- .../ReporterSettings.cs | 11 +- .../TakeOver.cs | 4 +- .../Machines.cs | 9 +- .../ImplementSweptPathTests.cs | 6 +- .../TurnClearanceTests.cs | 4 +- .../VehicleToolLimitsTests.cs | 4 +- .../AgOpenGpsProfileImportTests.cs | 7 +- .../AsymmetricSectionTurnTests.cs | 4 +- .../AutoSteerUTurnNUnitTests.cs | 4 +- .../HitchModelTests.cs | 279 ++++++++++++++++++ .../HitchSplitTests.cs | 187 ------------ .../LookAheadSlitTests.cs | 4 +- .../OffsetFixTests.cs | 20 +- .../Pipeline/ClosedPassSeamPipelineTests.cs | 4 +- .../Pipeline/HeadlandToggleTests.cs | 4 +- .../ProfileJsonServiceV1Tests.cs | 6 +- .../ProfileMigrationTests.cs | 5 +- .../Tool/RigGeometryTests.cs | 16 +- .../Tool/ToolPositionMatchesAgOpenGpsTests.cs | 4 +- .../ToolPositionServiceConcurrencyTests.cs | 3 +- .../ToolOffsetCoverageTests.cs | 4 +- .../TractorPathTests.cs | 4 +- .../TramOffSendsNothingTests.cs | 4 +- .../TramTriggerAtToolTests.cs | 4 +- .../YouTurn/AutoTurnHardBoundaryTests.cs | 7 +- .../YouTurn/ManualTurnHardBoundaryTests.cs | 4 +- .../YouTurn/ManualTurnRadiusTests.cs | 4 +- .../YouTurn/TurnReferenceAheadTests.cs | 4 +- .../YouTurn/TurnSetbackStaysAtEndTests.cs | 4 +- Tests/AgOpenWeb.UpdateScenarios.Tests/Farm.cs | 19 +- .../CarelessOperator.cs | 10 +- .../Machines.cs | 9 +- .../Readback.cs | 4 +- .../SettingsSweep.cs | 3 +- .../AutoSteerOffWhenSettingsChangeTests.cs | 3 +- .../Machines.cs | 9 +- 63 files changed, 750 insertions(+), 343 deletions(-) create mode 100644 Shared/AgOpenWeb.Services/Profile/HitchMigration.cs create mode 100644 Tests/AgOpenWeb.Services.Tests/HitchModelTests.cs delete mode 100644 Tests/AgOpenWeb.Services.Tests/HitchSplitTests.cs diff --git a/Shared/AgOpenWeb.Models/Configuration/ConfigurationStore.cs b/Shared/AgOpenWeb.Models/Configuration/ConfigurationStore.cs index 151675dbf..82f0ba2aa 100644 --- a/Shared/AgOpenWeb.Models/Configuration/ConfigurationStore.cs +++ b/Shared/AgOpenWeb.Models/Configuration/ConfigurationStore.cs @@ -71,6 +71,12 @@ public string ActiveToolProfileName set => SetProperty(ref _activeToolProfileName, value); } + /// + /// What the last profile load converted from an older format (a tool's hitch, #547), for the + /// operator and the log; null when nothing was. The loader saves the converted files at once. + /// + public string? ProfileMigrationNote { get; set; } + private string _activeToolProfilePath = string.Empty; public string ActiveToolProfilePath { diff --git a/Shared/AgOpenWeb.Models/Configuration/ToolConfig.cs b/Shared/AgOpenWeb.Models/Configuration/ToolConfig.cs index 16bfa5f7e..9d358b575 100644 --- a/Shared/AgOpenWeb.Models/Configuration/ToolConfig.cs +++ b/Shared/AgOpenWeb.Models/Configuration/ToolConfig.cs @@ -47,15 +47,18 @@ public double Offset set => SetProperty(ref _offset, value); } - // Hitch (rigid tool): axle center -> implement working center (e.g. tiller rotary - // shaft, disc shaft). Tool-dependent, so it lives with the tool. Used ONLY by - // front/rear-fixed rigid tools. Trailing/TBT tools instead use Vehicle.HitchLength - // for the tractor hitch pin. Front tools use this positive (ahead), rear negative. - private double _hitchLength = 1.8; - public double HitchLength - { - get => _hitchLength; - set => SetProperty(ref _hitchLength, Math.Max(0, value)); + // Rigid (front/rear fixed) tool: its working point (e.g. a tiller's rotor, a boom) from the + // tractor hitch it is mounted on (the rear 3-point's lower links, or the front 3-point), measured + // away from the tractor: behind a rear hitch, ahead of a front one. Negative = the working point + // is on the tractor's side of the hitch. The tractor's hitch is the vehicle's + // (Vehicle.ThreePointHitchLength / FrontHitchLength), so one tool profile fits every tractor + // (#547; ISO 11783-10: the device element's offset from its connector). Trailing/TBT tools hang + // on the drawbar (Vehicle.HitchLength) and use their own lengths below. + private double _workingPointFromHitch; + public double WorkingPointFromHitch + { + get => _workingPointFromHitch; + set => SetProperty(ref _workingPointFromHitch, value); } // Fore-aft length of the implement body, from its attachment (hitch for rigid diff --git a/Shared/AgOpenWeb.Models/Configuration/VehicleConfig.cs b/Shared/AgOpenWeb.Models/Configuration/VehicleConfig.cs index c6be78cf7..ce1fa609c 100644 --- a/Shared/AgOpenWeb.Models/Configuration/VehicleConfig.cs +++ b/Shared/AgOpenWeb.Models/Configuration/VehicleConfig.cs @@ -108,11 +108,10 @@ public double ImuOffset set => SetProperty(ref _imuOffset, value); } - // Hitch (vehicle): rear axle center -> tractor hitch pin. This is the trailer - // attach point and is used ONLY by trailing/TBT tools to place the hitch. - // Rigid front/rear-fixed tools instead use Tool.HitchLength (axle -> implement - // working center, which is tool-dependent). Stored unsigned/positive; the tool - // position geometry applies the rear sign and takes Math.Abs(). + // The tractor's hitch points, distances from the centre of the rear axle (ISO 11783-10: the + // tractor's device reference point; its connectors). Kept positive, each in its own direction. + // + // Drawbar: the hitch pin, behind the axle. Trailing and TBT tools hang on it. private double _hitchLength = 1.8; public double HitchLength { @@ -120,6 +119,24 @@ public double HitchLength set => SetProperty(ref _hitchLength, value); } + // Rear 3-point hitch: the centre of the lower link points, behind the axle (ISO 11783-10's CRP + // of a three-point hitch). Rear-fixed tools hang on it. + private double _threePointHitchLength = 1.8; + public double ThreePointHitchLength + { + get => _threePointHitchLength; + set => SetProperty(ref _threePointHitchLength, Math.Max(0, value)); + } + + // Front 3-point hitch: the centre of its lower link points, ahead of the axle. Front-fixed tools + // hang on it; asked for only when a front tool is used. + private double _frontHitchLength; + public double FrontHitchLength + { + get => _frontHitchLength; + set => SetProperty(ref _frontHitchLength, Math.Max(0, value)); + } + // ISO 11783 hitch/coupling type code on the tractor side (-1 = not available, // 0 = unknown/default, 1..10 = specific ISO coupling standards). Stored as the // ISO integer; the UI shows the text description. Informational metadata for now. diff --git a/Shared/AgOpenWeb.Models/Configuration/VehicleToolLimits.cs b/Shared/AgOpenWeb.Models/Configuration/VehicleToolLimits.cs index 8f28eaf21..072db2e03 100644 --- a/Shared/AgOpenWeb.Models/Configuration/VehicleToolLimits.cs +++ b/Shared/AgOpenWeb.Models/Configuration/VehicleToolLimits.cs @@ -34,7 +34,9 @@ private enum Unit { Metre, Centimetre, Degree, Second, KmH, Percent, None } ["vehicle.antennaHeight"] = new(0, 10, Unit.Metre, "Antenna height"), ["vehicle.antennaPivot"] = new(-10, 10, Unit.Metre, "Antenna pivot"), ["vehicle.antennaOffset"] = new(-5, 5, Unit.Metre, "Antenna offset"), - ["vehicle.hitchLength"] = new(0, 40, Unit.Metre, "Hitch length"), + ["vehicle.hitchLength"] = new(0, 40, Unit.Metre, "Drawbar pin"), + ["vehicle.threePointHitchLength"] = new(0, 10, Unit.Metre, "Rear 3-point hitch"), + ["vehicle.frontHitchLength"] = new(0, 15, Unit.Metre, "Front 3-point hitch"), ["vehicle.imuPivot"] = new(-10, 10, Unit.Metre, "IMU pivot"), ["vehicle.imuHeight"] = new(0, 10, Unit.Metre, "IMU height"), ["vehicle.imuOffset"] = new(-5, 5, Unit.Metre, "IMU offset"), @@ -46,7 +48,7 @@ private enum Unit { Metre, Centimetre, Degree, Second, KmH, Percent, None } ["gps.fixToFixDistance"] = new(0.1, 10, Unit.Metre, "Fix-to-fix heading distance"), ["roll.rollZero"] = new(-30, 30, Unit.Degree, "Roll zero"), ["roll.rollFilter"] = new(0, 0.99, Unit.None, "Roll filter"), - ["tool.hitchLength"] = new(0, 30, Unit.Metre, "Working center"), + ["tool.workingPointFromHitch"] = new(-10, 30, Unit.Metre, "Hitch to working point"), ["tool.trailingHitchLength"] = new(0.1, 30, Unit.Metre, "Trailing hitch"), ["tool.tankTrailingHitchLength"] = new(0.1, 30, Unit.Metre, "Tank trailing hitch"), ["tool.trailingToolToPivotLength"] = new(-20, 20, Unit.Metre, "Pivot distance"), diff --git a/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs b/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs index e42420790..325a680a1 100644 --- a/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs +++ b/Shared/AgOpenWeb.Models/Tool/ImplementSweptPath.cs @@ -28,8 +28,8 @@ public enum ToolMount /// Attachment type. /// Implement working width. /// Lateral offset of the tool centre (right positive). - /// Axle → tractor hitch pin (used by Trailing/TBT). - /// Axle → implement working centre (used by rigid tools). + /// Where the tool hangs along the tractor from the rear axle, + ahead: a fixed + /// tool's working point, a trailed tool's drawbar pin (). /// Hitch → tool pivot, along the tool. /// Tool pivot → tool working centre, AgOpenGPS's sign: positive with the tool behind its wheels. /// Hitch → tank pivot (TBT only). @@ -44,8 +44,7 @@ public readonly record struct ToolGeometry( ToolMount Mount, double Width, double Offset, - double VehicleHitchLength, - double ToolHitchLength, + double AttachX, double TrailingHitchLength, double TrailingToolToPivotLength, double TankTrailingHitchLength, @@ -102,7 +101,7 @@ public static ImplementSweptPathResult Compute(IReadOnlyList pivotPath, To double halfWidth = geom.Width / 2.0; // The point the tool hangs on, as the tool position has it (RigGeometry). - double hitchDistance = RigGeometry.AttachX(geom); + double hitchDistance = geom.AttachX; Vec3 lastToolPivot = default; Vec3 lastTank = default; diff --git a/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs b/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs index b25e0ec05..ee7aceec1 100644 --- a/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs +++ b/Shared/AgOpenWeb.Models/Tool/RigGeometry.cs @@ -40,25 +40,32 @@ public static ToolMount Mount(ToolConfig tool) public static bool IsRigid(ToolMount mount) => mount is ToolMount.FrontFixed or ToolMount.RearFixed; /// - /// The point the tool is attached at, along the tractor from the rear axle (+ ahead). - /// A fixed tool: its working point, Tool.HitchLength (axle → working point), - /// ahead for a front tool, behind for a rear one. A trailed or TBT tool: the tractor's - /// hitch pin, Vehicle.HitchLength, behind. Both are kept positive; the sign is - /// the mount's. + /// Where the tool is placed along the tractor from the rear axle (+ ahead), as ISO + /// 11783-10 chains it: the tractor's connector, then the tool from it. A fixed tool: its + /// working point, the vehicle's 3-point hitch (front or rear) plus the tool's distance from + /// it, away from the tractor. A trailed or TBT tool: the drawbar pin, behind the axle. /// - public static double AttachX(ToolMount mount, double vehicleHitchLength, double toolHitchLength) - { - double d = Math.Abs(IsRigid(mount) ? toolHitchLength : vehicleHitchLength); - return mount == ToolMount.FrontFixed ? d : -d; - } + public static double AttachX(ToolMount mount, double drawbar, double threePoint, double frontHitch, double workingPointFromHitch) => + mount switch + { + ToolMount.FrontFixed => Math.Abs(frontHitch) + workingPointFromHitch, + ToolMount.RearFixed => -(Math.Abs(threePoint) + workingPointFromHitch), + _ => -Math.Abs(drawbar), + }; - /// + /// public static double AttachX(VehicleConfig vehicle, ToolConfig tool) => - AttachX(Mount(tool), vehicle.HitchLength, tool.HitchLength); + AttachX(Mount(tool), vehicle.HitchLength, vehicle.ThreePointHitchLength, vehicle.FrontHitchLength, tool.WorkingPointFromHitch); - /// - public static double AttachX(in ToolGeometry geom) => - AttachX(geom.Mount, geom.VehicleHitchLength, geom.ToolHitchLength); + /// + /// A fixed tool's working point from the rear axle, the way profiles stored it before the + /// hitch points moved to the vehicle (format 2's Tool.HitchLength, positive, ahead + /// for a front tool): what an older build reads back from a newer profile. + /// + public static double AxleToWorkingPoint(VehicleConfig vehicle, ToolConfig tool) => + Mount(tool) == ToolMount.FrontFixed + ? Math.Abs(vehicle.FrontHitchLength) + tool.WorkingPointFromHitch + : Math.Abs(vehicle.ThreePointHitchLength) + tool.WorkingPointFromHitch; /// The point along the tractor (+ ahead) from . public static Vec3 AlongTractor(Vec3 pivot, double heading, double x) => @@ -93,8 +100,7 @@ public static (double Easting, double Northing) TrailedToolCentre( Mount: Mount(tool), Width: width, Offset: tool.Offset, - VehicleHitchLength: vehicle.HitchLength, - ToolHitchLength: tool.HitchLength, + AttachX: AttachX(vehicle, tool), TrailingHitchLength: tool.TrailingHitchLength, TrailingToolToPivotLength: tool.TrailingToolToPivotLength, TankTrailingHitchLength: tool.TankTrailingHitchLength, @@ -105,7 +111,7 @@ public static (double Easting, double Northing) TrailedToolCentre( /// from the tool's position (a U-turn tried and refused at this end) is stale once it changes. /// public static int Stamp(VehicleConfig vehicle, ToolConfig tool) => HashCode.Combine( - Mount(tool), vehicle.HitchLength, tool.HitchLength, tool.TrailingHitchLength, + Mount(tool), AttachX(vehicle, tool), tool.TrailingHitchLength, tool.TrailingToolToPivotLength, tool.TankTrailingHitchLength, tool.Offset, tool.Length); } } diff --git a/Shared/AgOpenWeb.RemoteServer/Contracts.cs b/Shared/AgOpenWeb.RemoteServer/Contracts.cs index aa7cf4265..df77654e7 100644 --- a/Shared/AgOpenWeb.RemoteServer/Contracts.cs +++ b/Shared/AgOpenWeb.RemoteServer/Contracts.cs @@ -458,7 +458,7 @@ public record DisplayConfigDto( /// Tool/Implement tab (ConfigStore.Tool + NumSections). Type: 0 front, 1 rear, /// 2 TBT, 3 trailing. Arrays fixed-size (16 widths/colours, 9 zone ranges). public record ToolConfigDto( - int Type, int HitchType, double HitchLength, double TrailingHitchLength, + int Type, int HitchType, double WorkingPointFromHitch, double TrailingHitchLength, double TankTrailingHitchLength, double Length, double LookAheadOn, double LookAheadOff, double TurnOffDelay, double Offset, double Overlap, double TrailingToolToPivotLength, @@ -516,7 +516,9 @@ public record VehicleConfigDto( string Name, int Type, // VehicleType: 0 Tractor, 1 Harvester, 2 FourWD int HitchType, // code; index into HitchTypeOptions = HitchType + 1 - double HitchLength, + double HitchLength, // drawbar pin behind the rear axle + double ThreePointHitchLength, // rear 3-point (lower links) behind the rear axle + double FrontHitchLength, // front 3-point ahead of the rear axle; 0 = none double Wheelbase, double TrackWidth, double AntennaPivot, diff --git a/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs b/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs index 9772f1754..1a17cbdb1 100644 --- a/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs +++ b/Shared/AgOpenWeb.RemoteServer/SceneProjector.cs @@ -824,14 +824,14 @@ public ConfigDto BuildConfig() var zones = new int[9]; for (int i = 0; i < 9; i++) zones[i] = t.GetZoneEndSection(i); var pins = new int[24]; for (int i = 0; i < 24; i++) pins[i] = (int)m.GetPinAssignment(i); return new ConfigDto( - new VehicleConfigDto(v.Name, (int)v.Type, v.HitchType, v.HitchLength, v.Wheelbase, + new VehicleConfigDto(v.Name, (int)v.Type, v.HitchType, v.HitchLength, v.ThreePointHitchLength, v.FrontHitchLength, v.Wheelbase, v.TrackWidth, v.AntennaPivot, v.AntennaHeight, v.AntennaOffset, v.ImuPivot, v.ImuHeight, v.ImuOffset), new GpsConfigDto(c.IsDualGps, c.DualHeadingOffset, c.DualReverseDistance, c.AutoDualFix, c.DualSwitchSpeed, c.MinGpsStep, c.FixToFixDistance, c.HeadingFusionWeight, c.ReverseDetection, c.RtkLostAlarm, c.RtkLostAction), new RollConfigDto(a.RollZero, a.RollFilter, a.IsRollInvert), - new ToolConfigDto(toolType, t.HitchType, t.HitchLength, t.TrailingHitchLength, + new ToolConfigDto(toolType, t.HitchType, t.WorkingPointFromHitch, t.TrailingHitchLength, t.TankTrailingHitchLength, t.Length, t.LookAheadOnSetting, t.LookAheadOffSetting, t.TurnOffDelay, t.Offset, t.Overlap, t.TrailingToolToPivotLength, t.IsSectionsNotZones, _config.NumSections, t.DefaultSectionWidth, widths, @@ -925,7 +925,7 @@ public long ConfigFingerprint() h = h * 31 + (c.IsDualGps ? 1 : 0) * 7 + (c.AutoDualFix ? 1 : 0) * 11 + (c.ReverseDetection ? 1 : 0) * 13 + (c.RtkLostAlarm ? 1 : 0) * 17 + c.RtkLostAction * 19 + (a.IsRollInvert ? 1 : 0) * 23; - foreach (var d in new[] { v.HitchLength, v.Wheelbase, v.TrackWidth, v.AntennaPivot, + foreach (var d in new[] { v.HitchLength, v.ThreePointHitchLength, v.FrontHitchLength, v.Wheelbase, v.TrackWidth, v.AntennaPivot, v.AntennaHeight, v.AntennaOffset, c.DualHeadingOffset, c.DualReverseDistance, c.DualSwitchSpeed, c.MinGpsStep, c.FixToFixDistance, c.HeadingFusionWeight, a.RollZero, a.RollFilter }) @@ -938,7 +938,7 @@ public long ConfigFingerprint() + (t.IsSectionOffWhenOut ? 4 : 0) + (t.IsHeadlandSectionControl ? 8 : 0) + t.MinCoverage + (t.IsWorkSwitchEnabled ? 16 : 0) + (t.IsWorkSwitchActiveLow ? 32 : 0) + (t.IsWorkSwitchManualSections ? 64 : 0) + (t.IsSteerSwitchEnabled ? 128 : 0) + (t.IsSteerSwitchManualSections ? 256 : 0); - foreach (var d in new[] { t.HitchLength, t.TrailingHitchLength, t.TankTrailingHitchLength, t.Length, + foreach (var d in new[] { t.WorkingPointFromHitch, t.TrailingHitchLength, t.TankTrailingHitchLength, t.Length, t.LookAheadOnSetting, t.LookAheadOffSetting, t.TurnOffDelay, t.Offset, t.Overlap, t.TrailingToolToPivotLength, t.DefaultSectionWidth, t.SlowSpeedCutoff, t.CoverageMargin, g.UTurnExtension, g.UTurnRadius, g.UTurnDistanceFromBoundary, mc.LookAhead }) diff --git a/Shared/AgOpenWeb.RemoteServer/WireCodec.cs b/Shared/AgOpenWeb.RemoteServer/WireCodec.cs index 88cee6f53..a4c0cae32 100644 --- a/Shared/AgOpenWeb.RemoteServer/WireCodec.cs +++ b/Shared/AgOpenWeb.RemoteServer/WireCodec.cs @@ -347,6 +347,8 @@ public static byte[] EncodeConfig(ConfigDto c) w.Write(v.Type); // i32 w.Write(v.HitchType); // i32 w.Write(v.HitchLength); // f64 + w.Write(v.ThreePointHitchLength); + w.Write(v.FrontHitchLength); w.Write(v.Wheelbase); w.Write(v.TrackWidth); w.Write(v.AntennaPivot); @@ -375,7 +377,7 @@ public static byte[] EncodeConfig(ConfigDto c) var t = c.Tool; w.Write(t.Type); // i32 w.Write(t.HitchType); - w.Write(t.HitchLength); // f64 + w.Write(t.WorkingPointFromHitch); // f64 w.Write(t.TrailingHitchLength); w.Write(t.TankTrailingHitchLength); w.Write(t.Length); diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/en.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/en.json index faf341971..07fd256b4 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/en.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/en.json @@ -286,6 +286,7 @@ "Draw boundary on satellite imagery": "Draw boundary on satellite imagery", "Draw on Map": "Draw on Map", "Draw on Map — one line per edge; where they cross encloses the headland": "Draw on Map — one line per edge; where they cross encloses the headland", + "Drawbar pin": "Drawbar pin", "Drive AB": "Drive AB", "Drive around field to record boundary": "Drive around field to record boundary", "Drive around obstacle (inner boundary)": "Drive around obstacle (inner boundary)", @@ -377,7 +378,9 @@ "From Field Boundary": "From Field Boundary", "From ISO-XML": "From ISO-XML", "From KML": "From KML", + "Front 3-point hitch": "Front 3-point hitch", "Front Fixed": "Front Fixed", + "Front hitch {h} (vehicle) + {d} = {t} ahead of the rear axle": "Front hitch {h} (vehicle) + {d} = {t} ahead of the rear axle", "Front to rear axle distance": "Front to rear axle distance", "Full": "Full", "Full Setup": "Full Setup", @@ -431,7 +434,7 @@ "Hidden track: tick it in Tracks to use it": "Hidden track: tick it in Tracks to use it", "Hitch": "Hitch", "Hitch / Wheelbase / Track": "Hitch / Wheelbase / Track", - "Hitch length": "Hitch length", + "Hitch to working point": "Hitch to working point", "Host": "Host", "Host IPs": "Host IPs", "Hotkeys": "Hotkeys", @@ -745,6 +748,8 @@ "Raise time": "Raise time", "Rate": "Rate", "Ready to finish": "Ready to finish", + "Rear 3-point hitch": "Rear 3-point hitch", + "Rear 3-point hitch {h} (vehicle) + {d} = {t} behind the rear axle": "Rear 3-point hitch {h} (vehicle) + {d} = {t} behind the rear axle", "Rear Fixed": "Rear Fixed", "Receiver and antenna descriptor": "Receiver and antenna descriptor", "Receiver: {fix}, correction age {age} s": "Receiver: {fix}, correction age {age} s", @@ -1087,7 +1092,6 @@ "Whole-boundary headland is not point-edited": "Whole-boundary headland is not point-edited", "Work switch": "Work switch", "Worked": "Worked", - "Working center": "Working center", "XTE": "XTE", "XTE Chart": "XTE Chart", "Yaw rate": "Yaw rate", diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/index.html b/Shared/AgOpenWeb.RemoteServer/wwwroot/index.html index e06ae861c..9d97980df 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/index.html +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/index.html @@ -375,7 +375,9 @@
- + + +
@@ -494,7 +496,8 @@
- + +
diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/config-panels.js b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/config-panels.js index 39819fd43..355aebb82 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/config-panels.js +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/config-panels.js @@ -54,7 +54,17 @@ function populateToolCfg(force) { if (!pi.src.endsWith(pp)) pi.src = pp; // Conditional visibility by tool type (0 front, 1 rear, 2 TBT, 3 trailing). const trailing = t.type === 3, tbt = t.type === 2, trailtbt = trailing || tbt; - tcShow('rigid', !trailing); tcShow('trailtbt', trailtbt); tcShow('tbt', tbt); tcShow('notrailtbt', !trailtbt); + tcShow('rigid', !trailtbt); tcShow('trailtbt', trailtbt); tcShow('tbt', tbt); tcShow('notrailtbt', !trailtbt); + // A fixed tool hangs on the tractor's 3-point hitch (the vehicle's): the sum says where its + // working point is from the rear axle (#547). + if (!trailtbt) { + const front = t.type === 0; + const hitch = (front ? cfgGet('vehicle.frontHitchLength') : cfgGet('vehicle.threePointHitchLength')) || 0; + const total = hitch + (t.workingPointFromHitch || 0); + document.getElementById('tc-hitchsum').textContent = front + ? tr('Front hitch {h} (vehicle) + {d} = {t} ahead of the rear axle', { h: fmtUnit(hitch, 'm', 2), d: fmtUnit(t.workingPointFromHitch || 0, 'm', 2), t: fmtUnit(total, 'm', 2) }) + : tr('Rear 3-point hitch {h} (vehicle) + {d} = {t} behind the rear axle', { h: fmtUnit(hitch, 'm', 2), d: fmtUnit(t.workingPointFromHitch || 0, 'm', 2), t: fmtUnit(total, 'm', 2) }); + } tcShow('individual', t.isSectionsNotZones); tcShow('zones', !t.isSectionsNotZones); tcShow('multicolor', t.isMultiColoredSections); tcShow('singlecolor', !t.isMultiColoredSections); tcShow('worksw', t.isWorkSwitchEnabled); tcShow('steersw', t.isSteerSwitchEnabled); diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/settings-panels.js b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/settings-panels.js index f6f60c3fc..7e0fa5439 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/js/settings-panels.js +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/js/settings-panels.js @@ -110,6 +110,9 @@ function populateVehicleCfg(force) { setImg('vc-img-antside', ['AntennaTractorTop', 'AntennaHarvesterTop', 'AntennaArticulatedTop']); setImg('vc-img-antoffset', ['AntennaTractorOffset', 'AntennaHarvesterOffset', 'AntennaArticulatedOffset']); vcHitchSel.value = cfgGet('vehicle.hitchType'); + // The front 3-point hitch is asked for only with a front-mounted tool (type 0). + const front = cfgGet('tool.type') === 0; + for (const el of vcPanel.querySelectorAll('[data-vshow="front"]')) el.hidden = !front; const w = cfgGet('gps.headingFusionWeight') || 0; vcFw.value = w; document.getElementById('vc-hfw').textContent = fusionLabel(w); // Dual-only fields are live only in Dual GPS mode; reverse detection only in single. diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/transport.js b/Shared/AgOpenWeb.RemoteServer/wwwroot/transport.js index 156d262ac..36ffea10b 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/transport.js +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/transport.js @@ -342,7 +342,8 @@ window.RemoteTransport = { } case TYPE.CONFIG: { const vehicle = { - name: str(), type: i32(), hitchType: i32(), hitchLength: f64(), wheelbase: f64(), + name: str(), type: i32(), hitchType: i32(), hitchLength: f64(), + threePointHitchLength: f64(), frontHitchLength: f64(), wheelbase: f64(), trackWidth: f64(), antennaPivot: f64(), antennaHeight: f64(), antennaOffset: f64(), imuPivot: f64(), imuHeight: f64(), imuOffset: f64(), }; @@ -355,7 +356,7 @@ window.RemoteTransport = { const rdF64 = () => { const n = i32(), a = new Array(n); for (let k = 0; k < n; k++) a[k] = f64(); return a; }; const rdI32 = () => { const n = i32(), a = new Array(n); for (let k = 0; k < n; k++) a[k] = i32(); return a; }; const tool = { - type: i32(), hitchType: i32(), hitchLength: f64(), trailingHitchLength: f64(), + type: i32(), hitchType: i32(), workingPointFromHitch: f64(), trailingHitchLength: f64(), tankTrailingHitchLength: f64(), length: f64(), lookAheadOn: f64(), lookAheadOff: f64(), turnOffDelay: f64(), offset: f64(), overlap: f64(), trailingToolToPivotLength: f64(), isSectionsNotZones: !!u8(), numSections: i32(), defaultSectionWidth: f64(), diff --git a/Shared/AgOpenWeb.RemoteWiring/RemoteServerWiring.Helpers.cs b/Shared/AgOpenWeb.RemoteWiring/RemoteServerWiring.Helpers.cs index de1f3abf2..b252273b9 100644 --- a/Shared/AgOpenWeb.RemoteWiring/RemoteServerWiring.Helpers.cs +++ b/Shared/AgOpenWeb.RemoteWiring/RemoteServerWiring.Helpers.cs @@ -118,6 +118,8 @@ bool IDX(out int i, out string rest) // "i,rest" — indexed array writes return; case "vehicle.hitchType": if (I(out var ht)) veh.HitchType = ht; return; case "vehicle.hitchLength": if (D(out var d1)) veh.HitchLength = d1; return; + case "vehicle.threePointHitchLength": if (D(out var d1b)) veh.ThreePointHitchLength = d1b; return; + case "vehicle.frontHitchLength": if (D(out var d1c)) veh.FrontHitchLength = d1c; return; case "vehicle.wheelbase": if (D(out var d2)) veh.Wheelbase = d2; return; case "vehicle.trackWidth": if (D(out var d3)) veh.TrackWidth = d3; return; case "vehicle.antennaPivot": if (D(out var d4)) veh.AntennaPivot = d4; return; @@ -152,7 +154,7 @@ bool IDX(out int i, out string rest) // "i,rest" — indexed array writes case "tool.type": // front/rear/tbt/trailing; a harvester only takes front (#111) tool.SetToolType(veh.Type == AgOpenWeb.Models.VehicleType.Harvester ? "front" : val); return; case "tool.hitchType": if (I(out var th)) tool.HitchType = th; return; - case "tool.hitchLength": if (D(out var t1)) tool.HitchLength = t1; return; + case "tool.workingPointFromHitch": if (D(out var t1)) tool.WorkingPointFromHitch = t1; return; case "tool.trailingHitchLength": if (D(out var t2)) tool.TrailingHitchLength = t2; return; case "tool.tankTrailingHitchLength": if (D(out var t3)) tool.TankTrailingHitchLength = t3; return; case "tool.length": if (D(out var t4)) tool.Length = t4; return; diff --git a/Shared/AgOpenWeb.Services/ConfigurationService.cs b/Shared/AgOpenWeb.Services/ConfigurationService.cs index 5bf31c975..e36fede73 100644 --- a/Shared/AgOpenWeb.Services/ConfigurationService.cs +++ b/Shared/AgOpenWeb.Services/ConfigurationService.cs @@ -157,6 +157,7 @@ public bool LoadProfiles(string vehicleName, string toolName) // Capture corruption status up-front (no mutation, no quarantine) so we // can record it for the startup recovery prompt after the load. var probe = ProbeProfiles(vehicleName, toolName); + Store.ProfileMigrationNote = null; // profileService.Load transparently recovers from the .bak // last-known-good copy when the primary v2 file is damaged. @@ -172,18 +173,11 @@ public bool LoadProfiles(string vehicleName, string toolName) // CreateDefaultProfile path) populated it. toolProfileService.Load(toolName, Store); - // One-way hitch migration (vehicle/tool config split): pre-split profiles stored - // the trailing/TBT tractor hitch-pin distance under Tool.HitchLength. It is now a - // vehicle property (Vehicle.HitchLength = axle -> hitch pin). When the vehicle file - // predates the split it loads as NaN (sentinel); seed it from the legacy tool value - // so existing trailing/TBT setups keep working, then it persists under the vehicle on - // next save. Rigid setups keep their own Tool.HitchLength regardless; a stray vehicle - // value is harmless because rigid geometry never reads it. Must run after BOTH files - // load (vehicle loads first), so the legacy Tool.HitchLength is available here. - if (double.IsNaN(Store.Vehicle.HitchLength)) - { - Store.Vehicle.HitchLength = Store.Tool.HitchLength; - } + // A vehicle file from before the hitch moved to the vehicle loads with no hitch (NaN); a + // format-2 tool loaded with it filled it in (HitchMigration). With no tool file to take it + // from, the default. + if (double.IsNaN(Store.Vehicle.HitchLength)) Store.Vehicle.HitchLength = 1.8; + if (double.IsNaN(Store.Vehicle.ThreePointHitchLength)) Store.Vehicle.ThreePointHitchLength = Store.Vehicle.HitchLength; LoadAutoSteerConfig(vehicleName); @@ -205,6 +199,12 @@ public bool LoadProfiles(string vehicleName, string toolName) { SaveProfiles(vehicleName, toolName); } + // A tool converted to the hitch on the vehicle (format 3, HitchMigration) is saved at once + // with its vehicle: a tool saved later without the vehicle's new hitch would move. + else if (Store.ProfileMigrationNote != null) + { + SaveProfiles(vehicleName, toolName); + } // Persist the active pair so the next startup restores the same // combo instead of falling through to LoadProfile(name) and pairing diff --git a/Shared/AgOpenWeb.Services/DebugDumpService.cs b/Shared/AgOpenWeb.Services/DebugDumpService.cs index 4ef713f5a..e1dede481 100644 --- a/Shared/AgOpenWeb.Services/DebugDumpService.cs +++ b/Shared/AgOpenWeb.Services/DebugDumpService.cs @@ -402,14 +402,20 @@ private static string BuildConfigSnapshot(ConfigurationStore store) store.Vehicle.AntennaHeight, store.Vehicle.AntennaPivot, store.Vehicle.AntennaOffset, - store.Vehicle.MaxSteerAngle + store.Vehicle.MaxSteerAngle, + store.Vehicle.HitchLength, + store.Vehicle.ThreePointHitchLength, + store.Vehicle.FrontHitchLength }, Tool = new { store.Tool.Width, store.Tool.Overlap, store.Tool.Offset, - store.Tool.HitchLength, + // The working point from the tractor's hitch (format 3, #547), and from the rear axle + // as format 2 had it (what older dumps carry as HitchLength). + store.Tool.WorkingPointFromHitch, + HitchLength = AgOpenWeb.Models.Tool.RigGeometry.AxleToWorkingPoint(store.Vehicle, store.Tool), store.Tool.TrailingHitchLength, // Where the working line is (#679: a placement report needs these, and they were missing). store.Tool.TrailingToolToPivotLength, diff --git a/Shared/AgOpenWeb.Services/Profile/AgOpenGpsProfileImport.cs b/Shared/AgOpenWeb.Services/Profile/AgOpenGpsProfileImport.cs index 30614320d..a58ba43f3 100644 --- a/Shared/AgOpenWeb.Services/Profile/AgOpenGpsProfileImport.cs +++ b/Shared/AgOpenWeb.Services/Profile/AgOpenGpsProfileImport.cs @@ -69,17 +69,20 @@ public static AgOpenGpsImport Run(string dataRoot, string vehiclesDirectory, ITo if (toolFiles.Count > 0 && TryRead(toolFiles[0]) is { } inUse) foreach (var key in GuidanceKeys) if (inUse.TryGetValue(key, out var value)) fromImplements[key] = value; - // The tractor's hitch pin: AgOpenGPS keeps it as a trailed implement's hitch length, - // AgOpenWeb with the tractor. Taken from the trailed implement saved last. + // The tractor's hitch points: AgOpenGPS keeps them as its implements' hitch lengths, + // AgOpenWeb with the tractor (#547). The drawbar pin from the trailed implement saved last; + // the rear 3-point and the front hitch from the rear- and front-mounted implements saved + // last, each fixed implement then its AgOpenGPS distance less that hitch (AgOpenGpsSettings). + var hitches = new Dictionary(); foreach (var file in toolFiles) { if (TryRead(file) is not { } tool || !tool.TryGetValue(HitchKey, out var hitch)) continue; - if (IsTrue(tool, "setTool_isToolTrailing") || IsTrue(tool, "setTool_isToolTBT")) - { - fromImplements[HitchKey] = hitch; - break; - } + string key = IsTrue(tool, "setTool_isToolTrailing") || IsTrue(tool, "setTool_isToolTBT") ? HitchKey + : IsTrue(tool, "setTool_isToolFront") ? AgOpenGpsSettings.FrontHitchKey + : AgOpenGpsSettings.ThreePointHitchKey; + hitches.TryAdd(key, hitch); } + foreach (var (k, v) in hitches) fromImplements[k] = v; var vehicles = new List(); var toolNames = new List(); @@ -121,7 +124,12 @@ public static AgOpenGpsImport Run(string dataRoot, string vehiclesDirectory, ITo try { var store = new ConfigurationStore(); - AgOpenGpsSettings.ApplyToStore(Merge(environment, own), name, file, store); + // The tractor's 3-point and front hitch go with each implement, so a fixed one's + // distance is from them (the drawbar is the implement's own hitch length here). + var tractorHitches = new Dictionary(); + foreach (var key in new[] { AgOpenGpsSettings.ThreePointHitchKey, AgOpenGpsSettings.FrontHitchKey }) + if (hitches.TryGetValue(key, out var h)) tractorHitches[key] = h; + AgOpenGpsSettings.ApplyToStore(Merge(environment, tractorHitches, own), name, file, store); tools.Save(name, store); } catch (Exception) diff --git a/Shared/AgOpenWeb.Services/Profile/AgOpenGpsSettings.cs b/Shared/AgOpenWeb.Services/Profile/AgOpenGpsSettings.cs index e1f688ab5..662318f68 100644 --- a/Shared/AgOpenWeb.Services/Profile/AgOpenGpsSettings.cs +++ b/Shared/AgOpenWeb.Services/Profile/AgOpenGpsSettings.cs @@ -43,6 +43,15 @@ public static Dictionary Parse(XDocument doc) /// active vehicle profile's path (an AgOpenGPS .xml: then /// keeps the AutoSteer settings read here instead of looking for its own AutoSteer file). /// + /// + /// Not AgOpenGPS's: the tractor's rear 3-point and front hitch, from the rear- and front-mounted + /// implements saved last (), so a fixed implement's distance + /// from its hitch keeps it where AgOpenGPS has it (#547). + /// + public const string ThreePointHitchKey = "agopenweb_threePointHitchLength"; + /// + public const string FrontHitchKey = "agopenweb_frontHitchLength"; + public static void ApplyToStore(IReadOnlyDictionary settings, string profileName, string filePath, ConfigurationStore store) { // Vehicle config @@ -84,14 +93,17 @@ public static void ApplyToStore(IReadOnlyDictionary settings, st store.Tool.Width = GetDouble(settings, "setVehicle_toolWidth", 6.0); store.Tool.Overlap = GetDouble(settings, "setVehicle_toolOverlap", 0.0); store.Tool.Offset = GetDouble(settings, "setVehicle_toolOffset", 0.0); - // AOG's single setVehicle_hitchLength is the tractor hitch pin (now a vehicle - // property, used by trailing/TBT) AND doubles as the rigid working-center distance. - // Import into both so the value is present whichever tool type the profile uses. - // AgOpenGPS writes it negative behind the tractor; AgOpenWeb keeps the distance (the - // tool's direction gives the side), and Tool.HitchLength took a negative one as 0. + // AgOpenGPS's one setVehicle_hitchLength, kept per implement: a trailed implement's is the + // tractor's drawbar pin, a fixed implement's is its working point from the rear axle (CTool: + // the working point is at the hitch). AgOpenWeb keeps the tractor's hitch points with the + // tractor and a fixed tool's distance from its hitch (#547). The import names the tractor's + // 3-point and front hitch (ThreePointHitchKey, FrontHitchKey: from the implements saved last); + // without them the hitch length is both, and a fixed tool's distance from it is 0: the tool + // where AgOpenGPS has it. AgOpenGPS writes it negative behind the tractor; AgOpenWeb keeps + // the distance (the hitch's side gives the direction). double xmlHitch = Math.Abs(GetDouble(settings, "setVehicle_hitchLength", 1.8)); store.Vehicle.HitchLength = xmlHitch; - store.Tool.HitchLength = xmlHitch; + store.Vehicle.ThreePointHitchLength = Math.Abs(GetDouble(settings, ThreePointHitchKey, xmlHitch)); // AgOpenGPS stores the trailing hitch negative (behind the hitch); AgOpenWeb keeps the // distance. 6.8.2+ names it setVehicle_… (ToolSettings, and SettingsLegacy for a combined // file); the combined file AgOpenGPS wrote up to 6.8.1 names it setTool_… (its Settings.cs). @@ -106,6 +118,9 @@ public static void ApplyToStore(IReadOnlyDictionary settings, st store.Tool.IsToolTBT = GetBool(settings, "setTool_isToolTBT", false); store.Tool.IsToolRearFixed = GetBool(settings, "setTool_isToolRearFixed", true); store.Tool.IsToolFrontFixed = GetBool(settings, "setTool_isToolFront", false); + store.Vehicle.FrontHitchLength = Math.Abs(GetDouble(settings, FrontHitchKey, store.Tool.IsToolFrontFixed ? xmlHitch : 0)); + store.Tool.WorkingPointFromHitch = xmlHitch - (store.Tool.IsToolFrontFixed + ? store.Vehicle.FrontHitchLength : store.Vehicle.ThreePointHitchLength); store.Tool.LookAheadOnSetting = GetDouble(settings, "setVehicle_toolLookAheadOn", 1.0); store.Tool.LookAheadOffSetting = GetDouble(settings, "setVehicle_toolLookAheadOff", 0.5); store.Tool.TurnOffDelay = GetDouble(settings, "setVehicle_toolOffDelay", 0.0); diff --git a/Shared/AgOpenWeb.Services/Profile/HitchMigration.cs b/Shared/AgOpenWeb.Services/Profile/HitchMigration.cs new file mode 100644 index 000000000..7ab3a2d33 --- /dev/null +++ b/Shared/AgOpenWeb.Services/Profile/HitchMigration.cs @@ -0,0 +1,83 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Globalization; +using System.IO; +using System.Text.Json; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Tool; + +namespace AgOpenWeb.Services.Profile; + +/// +/// Profile format 3 (#547, NonTracked/HITCH_MODEL_PLAN.md): the tractor's hitch points are the +/// vehicle's (drawbar, rear 3-point, front 3-point) and a fixed tool keeps only its distance from +/// its hitch, as ISO 11783-10 chains a device's connector and elements. Format 2 kept one number +/// per tool, the rear axle → working point. Reading a format-2 tool converts it against the vehicle +/// loaded with it, so the tool stays exactly where it was. +/// +public static class HitchMigration +{ + /// The profile format this build writes. + public const int CurrentFormatVersion = 3; + + /// + /// A format-2 tool's HitchLength (rear axle → working point, positive, ahead for a front + /// tool) as format 3: the tool's distance from the vehicle's hitch, worked out so the working + /// point stays where it was. The tool's type flags are already set. The vehicle's hitch is + /// filled in when it has none: a vehicle file from before the drawbar moved to the vehicle + /// (no hitch at all) takes the tool's value for its drawbar and 3-point, as that split did; a + /// front tool on a vehicle with no front hitch gives it the tool's value. Returns what was + /// done, for the log and the operator. + /// + public static string FromAxleToWorkingPoint(ConfigurationStore store, double axleToWorkingPoint) + { + var v = store.Vehicle; + var t = store.Tool; + double old = Math.Abs(axleToWorkingPoint); + string? note = null; + if (double.IsNaN(v.HitchLength)) v.HitchLength = old; + if (double.IsNaN(v.ThreePointHitchLength)) v.ThreePointHitchLength = v.HitchLength; + + if (RigGeometry.Mount(t) == ToolMount.FrontFixed) + { + if (v.FrontHitchLength <= 0) + { + v.FrontHitchLength = old; + note = string.Create(CultureInfo.InvariantCulture, + $"the front hitch is set to {old:0.00} m ahead of the rear axle, the tool's working point at it"); + } + t.WorkingPointFromHitch = old - v.FrontHitchLength; + } + else + { + t.WorkingPointFromHitch = old - v.ThreePointHitchLength; + } + return note ?? string.Create(CultureInfo.InvariantCulture, + $"the tool's working point is now measured from the tractor's hitch: {t.WorkingPointFromHitch:0.00} m"); + } + + /// + /// A profile file's formatVersion; 0 when it has none or cannot be read (no file, damaged). + /// + public static int FormatVersionOf(string path) + { + if (!File.Exists(path)) return 0; + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(path)); + if (doc.RootElement.ValueKind != JsonValueKind.Object) return 0; + foreach (var p in doc.RootElement.EnumerateObject()) + if (string.Equals(p.Name, "formatVersion", StringComparison.OrdinalIgnoreCase) + && p.Value.TryGetInt32(out int v)) + return v; + } + catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException) { } + return 0; + } + + /// A profile written by a newer build: this build leaves it as it is (as #825's fields and jobs). + public static bool IsNewerFormat(string path) => FormatVersionOf(path) > CurrentFormatVersion; +} diff --git a/Shared/AgOpenWeb.Services/Profile/ProfileJsonServiceV1.cs b/Shared/AgOpenWeb.Services/Profile/ProfileJsonServiceV1.cs index c26de546b..358273ff0 100644 --- a/Shared/AgOpenWeb.Services/Profile/ProfileJsonServiceV1.cs +++ b/Shared/AgOpenWeb.Services/Profile/ProfileJsonServiceV1.cs @@ -141,7 +141,7 @@ private static ProfileDto ToDto(string profileName, ConfigurationStore store) Width = store.Tool.Width, Overlap = store.Tool.Overlap, Offset = store.Tool.Offset, - HitchLength = store.Tool.HitchLength, + HitchLength = Models.Tool.RigGeometry.AxleToWorkingPoint(store.Vehicle, store.Tool), TrailingHitchLength = store.Tool.TrailingHitchLength, TankTrailingHitchLength = store.Tool.TankTrailingHitchLength, TrailingToolToPivotLength = store.Tool.TrailingToolToPivotLength, @@ -246,11 +246,13 @@ private static void ApplyDtoToStore(ProfileDto dto, string profileName, string f store.Tool.Width = dto.Tool?.Width ?? 6.0; store.Tool.Overlap = dto.Tool?.Overlap ?? 0.0; store.Tool.Offset = dto.Tool?.Offset ?? 0.0; - store.Tool.HitchLength = dto.Tool?.HitchLength ?? 1.8; - // v1 stored a single combined hitch length under Tool. After the vehicle/tool - // split the tractor hitch pin lives on the vehicle (used by trailing/TBT), so - // seed it from the same legacy value. Rigid tools keep reading Tool.HitchLength. - store.Vehicle.HitchLength = dto.Tool?.HitchLength ?? 1.8; + // v1 stored one hitch length under Tool: the tractor's hitch, and a fixed tool's working + // point at it. It is the vehicle's drawbar and 3-point; the tool's distance is converted + // after its type (below). + double v1Hitch = Math.Abs(dto.Tool?.HitchLength ?? 1.8); + store.Vehicle.HitchLength = v1Hitch; + store.Vehicle.ThreePointHitchLength = v1Hitch; + store.Vehicle.FrontHitchLength = 0; // Legacy profiles may have negative TrailingHitchLength from the old default; // migrate to "positive = behind hitch" by taking abs. store.Tool.TrailingHitchLength = Math.Abs(dto.Tool?.TrailingHitchLength ?? 2.5); @@ -260,6 +262,7 @@ private static void ApplyDtoToStore(ProfileDto dto, string profileName, string f store.Tool.IsToolTBT = dto.Tool?.IsToolTBT ?? false; store.Tool.IsToolRearFixed = dto.Tool?.IsToolRearFixed ?? true; store.Tool.IsToolFrontFixed = dto.Tool?.IsToolFrontFixed ?? false; + HitchMigration.FromAxleToWorkingPoint(store, v1Hitch); store.Tool.LookAheadOnSetting = dto.Tool?.LookAheadOn ?? Models.Configuration.ToolConfig.DefaultLookAheadOn; store.Tool.LookAheadOffSetting = dto.Tool?.LookAheadOff ?? Models.Configuration.ToolConfig.DefaultLookAheadOff; store.Tool.TurnOffDelay = dto.Tool?.TurnOffDelay ?? 0.0; diff --git a/Shared/AgOpenWeb.Services/Profile/ToolProfileJsonService.cs b/Shared/AgOpenWeb.Services/Profile/ToolProfileJsonService.cs index 90df6afc3..9e762a57d 100644 --- a/Shared/AgOpenWeb.Services/Profile/ToolProfileJsonService.cs +++ b/Shared/AgOpenWeb.Services/Profile/ToolProfileJsonService.cs @@ -38,8 +38,12 @@ public static void Save(string toolsDirectory, string profileName, Configuration if (!Directory.Exists(toolsDirectory)) Directory.CreateDirectory(toolsDirectory); + // A profile from a newer build is left as it is: this build would drop what it doesn't read. + var path = GetJsonPath(toolsDirectory, profileName); + if (HitchMigration.IsNewerFormat(path)) return; + // Atomic write + .bak last-known-good (see AtomicJsonFile). - AtomicJsonFile.WriteJson(GetJsonPath(toolsDirectory, profileName), ToDto(store), Options); + AtomicJsonFile.WriteJson(path, ToDto(store), Options); } public static bool Load(string toolsDirectory, string profileName, ConfigurationStore store) @@ -116,13 +120,15 @@ internal static ToolProfileDto ToDto(ConfigurationStore store) return new ToolProfileDto { - FormatVersion = 2, + FormatVersion = HitchMigration.CurrentFormatVersion, Tool = new ToolDto { Width = store.Tool.Width, Overlap = store.Tool.Overlap, Offset = store.Tool.Offset, - HitchLength = store.Tool.HitchLength, + // Format 2's axle → working point, with this vehicle: an older build reads it. + HitchLength = Models.Tool.RigGeometry.AxleToWorkingPoint(store.Vehicle, store.Tool), + WorkingPointFromHitch = store.Tool.WorkingPointFromHitch, Length = store.Tool.Length, TrailingHitchLength = store.Tool.TrailingHitchLength, TankTrailingHitchLength = store.Tool.TankTrailingHitchLength, @@ -201,7 +207,6 @@ internal static void ApplyDtoToStore(ToolProfileDto dto, string profileName, str store.Tool.Width = dto.Tool?.Width ?? 6.0; store.Tool.Overlap = dto.Tool?.Overlap ?? 0.0; store.Tool.Offset = dto.Tool?.Offset ?? 0.0; - store.Tool.HitchLength = dto.Tool?.HitchLength ?? 1.8; store.Tool.Length = dto.Tool?.Length ?? 0.0; // Legacy AOG sign convention used negative TrailingHitchLength; canonicalize. store.Tool.TrailingHitchLength = Math.Abs(dto.Tool?.TrailingHitchLength ?? 2.5); @@ -212,6 +217,12 @@ internal static void ApplyDtoToStore(ToolProfileDto dto, string profileName, str store.Tool.IsToolTBT = dto.Tool?.IsToolTBT ?? false; store.Tool.IsToolRearFixed = dto.Tool?.IsToolRearFixed ?? true; store.Tool.IsToolFrontFixed = dto.Tool?.IsToolFrontFixed ?? false; + // The working point from the tractor's hitch (format 3); a format-2 tool's axle → working + // point is converted against the vehicle loaded with it (HitchMigration), after the type. + if (dto.FormatVersion >= 3 && dto.Tool?.WorkingPointFromHitch is double fromHitch) + store.Tool.WorkingPointFromHitch = fromHitch; + else + store.ProfileMigrationNote = $"Tool '{profileName}': " + HitchMigration.FromAxleToWorkingPoint(store, dto.Tool?.HitchLength ?? 1.8); store.Tool.LookAheadOnSetting = dto.Tool?.LookAheadOn ?? Models.Configuration.ToolConfig.DefaultLookAheadOn; store.Tool.LookAheadOffSetting = dto.Tool?.LookAheadOff ?? Models.Configuration.ToolConfig.DefaultLookAheadOff; store.Tool.TurnOffDelay = dto.Tool?.TurnOffDelay ?? 0.0; @@ -328,7 +339,11 @@ internal class ToolDto public double Width { get; set; } public double Overlap { get; set; } public double Offset { get; set; } + /// Format 2: rear axle → working point. Format 3 still writes it (with the vehicle it + /// was saved with) for older builds; it reads . public double HitchLength { get; set; } + /// Format 3: the working point from the tractor's hitch, away from the tractor. + public double? WorkingPointFromHitch { get; set; } public double Length { get; set; } public double TrailingHitchLength { get; set; } public double TankTrailingHitchLength { get; set; } diff --git a/Shared/AgOpenWeb.Services/Profile/VehicleProfileJsonService.cs b/Shared/AgOpenWeb.Services/Profile/VehicleProfileJsonService.cs index fa21566c5..8133a412e 100644 --- a/Shared/AgOpenWeb.Services/Profile/VehicleProfileJsonService.cs +++ b/Shared/AgOpenWeb.Services/Profile/VehicleProfileJsonService.cs @@ -38,7 +38,10 @@ public static void Save(string vehiclesDirectory, string profileName, Configurat // Atomic write + .bak last-known-good (see AtomicJsonFile): a crash // mid-write can never leave a truncated profile that loads as defaults. - AtomicJsonFile.WriteJson(GetJsonPath(vehiclesDirectory, profileName), ToDto(store), Options); + var path = GetJsonPath(vehiclesDirectory, profileName); + // A profile from a newer build is left as it is: this build would drop what it doesn't read. + if (HitchMigration.IsNewerFormat(path)) return; + AtomicJsonFile.WriteJson(path, ToDto(store), Options); } public static bool Load(string vehiclesDirectory, string profileName, ConfigurationStore store) @@ -100,7 +103,7 @@ private static string GetJsonPath(string vehiclesDirectory, string profileName) internal static VehicleProfileDto ToDto(ConfigurationStore store) => new() { - FormatVersion = 2, + FormatVersion = HitchMigration.CurrentFormatVersion, Vehicle = new VehicleDto { AntennaHeight = store.Vehicle.AntennaHeight, @@ -115,6 +118,8 @@ private static string GetJsonPath(string vehiclesDirectory, string profileName) MaxSteerAngle = store.Vehicle.MaxSteerAngle, MaxAngularVelocity = store.Vehicle.MaxAngularVelocity, HitchLength = store.Vehicle.HitchLength, + ThreePointHitchLength = store.Vehicle.ThreePointHitchLength, + FrontHitchLength = store.Vehicle.FrontHitchLength, HitchType = store.Vehicle.HitchType, }, Guidance = new GuidanceDto @@ -180,6 +185,10 @@ internal static void ApplyDtoToStore(VehicleProfileDto dto, string profileName, // NaN sentinel = "not present in this file" → ConfigurationService.LoadProfiles // seeds it from the legacy Tool.HitchLength after the tool also loads. store.Vehicle.HitchLength = dto.Vehicle?.HitchLength ?? double.NaN; + // Format 3: the 3-point and front hitches. A format-2 vehicle had one hitch, the drawbar + // pin: its 3-point starts there (NaN, filled in with it when the vehicle file had none). + store.Vehicle.ThreePointHitchLength = dto.Vehicle?.ThreePointHitchLength ?? store.Vehicle.HitchLength; + store.Vehicle.FrontHitchLength = dto.Vehicle?.FrontHitchLength ?? 0.0; store.Vehicle.HitchType = dto.Vehicle?.HitchType ?? 0; // Guidance @@ -259,6 +268,10 @@ internal class VehicleDto // Nullable so a pre-split profile (no hitchLength) is detectable for one-way // migration from the legacy Tool.HitchLength (see ConfigurationService.LoadProfiles). public double? HitchLength { get; set; } + /// Format 3: the rear 3-point hitch (centre of the lower links) behind the axle. + public double? ThreePointHitchLength { get; set; } + /// Format 3: the front 3-point hitch ahead of the axle; 0 = none. + public double? FrontHitchLength { get; set; } public int HitchType { get; set; } } diff --git a/Shared/AgOpenWeb.Services/ToolProfileService.cs b/Shared/AgOpenWeb.Services/ToolProfileService.cs index 3a49310c3..1a4893982 100644 --- a/Shared/AgOpenWeb.Services/ToolProfileService.cs +++ b/Shared/AgOpenWeb.Services/ToolProfileService.cs @@ -109,7 +109,7 @@ public void CreateDefaultProfile(string profileName, ConfigurationStore store) store.Tool.Width = 6.0; store.Tool.Overlap = 0.0; store.Tool.Offset = 0.0; - store.Tool.HitchLength = 1.8; + store.Tool.WorkingPointFromHitch = 0.0; store.Tool.TrailingHitchLength = 2.5; store.Tool.TankTrailingHitchLength = 3.0; store.Tool.TrailingToolToPivotLength = 0.0; diff --git a/Shared/AgOpenWeb.Services/VehicleProfileService.cs b/Shared/AgOpenWeb.Services/VehicleProfileService.cs index 4f7099e65..b8ec4ff47 100644 --- a/Shared/AgOpenWeb.Services/VehicleProfileService.cs +++ b/Shared/AgOpenWeb.Services/VehicleProfileService.cs @@ -205,6 +205,8 @@ public void CreateDefaultProfile(string profileName, ConfigurationStore store) store.Vehicle.Wheelbase = 2.5; store.Vehicle.TrackWidth = 1.8; store.Vehicle.HitchLength = 1.8; + store.Vehicle.ThreePointHitchLength = 1.8; + store.Vehicle.FrontHitchLength = 0.0; store.Vehicle.MaxSteerAngle = 35.0; store.Vehicle.MaxAngularVelocity = 35.0; @@ -222,7 +224,7 @@ public void CreateDefaultProfile(string profileName, ConfigurationStore store) store.Tool.Width = 6.0; store.Tool.Overlap = 0.0; store.Tool.Offset = 0.0; - store.Tool.HitchLength = 1.8; + store.Tool.WorkingPointFromHitch = 0.0; store.Tool.TrailingHitchLength = 2.5; store.Tool.TankTrailingHitchLength = 3.0; store.Tool.TrailingToolToPivotLength = 0.0; diff --git a/Shared/AgOpenWeb.ViewModels/ConfigurationViewModel.cs b/Shared/AgOpenWeb.ViewModels/ConfigurationViewModel.cs index 3b96596ea..7bf5a1b86 100644 --- a/Shared/AgOpenWeb.ViewModels/ConfigurationViewModel.cs +++ b/Shared/AgOpenWeb.ViewModels/ConfigurationViewModel.cs @@ -1195,12 +1195,11 @@ private void InitializeToolEditCommands() v => Tool.Offset = v, "m", integerOnly: false, allowNegative: true, min: -5, max: 5)); - // Rigid tool (#2/#3): axle center -> implement working center (tiller/disc shaft). - // Tool-dependent; used only by front/rear-fixed tools. + // Rigid tool: its working point from the tractor's 3-point hitch (#547). EditToolHitchLengthCommand = new RelayCommand(() => - ShowNumericInput("Working Center Distance", Tool.HitchLength, - v => Tool.HitchLength = v, - "m", integerOnly: false, allowNegative: true, min: -15, max: 15)); + ShowNumericInput("Hitch to Working Point", Tool.WorkingPointFromHitch, + v => Tool.WorkingPointFromHitch = v, + "m", integerOnly: false, allowNegative: true, min: -10, max: 30)); EditToolLengthCommand = new RelayCommand(() => ShowNumericInput("Implement Length", Tool.Length, diff --git a/Shared/AgOpenWeb.ViewModels/MainViewModel.SettingsUnderAutoSteer.cs b/Shared/AgOpenWeb.ViewModels/MainViewModel.SettingsUnderAutoSteer.cs index a17a839b9..50a5287df 100644 --- a/Shared/AgOpenWeb.ViewModels/MainViewModel.SettingsUnderAutoSteer.cs +++ b/Shared/AgOpenWeb.ViewModels/MainViewModel.SettingsUnderAutoSteer.cs @@ -23,12 +23,13 @@ public partial class MainViewModel { nameof(VehicleConfig.Type), nameof(VehicleConfig.Wheelbase), nameof(VehicleConfig.AntennaHeight), nameof(VehicleConfig.AntennaPivot), nameof(VehicleConfig.AntennaOffset), nameof(VehicleConfig.HitchLength), + nameof(VehicleConfig.ThreePointHitchLength), nameof(VehicleConfig.FrontHitchLength), }; private static readonly HashSet ToolSteersBy = new(StringComparer.Ordinal) { nameof(ToolConfig.Width), nameof(ToolConfig.SectionWidths), nameof(ToolConfig.Overlap), nameof(ToolConfig.Offset), - nameof(ToolConfig.HitchLength), nameof(ToolConfig.TrailingHitchLength), nameof(ToolConfig.TankTrailingHitchLength), + nameof(ToolConfig.WorkingPointFromHitch), nameof(ToolConfig.TrailingHitchLength), nameof(ToolConfig.TankTrailingHitchLength), nameof(ToolConfig.TrailingToolToPivotLength), nameof(ToolConfig.CurrentToolType), }; @@ -71,9 +72,10 @@ private int SteeringGeometry() var s = ConfigStore; var v = s.Vehicle; var t = s.Tool; var h = new HashCode(); h.Add(v.Type); h.Add(v.Wheelbase); h.Add(v.AntennaHeight); h.Add(v.AntennaPivot); h.Add(v.AntennaOffset); h.Add(v.HitchLength); + h.Add(v.ThreePointHitchLength); h.Add(v.FrontHitchLength); h.Add(s.NumSections); for (int i = 0; i < s.NumSections; i++) h.Add(t.GetSectionWidth(i)); - h.Add(t.Width); h.Add(t.Overlap); h.Add(t.Offset); h.Add(t.HitchLength); h.Add(t.TrailingHitchLength); + h.Add(t.Width); h.Add(t.Overlap); h.Add(t.Offset); h.Add(t.WorkingPointFromHitch); h.Add(t.TrailingHitchLength); h.Add(t.TankTrailingHitchLength); h.Add(t.TrailingToolToPivotLength); h.Add(t.IsToolFrontFixed); h.Add(t.IsToolRearFixed); h.Add(t.IsToolTrailing); h.Add(t.IsToolTBT); h.Add(s.Connections.IsDualGps); h.Add(s.Connections.DualHeadingOffset); diff --git a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/ReporterSettings.cs b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/ReporterSettings.cs index 183926a36..061f8c220 100644 --- a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/ReporterSettings.cs +++ b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/ReporterSettings.cs @@ -30,7 +30,9 @@ private static readonly (string[] Path, string Key)[] Keys = (new[] { "Vehicle", "MaxSteerAngle" }, "autosteer.maxSteerAngle"), (new[] { "Tool", "Overlap" }, "tool.overlap"), (new[] { "Tool", "Offset" }, "tool.offset"), - (new[] { "Tool", "HitchLength" }, "tool.hitchLength"), + // A dump's Tool.HitchLength is a fixed tool's axle → working point (every build's dump + // carries it): applied as the tractor's 3-point and front hitch with the tool at them (#547). + (new[] { "Tool", "HitchLength" }, "vehicle.threePointHitchLength"), (new[] { "Tool", "TrailingHitchLength" }, "tool.trailingHitchLength"), (new[] { "Tool", "TrailingToolToPivotLength" }, "tool.trailingToolToPivotLength"), (new[] { "Tool", "TankTrailingHitchLength" }, "tool.tankTrailingHitchLength"), @@ -177,6 +179,13 @@ public static (List Commands, List> Writes, L }; if (text != null) cmds.Add(key + ":" + text); } + // The fixed tool's axle → working point went to the 3-point hitch above; the front hitch + // takes it too, and the tool sits at them (#547). + if (d.Num("Tool", "HitchLength") is double axleToWorking) + { + cmds.Add("vehicle.frontHitchLength:" + Math.Abs(axleToWorking).ToString("R", Inv)); + cmds.Add("tool.workingPointFromHitch:0"); + } cmds.Add(RollFilterOff); foreach (var (path, set) in Direct) { diff --git a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/TakeOver.cs b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/TakeOver.cs index 5408ea42f..3ad5ba478 100644 --- a/Tests/AgOpenWeb.DumpReplayScenarios.Tests/TakeOver.cs +++ b/Tests/AgOpenWeb.DumpReplayScenarios.Tests/TakeOver.cs @@ -75,10 +75,10 @@ public async Task DriveAsync() var store = rig.Store; var v = new VehicleSpec("reporter's vehicle", 0, Math.Max(1.0, store.Vehicle.Wheelbase), store.Vehicle.TrackWidth, store.Vehicle.AntennaHeight, - store.Vehicle.AntennaPivot, store.Vehicle.AntennaOffset, Math.Abs(store.Tool.HitchLength), Math.Max(10, store.Vehicle.MaxSteerAngle)); + store.Vehicle.AntennaPivot, store.Vehicle.AntennaOffset, Math.Abs(store.Vehicle.HitchLength), Math.Max(10, store.Vehicle.MaxSteerAngle)); string type = store.Tool.IsToolTBT ? "tbt" : store.Tool.IsToolTrailing ? "trailing" : store.Tool.IsToolFrontFixed ? "front" : "rear"; var t = new ToolSpec("reporter's implement", type, store.ActualToolWidth, Math.Max(1, store.NumSections), store.Tool.Offset, - type is "front" or "rear" ? Math.Abs(store.Tool.HitchLength) : Math.Abs(store.Tool.TrailingHitchLength), + type is "front" or "rear" ? AgOpenWeb.Models.Tool.RigGeometry.AxleToWorkingPoint(store.Vehicle, store.Tool) : Math.Abs(store.Tool.TrailingHitchLength), store.Tool.TrailingToolToPivotLength, Math.Abs(store.Tool.TankTrailingHitchLength), store.Tool.Overlap); Truth = new TruthMachine(v, t, 0); double speedKmh = Math.Clamp(Median(rows.Where(r => r.AutoSteer && r.Speed > 0.5).Select(r => r.Speed * 3.6).ToList()), 6, 12); diff --git a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Machines.cs b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Machines.cs index 3c2f33102..99c591f5e 100644 --- a/Tests/AgOpenWeb.FieldJobScenarios.Tests/Machines.cs +++ b/Tests/AgOpenWeb.FieldJobScenarios.Tests/Machines.cs @@ -92,7 +92,14 @@ public IEnumerable Settings() yield return "tool.type:" + Type; yield return "tool.numSections:" + Sections.ToString(i); for (int s = 0; s < Sections; s++) yield return "tool.sectionWidth:" + s.ToString(i) + "," + SectionCm(s).ToString("0.###", i); - if (Type is "front" or "rear") yield return "tool.hitchLength:" + HitchLength.ToString(i); + // A fixed implement's working point HitchLength from the rear axle: the tractor's 3-point + // (front hitch for a front implement) there and the implement at it (#547), as the + // AgOpenGPS import does. + if (Type is "front" or "rear") + { + yield return (Type == "front" ? "vehicle.frontHitchLength:" : "vehicle.threePointHitchLength:") + HitchLength.ToString(i); + yield return "tool.workingPointFromHitch:0"; + } else { yield return "tool.trailingHitchLength:" + HitchLength.ToString(i); diff --git a/Tests/AgOpenWeb.Models.Tests/ImplementSweptPathTests.cs b/Tests/AgOpenWeb.Models.Tests/ImplementSweptPathTests.cs index aa0bc8373..f89160fe0 100644 --- a/Tests/AgOpenWeb.Models.Tests/ImplementSweptPathTests.cs +++ b/Tests/AgOpenWeb.Models.Tests/ImplementSweptPathTests.cs @@ -21,7 +21,11 @@ private static ToolGeometry Geom(ToolMount mount, double width = 4.0, double off double vehicleHitch = 1.0, double toolHitch = 2.0, double trailingHitch = 3.0, double toolToPivot = 0.0, double tankHitch = 2.5, double length = 0.0) - => new(mount, width, offset, vehicleHitch, toolHitch, trailingHitch, toolToPivot, tankHitch, length); + => new(mount, width, offset, + // The attach point as the tool position has it: a fixed tool's working point + // (toolHitch from the axle), a trailed tool's drawbar pin (vehicleHitch). + mount == ToolMount.FrontFixed ? toolHitch : mount == ToolMount.RearFixed ? -toolHitch : -vehicleHitch, + trailingHitch, toolToPivot, tankHitch, length); /// Straight path heading north (E=0), points 0.5 m apart. private static List StraightNorth(int count = 40, double spacing = 0.5) diff --git a/Tests/AgOpenWeb.Models.Tests/TurnClearanceTests.cs b/Tests/AgOpenWeb.Models.Tests/TurnClearanceTests.cs index 0eeef6dca..e62a2bd0e 100644 --- a/Tests/AgOpenWeb.Models.Tests/TurnClearanceTests.cs +++ b/Tests/AgOpenWeb.Models.Tests/TurnClearanceTests.cs @@ -85,11 +85,11 @@ public void SweptFootprint_RearCornerPokingOut_IsNotClear() var path = new List(); for (int i = 0; i < 10; i++) path.Add(new Vec3(50, 95 + i * 0.2, 0)); // ends ~96.8, heading north var geom = new ToolGeometry(ToolMount.RearFixed, Width: 4, Offset: 0, - VehicleHitchLength: 0, ToolHitchLength: 0, TrailingHitchLength: 0, + AttachX: 0, TrailingHitchLength: 0, TrailingToolToPivotLength: 0, TankTrailingHitchLength: 0, Length: 0); // Front-fixed so the body extends NORTH (toward the top edge) past 100. - var frontGeom = geom with { Mount = ToolMount.FrontFixed, ToolHitchLength = 2, Length = 6 }; + var frontGeom = geom with { Mount = ToolMount.FrontFixed, AttachX = 2, Length = 6 }; var swept = ImplementSweptPath.Compute(path, frontGeom); var r = TurnClearance.Evaluate(swept, Square(), TurnClearance.KeepSide.Inside, margin: 0.5); diff --git a/Tests/AgOpenWeb.Models.Tests/VehicleToolLimitsTests.cs b/Tests/AgOpenWeb.Models.Tests/VehicleToolLimitsTests.cs index a079d7f2f..b5243580c 100644 --- a/Tests/AgOpenWeb.Models.Tests/VehicleToolLimitsTests.cs +++ b/Tests/AgOpenWeb.Models.Tests/VehicleToolLimitsTests.cs @@ -53,7 +53,9 @@ private static ConfigurationStore Sprayer() [TestCase("tool.offset", "250")] [TestCase("tool.overlap", "8")] [TestCase("tool.overlap", "20")] - [TestCase("tool.hitchLength", "150")] + [TestCase("tool.workingPointFromHitch", "150")] + [TestCase("vehicle.threePointHitchLength", "-1.5")] + [TestCase("vehicle.threePointHitchLength", "150")] [TestCase("tool.trailingHitchLength", "-4")] [TestCase("tool.trailingHitchLength", "0")] [TestCase("tool.tankTrailingHitchLength", "-3")] diff --git a/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs b/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs index afcdc2ca0..2e4ab43d0 100644 --- a/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/AgOpenGpsProfileImportTests.cs @@ -290,9 +290,14 @@ public void Run_ConvertsEveryTractorAndImplement_AndDeletesTheCopies() Assert.That(autoSteer["Case"].ProportionalGain, Is.EqualTo(120)); Assert.That(autoSteer["Case"].MinSteerSpeed, Is.EqualTo(1.2)); + // The mounted sprayer's 1.6 m is the tractor's rear 3-point now, the sprayer at it (#547): + // on the tractor, its working point where AgOpenGPS has it. + Assert.That(store.Vehicle.ThreePointHitchLength, Is.EqualTo(1.6), "the 3-point, from the mounted implement"); var sprayer = new ConfigurationStore(); + Assert.That(VehicleProfileJsonService.Load(vehicles, "Case", sprayer), Is.True); Assert.That(tools.Load("Sprayer", sprayer), Is.True); - Assert.That(sprayer.Tool.HitchLength, Is.EqualTo(1.6), "the distance, not 0"); + Assert.That(sprayer.Tool.WorkingPointFromHitch, Is.EqualTo(0).Within(1e-9)); + Assert.That(AgOpenWeb.Models.Tool.RigGeometry.AxleToWorkingPoint(sprayer.Vehicle, sprayer.Tool), Is.EqualTo(1.6).Within(1e-9), "the distance, not 0"); var tool = new ConfigurationStore(); Assert.That(tools.Load("Seeder", tool), Is.True); diff --git a/Tests/AgOpenWeb.Services.Tests/AsymmetricSectionTurnTests.cs b/Tests/AgOpenWeb.Services.Tests/AsymmetricSectionTurnTests.cs index 97928439c..cfc24376c 100644 --- a/Tests/AgOpenWeb.Services.Tests/AsymmetricSectionTurnTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/AsymmetricSectionTurnTests.cs @@ -70,7 +70,9 @@ public void SetUp() config.Tool.SetSectionWidth(1, 500); // 5m right (outer during left turn) // Fixed rear at rear axle (worst case for inner edge reversal) - config.Tool.HitchLength = 0; + // A fixed tool's working point 0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 0; config.Tool.IsToolRearFixed = true; config.Tool.IsToolTrailing = false; diff --git a/Tests/AgOpenWeb.Services.Tests/AutoSteerUTurnNUnitTests.cs b/Tests/AgOpenWeb.Services.Tests/AutoSteerUTurnNUnitTests.cs index fb8dec319..0eca53369 100644 --- a/Tests/AgOpenWeb.Services.Tests/AutoSteerUTurnNUnitTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/AutoSteerUTurnNUnitTests.cs @@ -75,7 +75,9 @@ public void SetUp() // speed limits (#106) have their own tests (SteerSpeedLimitTests). config.AutoSteer.MaxSteerSpeed = 0; config.Tool.Width = TOOL_WIDTH; // 6m - config.Tool.HitchLength = 3.0; + // A fixed tool's working point 3.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(3.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 3.0; config.Tool.IsToolTrailing = true; config.Tool.IsToolRearFixed = false; diff --git a/Tests/AgOpenWeb.Services.Tests/HitchModelTests.cs b/Tests/AgOpenWeb.Services.Tests/HitchModelTests.cs new file mode 100644 index 000000000..45e4804c0 --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/HitchModelTests.cs @@ -0,0 +1,279 @@ +// AgOpenWeb +// Copyright (C) 2024-2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using AgOpenWeb.Models; +using AgOpenWeb.Models.Base; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Services; +using AgOpenWeb.Services.Interfaces; +using AgOpenWeb.Services.Profile; +using AgOpenWeb.Services.Tool; +using Microsoft.Extensions.Logging.Abstractions; +using NSubstitute; + +namespace AgOpenWeb.Services.Tests; + +/// +/// The hitch model (#547, NonTracked/HITCH_MODEL_PLAN.md): the tractor's hitch points are the +/// vehicle's (drawbar, rear 3-point, front 3-point, from the rear axle) and a fixed tool keeps its +/// working point's distance from its hitch, as ISO 11783-10 chains a connector and a device +/// element. Profile format 3; a format-2 tool (one number, rear axle → working point) is converted +/// against the vehicle loaded with it, so it stays where it was. +/// +[TestFixture] +[NonParallelizable] // ConfigurationStore singleton + per-test temp dirs. +public class HitchModelTests +{ + private string _tempRoot = null!; + private string _vehiclesDir = null!; + private string _toolsDir = null!; + private TestVehicleProfileService _vehicleService = null!; + private TestToolProfileService _toolService = null!; + private ISettingsService _settingsService = null!; + private AppSettings _settings = null!; + private ConfigurationService _configService = null!; + + [SetUp] + public void SetUp() + { + _tempRoot = Path.Combine(Path.GetTempPath(), "AgOpenWeb-HitchModel-" + Guid.NewGuid().ToString("N")); + _vehiclesDir = Path.Combine(_tempRoot, "Vehicles"); + _toolsDir = Path.Combine(_tempRoot, "Tools"); + Directory.CreateDirectory(_vehiclesDir); + Directory.CreateDirectory(_toolsDir); + + _vehicleService = new TestVehicleProfileService(_vehiclesDir); + _toolService = new TestToolProfileService(_toolsDir); + + _settings = new AppSettings(); + _settingsService = Substitute.For(); + _settingsService.Settings.Returns(_settings); + + ConfigurationStore.SetInstance(new ConfigurationStore()); + + _configService = new ConfigurationService(_vehicleService, _toolService, _settingsService, ConfigurationStore.Instance); + } + + [TearDown] + public void TearDown() + { + try { Directory.Delete(_tempRoot, recursive: true); } catch { } + } + + // ---- Geometry: the tool hangs on the vehicle's hitch ---- + + private static ToolPositionService Placed(ConfigurationStore store) + { + var service = new ToolPositionService(store); + service.Update(new Vec3(0, 0, 0), 0); // heading 0 = +North + return service; + } + + // #547's case: the tractor's 3-point 1.2 m behind the axle, the tool's working point 0.8 m behind it. + [Test] + public void A_rear_tool_sits_at_the_tractors_hitch_plus_its_own_distance() + { + var store = ConfigurationStore.Instance; + store.Tool.SetToolType("rear"); + store.Vehicle.ThreePointHitchLength = 1.2; + store.Tool.WorkingPointFromHitch = 0.8; + store.Vehicle.HitchLength = 99.0; // the drawbar: not a rear-fixed tool's + + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(-2.0).Within(1e-9)); + + // Another tractor: only the vehicle's hitch changes, and the tool moves with it. + store.Vehicle.ThreePointHitchLength = 1.5; + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(-2.3).Within(1e-9)); + } + + [Test] + public void A_front_tool_sits_ahead_at_the_front_hitch_plus_its_own_distance() + { + var store = ConfigurationStore.Instance; + store.Tool.SetToolType("front"); + store.Vehicle.FrontHitchLength = 1.5; + store.Tool.WorkingPointFromHitch = 0.5; + store.Vehicle.ThreePointHitchLength = 99.0; + + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(2.0).Within(1e-9)); + } + + [Test] + public void A_working_point_on_the_tractors_side_of_the_hitch_is_a_negative_distance() + { + var store = ConfigurationStore.Instance; + store.Tool.SetToolType("rear"); + store.Vehicle.ThreePointHitchLength = 1.2; + store.Tool.WorkingPointFromHitch = -0.3; + + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(-0.9).Within(1e-9)); + } + + [TestCase("trailing")] + [TestCase("tbt")] + public void A_trailed_tool_hangs_on_the_drawbar(string type) + { + var store = ConfigurationStore.Instance; + store.Tool.SetToolType(type); + store.Vehicle.HitchLength = 3.0; + store.Vehicle.ThreePointHitchLength = 99.0; + store.Tool.WorkingPointFromHitch = 99.0; + + Assert.That(Placed(store).HitchPosition.Northing, Is.EqualTo(-3.0).Within(1e-9)); + } + + // ---- Format 2 → 3: the tool stays where it was ---- + + private void WriteV2Vehicle(string name, string vehicleJson) => + File.WriteAllText(Path.Combine(_vehiclesDir, name + ".json"), + "{ \"formatVersion\": 2, \"vehicle\": { \"wheelbase\": 2.5" + vehicleJson + " } }"); + + private void WriteV2Tool(string name, string type, double axleToWorkingPoint) => + File.WriteAllText(Path.Combine(_toolsDir, name + ".json"), + "{ \"formatVersion\": 2, \"tool\": { \"width\": 6, \"hitchLength\": " + + axleToWorkingPoint.ToString(System.Globalization.CultureInfo.InvariantCulture) + + ", \"isToolRearFixed\": " + (type == "rear" ? "true" : "false") + + ", \"isToolFrontFixed\": " + (type == "front" ? "true" : "false") + + ", \"isToolTrailing\": " + (type == "trailing" ? "true" : "false") + " } }"); + + [Test] + public void A_format_2_rear_tool_keeps_its_place_and_both_files_are_saved_as_format_3() + { + WriteV2Vehicle("Rig", ", \"hitchLength\": 1.1"); + WriteV2Tool("Rig", "rear", 2.0); + + Assert.That(_configService.LoadProfiles("Rig", "Rig"), Is.True); + var store = ConfigurationStore.Instance; + Assert.Multiple(() => + { + Assert.That(store.Vehicle.HitchLength, Is.EqualTo(1.1).Within(1e-9), "the drawbar as it was"); + Assert.That(store.Vehicle.ThreePointHitchLength, Is.EqualTo(1.1).Within(1e-9), "the 3-point starts at the one hitch"); + Assert.That(store.Tool.WorkingPointFromHitch, Is.EqualTo(0.9).Within(1e-9)); + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(-2.0).Within(1e-9), "where format 2 had it"); + Assert.That(store.ProfileMigrationNote, Does.Contain("0.90 m")); + Assert.That(HitchMigration.FormatVersionOf(Path.Combine(_vehiclesDir, "Rig.json")), Is.EqualTo(3), "saved at once"); + Assert.That(HitchMigration.FormatVersionOf(Path.Combine(_toolsDir, "Rig.json")), Is.EqualTo(3), "saved at once"); + }); + + // Read again (format 3 now): the same place, nothing converted. + ConfigurationStore.SetInstance(new ConfigurationStore()); + var again = new ConfigurationService(_vehicleService, _toolService, _settingsService, ConfigurationStore.Instance); + Assert.That(again.LoadProfiles("Rig", "Rig"), Is.True); + Assert.Multiple(() => + { + Assert.That(ConfigurationStore.Instance.ProfileMigrationNote, Is.Null); + Assert.That(Placed(ConfigurationStore.Instance).ToolPosition.Northing, Is.EqualTo(-2.0).Within(1e-9)); + }); + } + + [Test] + public void A_format_2_front_tool_gives_the_vehicle_its_front_hitch() + { + WriteV2Vehicle("Rig", ", \"hitchLength\": 1.1"); + WriteV2Tool("Rig", "front", 3.0); + + Assert.That(_configService.LoadProfiles("Rig", "Rig"), Is.True); + var store = ConfigurationStore.Instance; + Assert.Multiple(() => + { + Assert.That(store.Vehicle.FrontHitchLength, Is.EqualTo(3.0).Within(1e-9)); + Assert.That(store.Tool.WorkingPointFromHitch, Is.EqualTo(0.0).Within(1e-9)); + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(3.0).Within(1e-9), "where format 2 had it"); + Assert.That(store.ProfileMigrationNote, Does.Contain("front hitch")); + }); + } + + [Test] + public void A_vehicle_file_from_before_the_hitch_moved_takes_it_from_the_tool() + { + WriteV2Vehicle("Rig", ""); + WriteV2Tool("Rig", "trailing", 2.3); + + Assert.That(_configService.LoadProfiles("Rig", "Rig"), Is.True); + var store = ConfigurationStore.Instance; + Assert.Multiple(() => + { + Assert.That(store.Vehicle.HitchLength, Is.EqualTo(2.3).Within(1e-9)); + Assert.That(store.Vehicle.ThreePointHitchLength, Is.EqualTo(2.3).Within(1e-9)); + Assert.That(Placed(store).HitchPosition.Northing, Is.EqualTo(-2.3).Within(1e-9)); + }); + } + + [Test] + public void Format_3_round_trips_and_still_tells_an_older_build_where_the_tool_is() + { + var store = ConfigurationStore.Instance; + store.Tool.SetToolType("rear"); + store.Vehicle.HitchLength = 4.4; + store.Vehicle.ThreePointHitchLength = 1.2; + store.Vehicle.FrontHitchLength = 2.2; + store.Tool.WorkingPointFromHitch = 0.8; + _configService.SaveProfiles("Rig", "Rig"); + + var reload = new ConfigurationStore(); + Assert.That(VehicleProfileJsonService.Load(_vehiclesDir, "Rig", reload), Is.True); + Assert.That(ToolProfileJsonService.Load(_toolsDir, "Rig", reload), Is.True); + Assert.Multiple(() => + { + Assert.That(reload.Vehicle.HitchLength, Is.EqualTo(4.4).Within(1e-9)); + Assert.That(reload.Vehicle.ThreePointHitchLength, Is.EqualTo(1.2).Within(1e-9)); + Assert.That(reload.Vehicle.FrontHitchLength, Is.EqualTo(2.2).Within(1e-9)); + Assert.That(reload.Tool.WorkingPointFromHitch, Is.EqualTo(0.8).Within(1e-9)); + Assert.That(reload.ProfileMigrationNote, Is.Null); + }); + // An older build reads format 2's hitchLength: the axle → working point with this vehicle. + using var doc = System.Text.Json.JsonDocument.Parse(File.ReadAllText(Path.Combine(_toolsDir, "Rig.json"))); + Assert.That(doc.RootElement.GetProperty("tool").GetProperty("hitchLength").GetDouble(), Is.EqualTo(2.0).Within(1e-9)); + } + + [Test] + public void A_profile_from_a_newer_build_is_not_overwritten() + { + var path = Path.Combine(_toolsDir, "Rig.json"); + const string newer = "{ \"formatVersion\": 4, \"tool\": { \"width\": 6, \"somethingNew\": 1 } }"; + File.WriteAllText(path, newer); + ToolProfileJsonService.Save(_toolsDir, "Rig", ConfigurationStore.Instance); + Assert.That(File.ReadAllText(path), Is.EqualTo(newer)); + } + + // AgOpenGPS: one hitch length, the fixed tool's working point at it (CTool). The vehicle's hitches + // take it and the tool's distance is 0: the tool where AgOpenGPS has it. + [TestCase(false, -2.0)] + [TestCase(true, 2.0)] + public void An_AgOpenGPS_profile_puts_the_tool_where_AgOpenGPS_has_it(bool front, double y) + { + File.WriteAllText(Path.Combine(_vehiclesDir, "Legacy.XML"), + "" + + "" + + "-2.0" + + "" + (front ? "True" : "False") + "" + + "" + (front ? "False" : "True") + "" + + ""); + + var store = new ConfigurationStore(); + Assert.That(_vehicleService.Load("Legacy", store), Is.True); + Assert.Multiple(() => + { + Assert.That(store.Vehicle.HitchLength, Is.EqualTo(2.0).Within(1e-9)); + Assert.That(store.Vehicle.ThreePointHitchLength, Is.EqualTo(2.0).Within(1e-9)); + Assert.That(store.Vehicle.FrontHitchLength, Is.EqualTo(front ? 2.0 : 0.0).Within(1e-9)); + Assert.That(store.Tool.WorkingPointFromHitch, Is.EqualTo(0.0).Within(1e-9)); + Assert.That(Placed(store).ToolPosition.Northing, Is.EqualTo(y).Within(1e-9)); + }); + } + + private sealed class TestVehicleProfileService : VehicleProfileService + { + public TestVehicleProfileService(string dir) + : base(NullLogger.Instance, dir) { } + } + + private sealed class TestToolProfileService : ToolProfileService + { + public TestToolProfileService(string dir) + : base(NullLogger.Instance, dir) { } + } +} diff --git a/Tests/AgOpenWeb.Services.Tests/HitchSplitTests.cs b/Tests/AgOpenWeb.Services.Tests/HitchSplitTests.cs deleted file mode 100644 index 02777aa5b..000000000 --- a/Tests/AgOpenWeb.Services.Tests/HitchSplitTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -// AgOpenWeb -// Copyright (C) 2024-2026 AgOpenWeb Contributors -// SPDX-License-Identifier: Apache-2.0 - -using System; -using System.IO; -using AgOpenWeb.Models; -using AgOpenWeb.Models.Base; -using AgOpenWeb.Models.Configuration; -using AgOpenWeb.Services; -using AgOpenWeb.Services.Interfaces; -using AgOpenWeb.Services.Profile; -using AgOpenWeb.Services.Tool; -using Microsoft.Extensions.Logging.Abstractions; -using NSubstitute; - -namespace AgOpenWeb.Services.Tests; - -/// -/// Vehicle/Tool config split: the overloaded Tool.HitchLength was split into -/// Vehicle.HitchLength (#1, rear axle → tractor hitch pin, used by trailing/TBT) -/// and Tool.HitchLength (#2/#3, axle → rigid working center, used by fixed tools). -/// These tests pin the two halves: the geometry branch by tool type, and the -/// persistence/migration that seeds Vehicle.HitchLength on pre-split profiles. -/// -[TestFixture] -[NonParallelizable] // ConfigurationStore singleton + per-test temp dirs. -public class HitchSplitTests -{ - private string _tempRoot = null!; - private string _vehiclesDir = null!; - private string _toolsDir = null!; - private TestVehicleProfileService _vehicleService = null!; - private TestToolProfileService _toolService = null!; - private ISettingsService _settingsService = null!; - private AppSettings _settings = null!; - private ConfigurationService _configService = null!; - - [SetUp] - public void SetUp() - { - _tempRoot = Path.Combine(Path.GetTempPath(), "AgOpenWeb-HitchSplit-" + Guid.NewGuid().ToString("N")); - _vehiclesDir = Path.Combine(_tempRoot, "Vehicles"); - _toolsDir = Path.Combine(_tempRoot, "Tools"); - Directory.CreateDirectory(_vehiclesDir); - Directory.CreateDirectory(_toolsDir); - - _vehicleService = new TestVehicleProfileService(_vehiclesDir); - _toolService = new TestToolProfileService(_toolsDir); - - _settings = new AppSettings(); - _settingsService = Substitute.For(); - _settingsService.Settings.Returns(_settings); - - ConfigurationStore.SetInstance(new ConfigurationStore()); - - _configService = new ConfigurationService(_vehicleService, _toolService, _settingsService, ConfigurationStore.Instance); - } - - [TearDown] - public void TearDown() - { - try { Directory.Delete(_tempRoot, recursive: true); } catch { } - } - - // ---- Geometry: the hitch reference must branch by tool type ---- - - [Test] - public void Geometry_RigidTool_UsesToolHitchLength_IgnoresVehicle() - { - var store = ConfigurationStore.Instance; - store.Tool.IsToolRearFixed = true; - store.Tool.IsToolTrailing = false; - store.Tool.IsToolTBT = false; - store.Tool.IsToolFrontFixed = false; - store.Tool.Offset = 0; - store.Tool.HitchLength = 3.0; // rigid working center — should drive the hitch - store.Vehicle.HitchLength = 99.0; // vehicle pin — must be ignored for rigid - - var service = new ToolPositionService(store); - service.Update(new Vec3(0, 0, 0), 0); // heading 0 = +North - - // Rear tool → hitch is behind the pivot by Tool.HitchLength. - Assert.That(service.HitchPosition.Northing, Is.EqualTo(-3.0).Within(1e-6)); - Assert.That(service.HitchPosition.Easting, Is.EqualTo(0.0).Within(1e-6)); - } - - [Test] - public void Geometry_TrailingTool_UsesVehicleHitchLength_IgnoresTool() - { - var store = ConfigurationStore.Instance; - store.Tool.IsToolRearFixed = false; - store.Tool.IsToolTrailing = true; - store.Tool.IsToolTBT = false; - store.Tool.IsToolFrontFixed = false; - store.Tool.Offset = 0; - store.Tool.HitchLength = 99.0; // must be ignored for trailing - store.Vehicle.HitchLength = 3.0; // tractor hitch pin — should drive the hitch - - var service = new ToolPositionService(store); - service.Update(new Vec3(0, 0, 0), 0); - - // Trailing → hitch (the tractor pin) is behind the pivot by Vehicle.HitchLength. - Assert.That(service.HitchPosition.Northing, Is.EqualTo(-3.0).Within(1e-6)); - Assert.That(service.HitchPosition.Easting, Is.EqualTo(0.0).Within(1e-6)); - } - - // ---- Persistence + migration ---- - - [Test] - public void Migration_PreSplitVehicleFile_SeedsVehicleHitchFromTool() - { - // A pre-split v2 vehicle file with NO hitchLength, paired with a tool - // file that still carries the legacy hitch under Tool.HitchLength. - File.WriteAllText(Path.Combine(_vehiclesDir, "Combo.json"), - "{\n \"formatVersion\": 2,\n \"vehicle\": { \"wheelbase\": 2.5 }\n}"); - - var toolSource = new ConfigurationStore(); - toolSource.Tool.HitchLength = 2.3; - ToolProfileJsonService.Save(_toolsDir, "Combo", toolSource); - - Assert.That(_configService.LoadProfiles("Combo", "Combo"), Is.True); - Assert.That(ConfigurationStore.Instance.Vehicle.HitchLength, Is.EqualTo(2.3).Within(1e-6), - "Pre-split vehicle file (no hitchLength) should be seeded from the legacy Tool.HitchLength"); - } - - [Test] - public void Migration_VehicleFileWithHitch_NotClobberedByTool() - { - File.WriteAllText(Path.Combine(_vehiclesDir, "Combo.json"), - "{\n \"formatVersion\": 2,\n \"vehicle\": { \"wheelbase\": 2.5, \"hitchLength\": 1.1 }\n}"); - - var toolSource = new ConfigurationStore(); - toolSource.Tool.HitchLength = 9.9; - ToolProfileJsonService.Save(_toolsDir, "Combo", toolSource); - - Assert.That(_configService.LoadProfiles("Combo", "Combo"), Is.True); - Assert.That(ConfigurationStore.Instance.Vehicle.HitchLength, Is.EqualTo(1.1).Within(1e-6), - "An explicit vehicle hitchLength must win — no migration overwrite"); - } - - [Test] - public void RoundTrip_VehicleAndToolHitch_PersistIndependently() - { - var store = ConfigurationStore.Instance; - store.Vehicle.HitchLength = 4.4; - store.Tool.HitchLength = 6.6; - - _configService.SaveProfiles("Rig", "Rig"); - - var reload = new ConfigurationStore(); - Assert.That(VehicleProfileJsonService.Load(_vehiclesDir, "Rig", reload), Is.True); - Assert.That(ToolProfileJsonService.Load(_toolsDir, "Rig", reload), Is.True); - - Assert.That(reload.Vehicle.HitchLength, Is.EqualTo(4.4).Within(1e-6)); - Assert.That(reload.Tool.HitchLength, Is.EqualTo(6.6).Within(1e-6)); - } - - [Test] - public void XmlImport_VehicleHitchKey_PopulatesBothVehicleAndTool() - { - // AOG's single setVehicle_hitchLength is the tractor pin (vehicle) and - // doubles as the rigid working center (tool); import populates both. - File.WriteAllText(Path.Combine(_vehiclesDir, "Legacy.XML"), - "" + - "" + - "2.0" + - ""); - - var store = new ConfigurationStore(); - Assert.That(_vehicleService.Load("Legacy", store), Is.True); - Assert.That(store.Vehicle.HitchLength, Is.EqualTo(2.0).Within(1e-6)); - Assert.That(store.Tool.HitchLength, Is.EqualTo(2.0).Within(1e-6)); - } - - private sealed class TestVehicleProfileService : VehicleProfileService - { - public TestVehicleProfileService(string dir) - : base(NullLogger.Instance, dir) { } - } - - private sealed class TestToolProfileService : ToolProfileService - { - public TestToolProfileService(string dir) - : base(NullLogger.Instance, dir) { } - } -} diff --git a/Tests/AgOpenWeb.Services.Tests/LookAheadSlitTests.cs b/Tests/AgOpenWeb.Services.Tests/LookAheadSlitTests.cs index b954ebce0..3e3ab9d88 100644 --- a/Tests/AgOpenWeb.Services.Tests/LookAheadSlitTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/LookAheadSlitTests.cs @@ -73,7 +73,9 @@ private void SetUpPipeline(int numSections, double totalToolWidth, double sectionWidthCm = (totalToolWidth / numSections) * 100.0; for (int i = 0; i < numSections; i++) config.Tool.SetSectionWidth(i, (int)sectionWidthCm); - config.Tool.HitchLength = 0; + // A fixed tool's working point 0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 0; config.Tool.IsToolRearFixed = true; config.Tool.IsToolTrailing = false; diff --git a/Tests/AgOpenWeb.Services.Tests/OffsetFixTests.cs b/Tests/AgOpenWeb.Services.Tests/OffsetFixTests.cs index defdd1c4f..e160d7ada 100644 --- a/Tests/AgOpenWeb.Services.Tests/OffsetFixTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/OffsetFixTests.cs @@ -130,7 +130,9 @@ public void ToolPositionService_FollowsVehicle() // Configure a rear-fixed hitch at 2m behind var config = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance; - config.Tool.HitchLength = 2.0; + // A fixed tool's working point 2.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(2.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.IsToolRearFixed = true; // Vehicle at (100, 200) heading north (0 radians) @@ -156,7 +158,9 @@ public void ToolPositionService_ResetAfterLargeJump_SnapsImmediately() { var config = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance; config.Vehicle.HitchLength = 2.0; // trailing pin distance (axle -> tractor hitch pin) - config.Tool.HitchLength = 2.0; + // A fixed tool's working point 2.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(2.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 3.0; config.Tool.IsToolTrailing = true; config.Tool.IsToolRearFixed = false; @@ -202,7 +206,9 @@ public void ToolPositionService_ResetPlacesHitchBehindVehicle() // Bug: ResetTrailingState used +HitchLength (ahead) instead of -HitchLength (behind) var config = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance; config.Vehicle.HitchLength = 5.0; // trailing pin distance (axle -> tractor hitch pin) - config.Tool.HitchLength = 5.0; + // A fixed tool's working point 5.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(5.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 15.0; config.Tool.IsToolTrailing = true; config.Tool.IsToolRearFixed = false; @@ -234,7 +240,9 @@ public void ToolPositionService_ResetThenUpdateProducesConsistentPositions() // After reset + one Update frame, tool should be near vehicle var config = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance; config.Vehicle.HitchLength = 5.0; // trailing pin distance (axle -> tractor hitch pin) - config.Tool.HitchLength = 5.0; + // A fixed tool's working point 5.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(5.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 15.0; config.Tool.IsToolTrailing = true; config.Tool.IsToolRearFixed = false; @@ -272,7 +280,9 @@ public void ToolPositionService_TrailingStillWorksAtNormalSpeed() // causing the tool to snap rigid every frame instead of trailing var config = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance; config.Vehicle.HitchLength = 2.0; // trailing pin distance (axle -> tractor hitch pin) - config.Tool.HitchLength = 2.0; + // A fixed tool's working point 2.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(2.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.TrailingHitchLength = 15.0; // Large trailing distance config.Tool.IsToolTrailing = true; config.Tool.IsToolRearFixed = false; diff --git a/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs b/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs index 3c94e7d79..8894a80ef 100644 --- a/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Pipeline/ClosedPassSeamPipelineTests.cs @@ -60,7 +60,9 @@ public void SetUp() config.Tool.IsToolRearFixed = true; config.Tool.IsToolTrailing = false; config.Tool.IsToolTBT = false; - config.Tool.HitchLength = -1.5; + // A fixed tool's working point -1.5 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(-1.5); + config.Tool.WorkingPointFromHitch = 0; config.Guidance.UTurnRadius = 8; _gpsService = new GpsService(); diff --git a/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs b/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs index 74713fa43..abe0de2ec 100644 --- a/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Pipeline/HeadlandToggleTests.cs @@ -168,7 +168,9 @@ public void HeadlandDistanceHud_PastTheLineHeadingOut_KeepsTheLastDistance() /// The control loop's tool update (heading north, the tool on the pivot), then one GPS cycle. private void DriveTool(double e, double n) { - ConfigurationStore.Instance.Tool.HitchLength = 0; + // A fixed tool's working point 0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + ConfigurationStore.Instance.Vehicle.ThreePointHitchLength = ConfigurationStore.Instance.Vehicle.FrontHitchLength = System.Math.Abs(0); + ConfigurationStore.Instance.Tool.WorkingPointFromHitch = 0; _toolPosition.Update(new Vec3(e, n, 0), 0); Cycle(); } diff --git a/Tests/AgOpenWeb.Services.Tests/ProfileJsonServiceV1Tests.cs b/Tests/AgOpenWeb.Services.Tests/ProfileJsonServiceV1Tests.cs index aedf368b7..33bde913e 100644 --- a/Tests/AgOpenWeb.Services.Tests/ProfileJsonServiceV1Tests.cs +++ b/Tests/AgOpenWeb.Services.Tests/ProfileJsonServiceV1Tests.cs @@ -80,7 +80,8 @@ public void SaveAndLoad_ToolConfig_AllProperties() _store.Tool.Width = 12.0; _store.Tool.Overlap = 0.15; _store.Tool.Offset = -0.5; - _store.Tool.HitchLength = 2.5; + _store.Vehicle.ThreePointHitchLength = 2.5; // v1 kept one hitch length (#547) + _store.Tool.WorkingPointFromHitch = 0; _store.Tool.IsToolTrailing = true; _store.Tool.IsToolTBT = false; _store.NumSections = 4; @@ -94,7 +95,8 @@ public void SaveAndLoad_ToolConfig_AllProperties() Assert.That(loadStore.Tool.Width, Is.EqualTo(12.0).Within(1e-6)); Assert.That(loadStore.Tool.Overlap, Is.EqualTo(0.15).Within(1e-6)); Assert.That(loadStore.Tool.Offset, Is.EqualTo(-0.5).Within(1e-6)); - Assert.That(loadStore.Tool.HitchLength, Is.EqualTo(2.5).Within(1e-6)); + Assert.That(loadStore.Vehicle.HitchLength, Is.EqualTo(2.5).Within(1e-6), "v1's one hitch length: the drawbar"); + Assert.That(AgOpenWeb.Models.Tool.RigGeometry.AxleToWorkingPoint(loadStore.Vehicle, loadStore.Tool), Is.EqualTo(2.5).Within(1e-6)); Assert.That(loadStore.Tool.IsToolTrailing, Is.True); Assert.That(loadStore.Tool.IsToolTBT, Is.False); Assert.That(loadStore.NumSections, Is.EqualTo(4)); diff --git a/Tests/AgOpenWeb.Services.Tests/ProfileMigrationTests.cs b/Tests/AgOpenWeb.Services.Tests/ProfileMigrationTests.cs index 94cd35f7f..4d895e680 100644 --- a/Tests/AgOpenWeb.Services.Tests/ProfileMigrationTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/ProfileMigrationTests.cs @@ -108,9 +108,10 @@ public void V1Profile_OverwritesVehicleFileWithV2Schema() _configService.MigrateV1ProfilesIfNeeded(); - // The vehicle file should now have FormatVersion = 2 and no Tool block. + // The vehicle file should now be the current format (3 since the hitch moved to the vehicle, + // #547) with no Tool block. var json = File.ReadAllText(Path.Combine(_vehiclesDir, "MyTractor.json")); - Assert.That(json, Does.Contain("\"formatVersion\": 2")); + Assert.That(json, Does.Contain("\"formatVersion\": 3")); Assert.That(json, Does.Not.Contain("\"tool\":"), "v2 vehicle file must not carry the Tool block (it lives in Tools/.json)"); Assert.That(json, Does.Not.Contain("\"sections\":"), diff --git a/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs b/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs index 50705b4e7..faaa999c7 100644 --- a/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Tool/RigGeometryTests.cs @@ -32,13 +32,16 @@ private static IEnumerable Flags() => private static ToolConfig Tool(bool front, bool rear, bool trailing, bool tbt) => new() { IsToolFrontFixed = front, IsToolRearFixed = rear, IsToolTrailing = trailing, IsToolTBT = tbt, - HitchLength = 1.3, TrailingHitchLength = 2.5, TankTrailingHitchLength = 3.0, TrailingToolToPivotLength = 0.4, + WorkingPointFromHitch = 0, TrailingHitchLength = 2.5, TankTrailingHitchLength = 3.0, TrailingToolToPivotLength = 0.4, }; - // The tool position's hitch before RigGeometry (ToolPositionService.Update, develop 5babf3ad). + // The tool position's hitch before RigGeometry (ToolPositionService.Update, develop 5babf3ad), + // with a fixed tool's axle → working point 1.3 m (format 2's Tool.HitchLength). In the hitch + // model (#547) that is the vehicle's 3-point and front hitch at 1.3 m, the tool at them. + private const double OldToolHitch = 1.3; private static double OldAttachX(VehicleConfig v, ToolConfig t) { - double d = (t.IsToolFrontFixed || t.IsToolRearFixed) ? Math.Abs(t.HitchLength) : Math.Abs(v.HitchLength); + double d = (t.IsToolFrontFixed || t.IsToolRearFixed) ? OldToolHitch : Math.Abs(v.HitchLength); if (t.IsToolRearFixed || t.IsToolTrailing || t.IsToolTBT) d = -d; return d; } @@ -46,7 +49,7 @@ private static double OldAttachX(VehicleConfig v, ToolConfig t) [TestCaseSource(nameof(Flags))] public void The_hitch_point_is_where_the_tool_position_had_it(bool front, bool rear, bool trailing, bool tbt) { - var v = new VehicleConfig { HitchLength = 0.9 }; + var v = new VehicleConfig { HitchLength = 0.9, ThreePointHitchLength = OldToolHitch, FrontHitchLength = OldToolHitch }; var t = Tool(front, rear, trailing, tbt); if (!front && !rear && !trailing && !tbt) { @@ -64,7 +67,7 @@ public void The_hitch_point_is_where_the_tool_position_had_it(bool front, bool r if (!oldRigid) Assert.That(RigGeometry.Mount(t), Is.EqualTo(tbt ? ToolMount.TBT : ToolMount.Trailing)); // The swept path's geometry gives the same point. - Assert.That(RigGeometry.AttachX(RigGeometry.Geometry(v, t, 6)), Is.EqualTo(OldAttachX(v, t))); + Assert.That(RigGeometry.Geometry(v, t, 6).AttachX, Is.EqualTo(OldAttachX(v, t))); }); } @@ -77,7 +80,8 @@ public void The_tool_position_service_puts_the_hitch_there(string type, double x var store = new ConfigurationStore(); ConfigurationStore.SetInstance(store); store.Vehicle.HitchLength = 0.9; - store.Tool.HitchLength = 1.3; + store.Vehicle.ThreePointHitchLength = store.Vehicle.FrontHitchLength = 1.3; + store.Tool.WorkingPointFromHitch = 0; store.Tool.SetToolType(type); var svc = new ToolPositionService(store); svc.Update(new Vec3(10, 20, 0), 0); // heading north diff --git a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs index 60cddb489..f7f644135 100644 --- a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionMatchesAgOpenGpsTests.cs @@ -127,7 +127,9 @@ private static void Drive(string mount, double pivotDistance, double turnRadius var c = ConfigurationStore.Instance; c.Vehicle.AntennaPivot = 0; c.Vehicle.HitchLength = Hitch; - c.Tool.HitchLength = Hitch; + // A fixed tool's working point Hitch m from the rear axle: the tractor's hitch there, the tool on it (#547). + c.Vehicle.ThreePointHitchLength = c.Vehicle.FrontHitchLength = System.Math.Abs(Hitch); + c.Tool.WorkingPointFromHitch = 0; c.Tool.TankTrailingHitchLength = Tank; c.Tool.TrailingHitchLength = Trailing; c.Tool.TrailingToolToPivotLength = pivotDistance; diff --git a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionServiceConcurrencyTests.cs b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionServiceConcurrencyTests.cs index d6484083a..06e6d3b3b 100644 --- a/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionServiceConcurrencyTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/Tool/ToolPositionServiceConcurrencyTests.cs @@ -29,7 +29,8 @@ public void SetUp() ConfigurationStore.SetInstance(new ConfigurationStore()); var tool = ConfigurationStore.Instance.Tool; tool.Width = 6; - tool.HitchLength = 0; + tool.WorkingPointFromHitch = 0; // the rigid tool at the rear axle, as before #547 + ConfigurationStore.Instance.Vehicle.ThreePointHitchLength = 0; tool.IsToolRearFixed = true; tool.IsToolTrailing = false; tool.Offset = 0; diff --git a/Tests/AgOpenWeb.Services.Tests/ToolOffsetCoverageTests.cs b/Tests/AgOpenWeb.Services.Tests/ToolOffsetCoverageTests.cs index b29eff679..aa42ae549 100644 --- a/Tests/AgOpenWeb.Services.Tests/ToolOffsetCoverageTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/ToolOffsetCoverageTests.cs @@ -29,7 +29,9 @@ public void SetUp() { ConfigurationStore.SetInstance(new ConfigurationStore()); _config = ConfigurationStore.Instance; - _config.Tool.HitchLength = 4; + // A fixed tool's working point 4 m from the rear axle: the tractor's hitch there, the tool on it (#547). + _config.Vehicle.ThreePointHitchLength = _config.Vehicle.FrontHitchLength = System.Math.Abs(4); + _config.Tool.WorkingPointFromHitch = 0; _config.Vehicle.HitchLength = 2; _config.Tool.TrailingHitchLength = 3; _config.Tool.Offset = 0.8; // tool 0.8 m right of the tractor diff --git a/Tests/AgOpenWeb.Services.Tests/TractorPathTests.cs b/Tests/AgOpenWeb.Services.Tests/TractorPathTests.cs index b53b1fdd5..03c4770ef 100644 --- a/Tests/AgOpenWeb.Services.Tests/TractorPathTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/TractorPathTests.cs @@ -414,7 +414,9 @@ private List DriveWithImplement( bool trailing = true, bool fixedRear = false) { var config = ConfigurationStore.Instance; - config.Tool.HitchLength = 3.0; + // A fixed tool's working point 3.0 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(3.0); + config.Tool.WorkingPointFromHitch = 0; config.Tool.Width = 6.0; config.Tool.IsToolTrailing = trailing; config.Tool.IsToolRearFixed = fixedRear; diff --git a/Tests/AgOpenWeb.Services.Tests/TramOffSendsNothingTests.cs b/Tests/AgOpenWeb.Services.Tests/TramOffSendsNothingTests.cs index 13f921e39..d6766bc0e 100644 --- a/Tests/AgOpenWeb.Services.Tests/TramOffSendsNothingTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/TramOffSendsNothingTests.cs @@ -49,7 +49,9 @@ public void SetUp() _config.Vehicle.TrackWidth = 1.8; _config.Tool.IsToolRearFixed = true; _config.Tool.IsToolTrailing = _config.Tool.IsToolTBT = _config.Tool.IsToolFrontFixed = false; - _config.Tool.HitchLength = HitchLength; + // A fixed tool's working point HitchLength m from the rear axle: the tractor's hitch there, the tool on it (#547). + _config.Vehicle.ThreePointHitchLength = _config.Vehicle.FrontHitchLength = System.Math.Abs(HitchLength); + _config.Tool.WorkingPointFromHitch = 0; _config.Tool.Offset = 0; _config.Tool.Width = 6; _config.NumSections = 1; diff --git a/Tests/AgOpenWeb.Services.Tests/TramTriggerAtToolTests.cs b/Tests/AgOpenWeb.Services.Tests/TramTriggerAtToolTests.cs index 47b088775..f302d1780 100644 --- a/Tests/AgOpenWeb.Services.Tests/TramTriggerAtToolTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/TramTriggerAtToolTests.cs @@ -44,7 +44,9 @@ public void SetUp() _config.Vehicle.TrackWidth = 1.8; _config.Tool.IsToolRearFixed = true; _config.Tool.IsToolTrailing = _config.Tool.IsToolTBT = _config.Tool.IsToolFrontFixed = false; - _config.Tool.HitchLength = HitchLength; + // A fixed tool's working point HitchLength m from the rear axle: the tractor's hitch there, the tool on it (#547). + _config.Vehicle.ThreePointHitchLength = _config.Vehicle.FrontHitchLength = System.Math.Abs(HitchLength); + _config.Tool.WorkingPointFromHitch = 0; _config.Tool.TrailingHitchLength = TrailingArm; _config.Tool.Offset = 0; _config.Tool.Width = 6; diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/AutoTurnHardBoundaryTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/AutoTurnHardBoundaryTests.cs index 4bf1acac7..8a8d584aa 100644 --- a/Tests/AgOpenWeb.Services.Tests/YouTurn/AutoTurnHardBoundaryTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/AutoTurnHardBoundaryTests.cs @@ -38,7 +38,9 @@ public void SetUp() // the implement, letting a 16 m boom hit the fence. c.Tool.Width = 6; c.Tool.Overlap = 0; - c.Tool.HitchLength = 4.5; // working-centre distance + // A fixed tool's working point 4.5 m from the rear axle: the tractor's hitch there, the tool on it (#547). + c.Vehicle.ThreePointHitchLength = c.Vehicle.FrontHitchLength = System.Math.Abs(4.5); + c.Tool.WorkingPointFromHitch = 0; // working-centre distance c.Tool.Length = 3.5; // overall implement length c.Tool.IsToolRearFixed = true; // 3-point mounted c.Tool.IsToolTrailing = false; @@ -88,8 +90,7 @@ private YouTurnCreationService.TurnPathResult Run(Boundary boundary) private static ToolGeometry Rig() => new( ToolMount.RearFixed, Width: 16, Offset: 0, - VehicleHitchLength: ConfigurationStore.Instance.Vehicle.HitchLength, - ToolHitchLength: 4.5, TrailingHitchLength: 0, TrailingToolToPivotLength: 0, + AttachX: -4.5, TrailingHitchLength: 0, TrailingToolToPivotLength: 0, TankTrailingHitchLength: 0, Length: 3.5); [Test] diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnHardBoundaryTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnHardBoundaryTests.cs index 2fdda459d..0af3ca627 100644 --- a/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnHardBoundaryTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnHardBoundaryTests.cs @@ -36,7 +36,9 @@ public void SetUp() config.NumSections = 1; config.Tool.SetSectionWidth(0, 1600); // 16 m via sections -> ActualToolWidth = 16 config.Tool.Overlap = 0; - config.Tool.HitchLength = 2; + // A fixed tool's working point 2 m from the rear axle: the tractor's hitch there, the tool on it (#547). + config.Vehicle.ThreePointHitchLength = config.Vehicle.FrontHitchLength = System.Math.Abs(2); + config.Tool.WorkingPointFromHitch = 0; config.Tool.IsToolRearFixed = true; config.Tool.IsToolTrailing = false; config.Tool.IsToolTBT = false; diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnRadiusTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnRadiusTests.cs index 2263d3373..17aa9eaed 100644 --- a/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnRadiusTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/ManualTurnRadiusTests.cs @@ -36,7 +36,9 @@ public void SetUp() _config = ConfigurationStore.Instance; _config.NumSections = 1; _config.Tool.Overlap = 0; - _config.Tool.HitchLength = 2; + // A fixed tool's working point 2 m from the rear axle: the tractor's hitch there, the tool on it (#547). + _config.Vehicle.ThreePointHitchLength = _config.Vehicle.FrontHitchLength = System.Math.Abs(2); + _config.Tool.WorkingPointFromHitch = 0; _config.Tool.IsToolRearFixed = true; _config.Tool.IsToolTrailing = false; _config.Tool.IsToolTBT = false; diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnReferenceAheadTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnReferenceAheadTests.cs index 372ab23cb..f663c6fc1 100644 --- a/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnReferenceAheadTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnReferenceAheadTests.cs @@ -72,7 +72,9 @@ public void A_turn_seated_back_behind_the_tractor_to_clear_a_hard_boundary_is_re var c = ConfigurationStore.Instance; c.Tool.Width = 6; c.Tool.Overlap = 0; - c.Tool.HitchLength = 4.5; + // A fixed tool's working point 4.5 m from the rear axle: the tractor's hitch there, the tool on it (#547). + c.Vehicle.ThreePointHitchLength = c.Vehicle.FrontHitchLength = System.Math.Abs(4.5); + c.Tool.WorkingPointFromHitch = 0; c.Tool.Length = 3.5; c.Tool.IsToolRearFixed = true; c.Tool.IsToolTrailing = false; diff --git a/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs index 386c059d8..125e1b585 100644 --- a/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs +++ b/Tests/AgOpenWeb.Services.Tests/YouTurn/TurnSetbackStaysAtEndTests.cs @@ -46,7 +46,9 @@ private static YouTurnCreationService NewCreation(bool front) c.Tool.Width = width; c.Tool.Overlap = 0; c.Tool.Offset = 0; - c.Tool.HitchLength = front ? 4.5 : 1.5; + // A fixed tool's working point front ? 4.5 : 1.5 m from the rear axle: the tractor's hitch there, the tool on it (#547). + c.Vehicle.ThreePointHitchLength = c.Vehicle.FrontHitchLength = System.Math.Abs(front ? 4.5 : 1.5); + c.Tool.WorkingPointFromHitch = 0; c.Tool.IsToolFrontFixed = front; c.Tool.IsToolRearFixed = !front; c.Tool.IsToolTrailing = false; diff --git a/Tests/AgOpenWeb.UpdateScenarios.Tests/Farm.cs b/Tests/AgOpenWeb.UpdateScenarios.Tests/Farm.cs index 0bbff9d5f..7e139a13d 100644 --- a/Tests/AgOpenWeb.UpdateScenarios.Tests/Farm.cs +++ b/Tests/AgOpenWeb.UpdateScenarios.Tests/Farm.cs @@ -251,6 +251,23 @@ public static class FarmBuilder { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; + /// + /// A setting as the build it goes to names it. A fixed implement's distance was the rear axle → + /// working point (tool.hitchLength) up to profile format 2; this build keeps it from the + /// tractor's 3-point hitch (#547), so the same place is the distance less the hitch of the + /// vehicle in use. The readback (Tool.HitchLength in the dump) is the axle → working point on + /// every build. + /// + private static string ThisBuildKey(ICommandSender s, Setting st) + { + if (s is RigSender rig && st.Key == "tool.hitchLength") + { + double fromHitch = double.Parse(st.Value, Inv) - rig.Rig.Store.Vehicle.ThreePointHitchLength; + return "tool.workingPointFromHitch:" + fromHitch.ToString(Inv); + } + return st.Key + ":" + st.Value; + } + public static async Task BuildAsync(ICommandSender s, Farm farm, Scenario sc, bool slow) { // Settle a fresh install: the simulator on, standing still. @@ -272,7 +289,7 @@ public static async Task BuildAsync(ICommandSender s, Farm farm, Scenario sc, bo { await s.SendAsync("profile.new", "tool\t" + t.Name); await s.PauseAsync(slow ? 1.0 : 0); - foreach (var st in t.Settings) await s.SendAsync("config.set", st.Key + ":" + st.Value); + foreach (var st in t.Settings) await s.SendAsync("config.set", ThisBuildKey(s, st)); await s.SendAsync("profile.save"); await s.PauseAsync(slow ? 1.0 : 0); sc.Step(Kit.F($"implement profile '{t.Name}' made with {t.Settings.Count} settings and saved")); diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/CarelessOperator.cs b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/CarelessOperator.cs index a8e25bc85..4962cb84d 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/CarelessOperator.cs +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/CarelessOperator.cs @@ -77,13 +77,13 @@ private static IEnumerable Drives() yield return new("T-OV8", "Overlap typed as wide as the boom (8 m)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.overlap:8"), Impossible: true); yield return new("T-OV10", "Overlap typed wider than the boom (10 m)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.overlap:10"), Impossible: true); yield return new("T-OVCM", "Overlap typed in cm (20 in the metre box)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.overlap:20"), Impossible: true); - yield return new("T-RIG0", "Boom distance behind the axle left at 0 (really 1.5 m)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.hitchLength:0")); - yield return new("T-RIGCM", "Boom distance typed in cm (150)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.hitchLength:150"), Impossible: true); + yield return new("T-RIG0", "Boom distance behind the axle left at 0 (really 1.5 m)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "vehicle.threePointHitchLength:0")); + yield return new("T-RIGCM", "Boom distance typed in cm (150)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "vehicle.threePointHitchLength:150"), Impossible: true); yield return new("T-TRAILNEG", "Trailed sprayer's drawbar typed negative (-4 m)", Puma, Machines.Sprayer10, Um, 0, W(Puma, Machines.Sprayer10, Um, "tool.trailingHitchLength:-4"), Impossible: true); yield return new("T-TRAIL0", "Trailed sprayer's drawbar left at 0", Puma, Machines.Sprayer10, Um, 0, W(Puma, Machines.Sprayer10, Um, "tool.trailingHitchLength:0"), Impossible: true); yield return new("T-TYPEXYZ", "Implement type that does not exist ('xyz')", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.type:xyz"), Impossible: true); yield return new("T-TYPERIGID", "Trailed sprayer set as mounted (rear, 4 m behind)", Puma, Machines.Sprayer10, Um, 0, - W(Puma, Machines.Sprayer10, Um, "tool.type:rear", "tool.hitchLength:4")); + W(Puma, Machines.Sprayer10, Um, "tool.type:rear", "vehicle.threePointHitchLength:4", "tool.workingPointFromHitch:0")); yield return new("T-TBTASTRAIL", "TBT seed drill set as a plain trailed one", Machines.Big, Machines.Tbt12, Machines.BigSys, 0, W(Machines.Big, Machines.Tbt12, Machines.BigSys, "tool.type:trailing")); yield return new("T-SLOWCUT", "Slow speed cut-off typed 100 km/h (work speed 9)", Puma, Boom, Um, 0, W(Puma, Boom, Um, "tool.slowSpeedCutoff:100"), Impossible: true); @@ -223,8 +223,8 @@ private static IEnumerable BadSettings() yield return new("Implement", "Overlap = width", "tool.overlap:8"); yield return new("Implement", "Overlap > width", "tool.overlap:10"); yield return new("Implement", "Gap wider than the implement", "tool.overlap:-10"); - yield return new("Implement", "Mounted distance negative", "tool.hitchLength:-1.5"); - yield return new("Implement", "Mounted distance in cm", "tool.hitchLength:150"); + yield return new("Implement", "Mounted distance negative", "vehicle.threePointHitchLength:-1.5"); + yield return new("Implement", "Mounted distance in cm", "vehicle.threePointHitchLength:150"); yield return new("Implement", "Drawbar negative", "tool.trailingHitchLength:-4"); yield return new("Implement", "Drawbar 0", "tool.trailingHitchLength:0"); yield return new("Implement", "TBT tank hitch negative", "tool.tankTrailingHitchLength:-3"); diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Machines.cs b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Machines.cs index caa9cdd87..9a6fb9957 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Machines.cs +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Machines.cs @@ -65,7 +65,14 @@ public IEnumerable Settings() if (SectionWidthCm is >= 10 and <= 1000) yield return "tool.defaultSectionWidth:" + SectionWidthCm.ToString("0.###", i); yield return "tool.numSections:" + Sections.ToString(i); for (int s = 0; s < Sections; s++) yield return "tool.sectionWidth:" + s.ToString(i) + "," + SectionWidthCm.ToString("0.###", i); - if (Type is "front" or "rear") yield return "tool.hitchLength:" + HitchLength.ToString(i); + // A fixed implement's working point HitchLength from the rear axle: the tractor's 3-point + // (front hitch for a front implement) there and the implement at it (#547), as the + // AgOpenGPS import does. + if (Type is "front" or "rear") + { + yield return (Type == "front" ? "vehicle.frontHitchLength:" : "vehicle.threePointHitchLength:") + HitchLength.ToString(i); + yield return "tool.workingPointFromHitch:0"; + } else { yield return "tool.trailingHitchLength:" + HitchLength.ToString(i); diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Readback.cs b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Readback.cs index 30ffce9e4..087acedbb 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Readback.cs +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/Readback.cs @@ -29,6 +29,8 @@ public static string Get(ConfigurationStore s, string key) "vehicle.antennaPivot" => D(v.AntennaPivot), "vehicle.antennaOffset" => D(v.AntennaOffset), "vehicle.hitchLength" => D(v.HitchLength), + "vehicle.threePointHitchLength" => D(v.ThreePointHitchLength), + "vehicle.frontHitchLength" => D(v.FrontHitchLength), "autosteer.maxSteerAngle" => D(v.MaxSteerAngle), "gps.isDualGps" => c.IsDualGps ? "1" : "0", "gps.dualHeadingOffset" => D(c.DualHeadingOffset), @@ -37,7 +39,7 @@ public static string Get(ConfigurationStore s, string key) "roll.rollFilter" => D(a.RollFilter), "tool.type" => t.IsToolFrontFixed ? "front" : t.IsToolRearFixed ? "rear" : t.IsToolTBT ? "tbt" : t.IsToolTrailing ? "trailing" : "none", "tool.numSections" => s.NumSections.ToString(i), - "tool.hitchLength" => D(t.HitchLength), + "tool.workingPointFromHitch" => D(t.WorkingPointFromHitch), "tool.trailingHitchLength" => D(t.TrailingHitchLength), "tool.trailingToolToPivotLength" => D(t.TrailingToolToPivotLength), "tool.tankTrailingHitchLength" => D(t.TankTrailingHitchLength), diff --git a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/SettingsSweep.cs b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/SettingsSweep.cs index 9b9a5154d..ff6275250 100644 --- a/Tests/AgOpenWeb.VehicleToolScenarios.Tests/SettingsSweep.cs +++ b/Tests/AgOpenWeb.VehicleToolScenarios.Tests/SettingsSweep.cs @@ -172,7 +172,8 @@ public static class SettingsSweep // flip would be set back to 0 and try nothing. new("tool.lookAheadOff", "0", "1.5", Probe.Sections, Effect.Sections, "Look-ahead off, s", "tool.lookAheadOn:2"), new("tool.turnOffDelay", "0", "2", Probe.Sections, Effect.Sections, "Turn-off delay, s"), - new("tool.hitchLength", "1.5", "4", Probe.Sections, Effect.Sections, "Hitch length (mounted)"), + new("vehicle.threePointHitchLength", "1.5", "4", Probe.Sections, Effect.Sections, "Rear 3-point hitch (mounted)"), + new("tool.workingPointFromHitch", "0", "2.5", Probe.Sections, Effect.Sections, "Hitch to working point (mounted)"), // AgOpenGPS uses the vehicle type for the picture and, for a combine, a header in front only. new("vehicle.type", "0", "1", Probe.Sections, Effect.Sections, "Vehicle type (tractor → combine: header in front)"), new("tool.type", "rear", "front", Probe.Sections, Effect.Sections, "Implement type (rear → front)"), diff --git a/Tests/AgOpenWeb.ViewModels.Tests/AutoSteerOffWhenSettingsChangeTests.cs b/Tests/AgOpenWeb.ViewModels.Tests/AutoSteerOffWhenSettingsChangeTests.cs index db605252c..6831623ee 100644 --- a/Tests/AgOpenWeb.ViewModels.Tests/AutoSteerOffWhenSettingsChangeTests.cs +++ b/Tests/AgOpenWeb.ViewModels.Tests/AutoSteerOffWhenSettingsChangeTests.cs @@ -44,7 +44,8 @@ private static IEnumerable SteeringChanges() yield return new TestCaseData((Action)(s => s.Tool.SetSectionWidth(3, 150)), "implement").SetName("A section width"); yield return new TestCaseData((Action)(s => s.Tool.Offset = 2.5), "implement").SetName("Implement offset"); yield return new TestCaseData((Action)(s => s.Tool.Overlap = 0.2), "implement").SetName("Overlap"); - yield return new TestCaseData((Action)(s => s.Tool.HitchLength = 3), "implement").SetName("Implement hitch"); + yield return new TestCaseData((Action)(s => s.Tool.WorkingPointFromHitch = 3), "implement").SetName("Implement hitch"); + yield return new TestCaseData((Action)(s => s.Vehicle.ThreePointHitchLength = 0.7), "vehicle").SetName("Rear 3-point hitch"); yield return new TestCaseData((Action)(s => s.Vehicle.AntennaOffset = 0.25), "vehicle").SetName("Antenna offset"); yield return new TestCaseData((Action)(s => s.Vehicle.AntennaHeight = 1), "vehicle").SetName("Antenna height"); yield return new TestCaseData((Action)(s => s.Vehicle.Wheelbase = 3.5), "vehicle").SetName("Wheelbase"); diff --git a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/Machines.cs b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/Machines.cs index 3bfdbc960..88afc716d 100644 --- a/Tests/AgOpenWeb.WorkingDayScenarios.Tests/Machines.cs +++ b/Tests/AgOpenWeb.WorkingDayScenarios.Tests/Machines.cs @@ -63,7 +63,14 @@ public IEnumerable Settings() yield return "tool.type:" + Type; yield return "tool.numSections:" + Sections.ToString(i); for (int s = 0; s < Sections; s++) yield return "tool.sectionWidth:" + s.ToString(i) + "," + SectionWidthCm.ToString("0.###", i); - if (Type is "front" or "rear") yield return "tool.hitchLength:" + HitchLength.ToString(i); + // A fixed implement's working point HitchLength from the rear axle: the tractor's 3-point + // (front hitch for a front implement) there and the implement at it (#547), as the + // AgOpenGPS import does. + if (Type is "front" or "rear") + { + yield return (Type == "front" ? "vehicle.frontHitchLength:" : "vehicle.threePointHitchLength:") + HitchLength.ToString(i); + yield return "tool.workingPointFromHitch:0"; + } else { yield return "tool.trailingHitchLength:" + HitchLength.ToString(i); From a6cc5967094ab88d2ede7e35791b374ae8461639 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:29:20 -0600 Subject: [PATCH 14/24] Update harness: read back a fixed tool's working point from the rear axle on every build Co-Authored-By: Claude Opus 5.5 --- Tests/AgOpenWeb.UpdateScenarios.Tests/Snapshot.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/AgOpenWeb.UpdateScenarios.Tests/Snapshot.cs b/Tests/AgOpenWeb.UpdateScenarios.Tests/Snapshot.cs index 8b545e9c2..f113bfdb1 100644 --- a/Tests/AgOpenWeb.UpdateScenarios.Tests/Snapshot.cs +++ b/Tests/AgOpenWeb.UpdateScenarios.Tests/Snapshot.cs @@ -181,6 +181,10 @@ public static void DumpStore(Dictionary d, UpdateRig rig) DumpObject(d, "Hotkeys", st.Hotkeys); d["NumSections"] = st.NumSections.ToString(Inv); d["ActualToolWidth"] = Num(st.ActualToolWidth); + // A fixed implement's working point from the rear axle: format 2's Tool.HitchLength, which this + // build splits into the vehicle's hitch and the tool's distance from it (#547). Older builds + // read it straight from the store, so every build is compared on the same number. + d["Tool.HitchLength"] = Num(AgOpenWeb.Models.Tool.RigGeometry.AxleToWorkingPoint(st.Vehicle, st.Tool)); d["ActiveVehicleProfileName"] = st.ActiveVehicleProfileName; d["ActiveToolProfileName"] = st.ActiveToolProfileName; d["IsMetric"] = st.IsMetric.ToString(); From 422d5e5b685db66168d08a6f6d60d34d6fc5fef0 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:26:29 -0600 Subject: [PATCH 15/24] Hitch diagrams: the vehicle's distances to its lower-link pins, a fixed tool's from them The rear and front tool pictures measured from the rear axle to the working point, the old single value; since #547 the tool's field is the distance from the 3-point lower-link pins. The vehicle's hitch picture now follows the field being typed in (rear 3-point, drawbar pin, front 3-point), else the hitch the tool hangs on. A test checks that every picture the web client names is in wwwroot/icons. Co-Authored-By: Claude Opus 5.5 --- .../wwwroot/icons/ToolHitch3ptFront.png | Bin 0 -> 74938 bytes .../wwwroot/icons/ToolHitch3ptRear.png | Bin 0 -> 79319 bytes .../wwwroot/icons/ToolHitchPageFront.png | Bin 71099 -> 0 bytes .../wwwroot/icons/ToolHitchPageRear.png | Bin 76413 -> 0 bytes .../wwwroot/icons/VehicleHitch3ptFront.png | Bin 0 -> 74442 bytes .../wwwroot/icons/VehicleHitch3ptRear.png | Bin 0 -> 79231 bytes .../wwwroot/js/config-panels.js | 4 +- .../wwwroot/js/settings-panels.js | 13 +++- .../IconReferenceTests.cs | 63 ++++++++++++++++++ 9 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptFront.png create mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptRear.png delete mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitchPageFront.png delete mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitchPageRear.png create mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/VehicleHitch3ptFront.png create mode 100644 Shared/AgOpenWeb.RemoteServer/wwwroot/icons/VehicleHitch3ptRear.png create mode 100644 Tests/AgOpenWeb.RemoteServer.Tests/IconReferenceTests.cs diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptFront.png b/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptFront.png new file mode 100644 index 0000000000000000000000000000000000000000..2f212a26f193aaa5406238959ab3b60760aefd00 GIT binary patch literal 74938 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 HEF=E|Bt*@Q literal 0 HcmV?d00001 diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptRear.png b/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitch3ptRear.png new file mode 100644 index 0000000000000000000000000000000000000000..3b7af0fd1fbfce495f39b6d16c4b76540e455cf0 GIT binary patch literal 79319 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 literal 0 HcmV?d00001 diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitchPageFront.png b/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/ToolHitchPageFront.png deleted file mode 100644 index a015fcb9040df3f074855829ff9f88b09e9d49a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71099 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≧e?)d%RR~pe)3H)^QnZK3eu2Ps219unFSyo!bLlPK*2ZS#V!z(lGcG_B&agZ` zm}aP zxmsceyO#zG3{`IoQX^$kH{D`FxZ%_*Jb0E^&!>|S{dx3Y7R!7=Qy66ZK&Xc5{R3$K2y_R(Kd5B4E`l^U!27@qd5*^I6S zO(3l927cZx)6CKNS;X8+VNd%LhVztLL|CQe^6#&&Z6TIy@bl_sYWXl6s!3%rv5jcw z#%&A<7=}}SH|*ugIjFJ_P;_M~yPZE_L?Mk=N70F)x;pK7i;pD?8FIM(RB86WS#tEB zb|8Dr@E2{7vGxHTR;{OkGihk<7cDTiSaplTSd*ldQnko>FIRPI#o*mgHHub(^q7oU zw&tE{uC*qpKM*cdJU$bxF=I(`q)v@#d0a2V-s%4kJeZj|yCj#%|E(-w}nEww7BNPE+P?tlG@ zLQV~*Uzt2HS}5?25*VC+pdC0p@r8ow!$tklVG5ApWwZyI*N*NIi|x}8H0UvdO5!c) zNYpKylY*$2$kFu%{~{6O=il{Bfwc<$7@<%la-Nyyr1APF|B>U5s>a@Q>zYFVX5IQp z%%DgK4bQnYo-yfKzg#@yVG-3&oo3Oy8Nd)Cj|h&Yb;=r29U~-?SGMjm4V%~}BNlB? zGIG;rW2?YeC_AZoY~**$o!e&Gn++C;#O4;}PMeCB?Yp=f&3kLe{=Vqsxj!&_?2lNdVnqVP8%KZY^E`hnNJ zaI^_-dp>?8vr@hmeJ5bQKilS&!_hPkbbFy%cVbrt<$&qsFcp+@HO?c84v69)Z_z6s~3an1i1xu{h{=EB| zmsdxnb`!QoLEaSZ*XU^PGEb_FT+Fv4Qz+9bbEKcdNTsOdXz*=53Kl^r?!(ReywdDs zNQ(qtohF9e{%K+ceEg%L@>maI?bq!K$_Q?qj#IWJk;2W!3Z+TKaU!bb*mYU_BzU?k ze3>vO^6K<<`)o)b?R7WsQnC$p=SZ?UVzS?Q-coPq)&?j@m=5d_SDj$~c-`675cO~x z*km?4;N>VR8qrd!@OMT6iJEE_+^+4-_4hk~AsaUR#m3QPP^ce?(%Y{l)n^jqbeNCh z$3|d9PuiOxoy8799#Bzf$34t;26MjIgWXJ(@{be)13YeIF{0StoSY%27N};;!M=U) zN1Y}(c?gkGs7wsl$%i5*Z6cfcj~KUnbq#LsW6iOc;(_O^j40Xp)fAGqNMjB6VU)br zF7V?b1bTE~As}=jJO#o5P(5Yn%3%RIy1U}MCFeT?^PG5O5ZU|Z*JXbaX0gg^b&qq= z7Z_}+=vG#|co2Na4W{KrJvEA_3hxZFwsf;YX>jPd`~l3f#1r2W!h;xUdl{JybnKk>yF1BV@eb~|t9^1>tQGal4Q$t! z;>;_W^{hPBo8Pz%etiwz>~edCE7E86kJLVRZaxFb2aaVTdaip(N--w}69Agh#mIN_ zP1FEt#d%CzZ-or^wjRt!ejE>bn5CcRlwz1BbM{aHu5F~+0=1{1hcu2w1YiJ=%+77) zE`bQ%sk#nD$pQoT03efe+l;p?-HeGc^Bo-_G3cX5?W*j}G$GSNy_eSO&H!+rpE_~z z7+b^R%p-+}Tk+97WA+ry-z~K04e+cdwZZScZN0_W;L_a28ck&CE${qdv;#n6JAh_> zB5Kf_L=HJX zuXqyx>Cdh^ax(&W1#-Klpf+O)=5qh3a?_HK6z-D(@;OE92<9uXk%b8BB&El%(#qfsjpb|&8>M>nmTs1R|L(i$M(fi#%|!nkY$1jq zqtc1+QUfT2nS92P#9`YZtjl_!5iD!fm{t{WSVl~fF#wh_k=mQgMOEe2J-18rC>l+T zNG;1&U8N+1-Pfho<5V}0cX%A5sx40aw*x>sjsVuiNdNR4PEQr&MOEDWDGe&0f9kv|?5IZ@3VVbA=uI;d4tGgP0ZH5)p>E~Tt_AR$QBk=CA3 z<&QWK87M2ll`460H2OOeDTb2^K07)mJX!pGKiLAhM1!lIU0&G6Z$ z6{k8DK$t(SX`*;H5-wK=+IOnY8l5sYPGjkjzeB?Zve*I2meIrbx|*{2Z&+wn@T=9+ zeS6K^#WKtsfeN$5~ zL~7<{UioWvJ9SB6TK%_x#0lP1O96xsLP{872;qVrnu%l+6XR{4AMZL6DiEU^6ERU1 zA|bX_^AFEK}S=Ve^rboxIT2eD!s1rG=AGbsO?O`W$SEDExkG^VNX#wBSlX( zqoMw~B~T}Ddl6l7My*y$Q!h8$=Bzqu{1Qxt=ka)_dKqdTqNF*crEBM5xOg4+0_8p_ zOk_(Z!$>f*mHR90Y=}+*Y#41+7c8z4+lm@?FMpuhbNg0X5@Or_r7Rq15eoC+A{n>b z)-HR-@Qr`3auGfWoR{PgUAcND0Qc{NsVl+qaE$EoWmHM_42EqGeB$=M8>&|jfoO(b zNt5om_p^0y|2z^5CG5Ln?sabHq99wwfnD|;fKn+qWH8i^_SgI4^wstMElLm{*fxOf zZ13H~X>tg1cX7wt@(-$2`;D5&vAbTk-(34!a*oe(!BXr3Ba^-E*t^wYqv#Zn5p|CgB z0OK-$K}phLEipo&X&@E0F`eCj9GsrV{#w?Q_)y@U{D3r3q6#aU?}l49mZ;l}{o6AQ z7nxEqKm}2zx*ZZXN)LHXllJ>GG#}(j$Fj;2;smtD(8Gf8fqSHe!?RrTg)|v062*Ml zPc{vsn?rl&1VK~p=B~x5NMYAs4ZpZ95;M4Nh4D`jbq)s+0*dQ3`5qu?n287k7ixa( z;n*sl678Ska9OCar@9yFh-YB| zjqFw`cSEtNE!t(xx`l33Rm+SZQX62v{>+si;L4!y3dD0d@2Oj1n$aPu77s-c#ARB) z0VJt%OA9DLBq-?S+T;Ww%p0^0aaeR(erP={nA(oQ6%~U zT%2#d*n}1?5)hqxe03m$!RLHrbY6|J0b7PSNa9Y~UAB8o^T9rfdU_5dfbUsCdRE#( zJ2z};$WbGxI3;UfB|YDSbR-a3ViW@EFNYL@p1+4_?Ro}fyD*GgO-!RXt;l%*z_;+? z4iliW#9!~0#RjJ_rr(9-%u@i8-iIb7j7DU#rp53f@#a*YMLOs@#1eBFfqRC`mJ`jX z&wp>boE0@zeYgl^hN#v?T&%|!87$BPqrjASSU$T5RI=_oS8HZ%to6UTL=roN@*|w2 z2a6b)qklmupocT;HlJY*_zufJ6L`d@jPev;F{B5Y=q3nT*OgX&$aR^_LSXJ4ek{$) z(|tdwqxr6?y#UGObiE@(kUl#}(2Nf(8*Y(?F|R>9y}jzeJ!uUOE4UaQy9Pwr1l-6Q zEjR$8vbYM{BGCdpzRl7Q6OoTNjpr1q21PzAs$5!zGEBoaXni^Ia%z5(9rtJ$v5KtJ zo2fl6K61@!U7@#I*>QA3~bZ_0lSkXf~@~m?ouZ81kxG?f9e7JhsM|5!{HZ z%EU-Xi=4!%*4w6rz)*uMU=n~Sba}XftK#czATV6U)^&GBRSi8qtTFi#A&0__Pin(wr&?CoB$5vK!0+QwoSq`e^PO zmiIGNN7dC&1fuWkk?KK!N}y;O|LrI9=78U}pVVno zHWaW(-c{S!=O}8rnRwCf#P1@|5b#`Vv$kQ;-+|O2*F{78V2|6?bgdcXVLOSWZ6ulh=%<4V+afdi&(gc>efd3OO8N6cW+2+HSQme*RH>NT+9FI+Wr=Y zzeB5)Byz?OOtFYKZofZgCq2K_liF@jkKUHwCi<-oeTR(#hJcGH zLU9Ym^v%SwY8i~aXPlg(>DWkN0(hG=#Hzx76$rd=d9!FWN{1&4kf&DX)OHaum_*^a z>n97m>UGi1JS}Br@&XIX?9&h~Q^;Ti4n7X0@enT6y4^8}`OAWO0P5J`wKh%w6tWqf zZo!jt%bZk=<_c{LiE>G`^6f>WWC7E^UThP3*B>%Lmn1a|kSDq(cZNvi?G&nQ*bwCP z5)+sydcP+!vb4{Ir`rsS961Abn1^P@8T%5B!U$Z5nDWX*Q(fc-nM@Y2Y)WlpO52@U z+a$ddi_Bq2K|{K4BnEJ4B(o{Rr42{`#pb$|zL+5{DD2pW&~XV)BGD`IuVR&v%l$)0{S1QvyW!zsj`@i@tjk-#(9K8qRM2X z$q$u>Xq~m_bSAJ9X+hNfPVJ6cNQnb{^qOG0odeNy9Bo^BW~n0s2IK5>8wYcAj^;uz za-hKnE72zCL2ibu>ZG<9ulEGY>F_jsH;LHE67S}CZ)j(yBcb6&j8n$jI7uvH3%``E z4TWGd<$tRvF1)(z5}2)jg&Vnfy+?=&_u$qoO@^$m)&mm5Z5bHPYUV5eDg%RF6nq~{ zVU-((MkWj-6UrV0iYaI+wH&KhZ+MUt!!`;6I-(C?@Wb=6?wir)UF43t_7l;6xu|%> zxBwjwX)gE$uf^;+m{JpbO%r?`7lf#|;4RABvnJ@)?<7p&b_1RC`orEdJNe~8U+>uDoDYVn8z353VJUlT|xW2KhPt|Ht&-Uye+ zixQ;npP2c2UCYQ+I~fNRkzkw%%9GX0+7~^f-)$wc(0l^KSC?^||2dOa&G<$Kkk}Rx zCFP*RO2tms6S{omZq?FW4!jqpM z=K5&CVK9XT(dus8zc^xDZ7tA+_`-*y_~3oP9!2{L3!u9BevfQE#5j09(g}*aFZhd? zL7pCT3tiEd7xoTMMBbz?*=03wE=jecV~d8pp%Q!N9Ey8=-d2`7`b2y?Vheh^)C#@5 zxf7OA`hpPGjUeu^1z7EL%A`uI3q56K3V~oK0*JA-uBfE@2@uHf5D@8;40V!;u4Gw* zf`BvgHGZ{AKR}gnw#q!;)YW#UaF9s)0iYi{N`-%s1uo%3&?-5PZ^?a7ha(8rr`)k; zi7OpD|6Yw%HoT*jtC-)*u&8b1`PB8&;{G#_lt%YO43Sz1Ks@S(?Mb1}u7F9%GviD8 z01d^YAD}>^t~A7j_`k%A)&tP3yY^K7+*^4YdZV`_jUWi~WHC~BpjT!qs2UPI43QEM zwYb}h`QO34_GyL|Qk=Xa4%iF=96P$div^%!oORpsYo_z%XRhO1pqXd?$}Dnop==b=l8$yTnBqmJve7X9KZKGhZLN$>P+m+_n;m9+1~vM ztBx9>^C6z94;_m6mxk$)-O+v{C7`X_~Zh z4msu%Vo5M!0+R6p=4MoW98gSva7 z!-$x!HJ4%(p~W|x2zx1q&9wJOQ=qSjrj^zS-fh%6ac&*L2#iFYn~4psILhrrz$4Ve z@i>Z{cu?^hXzi&L8XhI~DC_ulp?3;hbWKlrucwnsE1oAyc2f6`38Rr{vpcC}2#vVUHOuOpRv6p~Ma z7vZ{4X=jYks2~>uei4C#`ywIQ+dkG#x-6VG;dnNZy^P`UVR-Tfx=qwAtR#lE(j?{q zAThi7<48WLTXJ4bfP@i@%-8nB;I0&zzEqvfk3lRw z3{TfG7PqMcz}}N}_Yu4iPZ}GMZ~5Je#bslc>-!$DoAiRmZ%2NqK^#{)tc^v|FjL1g z7eMoUQfo%YI2W6*p%j@-#Pd?`N78|M^lrMcs1`(8#hAtrpx90b);7h@Q7x81RNVcp z;T!={r~#91SMap$4UjBb1YCbTg;Q#235Z|;;51t7G+b8dUBOc1c9$y8c*_7@<|caA zpK3EMp-_$**-RQh83JpX{1`9(iCLdD>>tg)Ux?uPx0xmF3!HD)TI=(w*t3J6CsBcI z+2_A52uI8)JTiyc=yH(Qm6_)7iq^#u-WhRTfCNUaM_h%QsEIGWZxzP6jpR~G`023_ zWT8ypONxEb=8V{PF!~Q@F@GcV;!$Fs(kvB{k7w%1@Ya1<6h*$}#m#9e(-nu+Log!U zHtPe_ELj1gRRu`Lu8E$QVS*vG72$D@JZf}BY8n8mCu9>pq3d9y#qk;beKa$@ncrsV z^0x_cA=3p@-1X~0VF4)L@~mP55ifXzg)=Dv0xrU7Y~VtI8nU1)03Z8Ney|UhI$~=j z1RqfnMw`d{CIa^-j;Lt}{g02Ip_WnYYFZe|n0?%Yu>Njk1q6nGE~N=iqwhzXeMND~ zdX2U%FNt4$hc%%1P_}QGbQGdHOayQnxt?liVISw}hzTujO;b_It0j1Hqhp+a=bcw87 zJ~%DZ=ni3k8*Q(#8q{T~+0~GL0P_^$iA1_-CJ|{5N5zj|heQQ4#32Re`iQ<8{F3N= zyoKUD1S9U}U@ZJFphP!<%lpY=+o3mc3xRwjwnoh&zv7qRk#UB656VcqtQ+~hmZf~Y zr=)UG=JwTNDot89l@;}_He$P-V`T2(3E$Zbxb?S#-Wb>Ay4y4oW6X2#30j3fa1|LL z7fQ=)jdT)b653gG7~P*kqEp4)B!aC=61_9UKj8q10W|;BL4qDAGF@v^J_*tTBFAKm zG{ec3C5G%i=E8iM6lMf9t$*ZO@Fbf(9xNYZ*;FkDKo6p;{qyIew>)VF1H1Fo^w5uG zJVT{xRmG#U?ZDI=w+@!Aa9AZ=_%w46Hf+Q*_0@l0LDY?EqlF;O$Y|1s&1+03 ze}upfkmdaOU{$u!g+Rv)dR``Y6F~_A4z;1k&iM*8UjTd^z{m&MRn`m)ej3{`4tl~P z?agw~8B+ZOqR*B^I@K4F`8b$WOtRU=KwnBmES*mln5mXTjz;r6N=1NOxR+7nzd*gp zTC^#Ii|O^84WXC>A)5{Co;BrVw_dj21`9~|Z3F}et>N#*vP<+!NicEV7`b&`cB@iR z0>3(yzV4nxk?L>joM1Gm&bjap$7E~S#z-9hK(P-0{1?XjZ2j+}@{?ikp&XTG-TV}& zb1V2i)d&}b4xMj(1aV>*epfK83Pty!MZ>%P;!9hTazG@5T8#e#pW?%zx0mMqa{PUr z`Ol-l#Z{(}*ekZjT@#9Q^XkJ{+{QfVui~5l}_TiZ7&E(mfrxaKK^bqvnwiX0_Gw+vA^&j81Hf9KR z<^Lr49q9r}{TfXPjwrX5nOGvRO>VG?OrlOVDPU605RYR1%2xFc%$|hOBfxd}gnpC~ ziG{D|hcrNMBLk2<7Eo@7R}0TOxIR!h8136V^dA8IlvX^>;#DMTBQt7n2ma*;R57dO z)uuL-UceA%KD@v<+^Tg}aK=^sctIbIWR)}%m;^uYTk*`gs`o;96x+Xj@Z?7IEzF|( z7q}2ZM8!4~+pS4N!LDfFc^#vxwL}=6_0IiCC&BdTPW^M-Ga5(`QI zyG4-bbIsQvF`@u4mg|38gZxgh8;zHUxoWhJNrlfw!G&`3<;

`JEBBzc=PS)L04 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 diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/VehicleHitch3ptFront.png b/Shared/AgOpenWeb.RemoteServer/wwwroot/icons/VehicleHitch3ptFront.png new file mode 100644 index 0000000000000000000000000000000000000000..b3b37c0a000701fe4f669a1e59fe4da7fbd0fd9e GIT binary patch literal 74442 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
_nUDz5z>nFXIK5 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%0A|axzofH$ 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 cfgSend('vehicle.hitchType', vcHitchSel.value)); +// The hitch field being typed in picks the hitch picture (the rear 3-point, the drawbar pin or the front 3-point). +let vcHitchKey = null; +for (const k of ['vehicle.threePointHitchLength', 'vehicle.hitchLength', 'vehicle.frontHitchLength']) { + const el = vcPanel.querySelector('.cfg-num[data-key="' + k + '"]'); + el.addEventListener('focus', () => { vcHitchKey = k; populateVehicleCfg(false); }); + el.addEventListener('blur', () => { if (vcHitchKey === k) vcHitchKey = null; populateVehicleCfg(false); }); +} // Heading fusion = GPS share (0–1), shown like AgOpenGPS: "GPS 30% · IMU 70%" (#112). const fusionLabel = w => tr('GPS {gps}% · IMU {imu}%', { gps: Math.round(w * 100), imu: 100 - Math.round(w * 100) }); vcFw.addEventListener('input', () => { document.getElementById('vc-hfw').textContent = fusionLabel(+vcFw.value); cfgSend('gps.headingFusionWeight', vcFw.value); }); @@ -104,7 +111,11 @@ function populateVehicleCfg(force) { if (n) { const p = '/icons/' + n + '.png'; if (!el.src.endsWith(p)) el.src = p; el.style.display = ''; } else el.style.display = 'none'; }; - setImg('vc-img-hitch', ['Hitch', '', 'HitchArticulated']); + // The hitch picture shows the field being typed in, else the hitch the tool hangs on (#547). + const hk = vcHitchKey || ({ 0: 'vehicle.frontHitchLength', 2: 'vehicle.hitchLength', 3: 'vehicle.hitchLength' }[cfgGet('tool.type')] || 'vehicle.threePointHitchLength'); + setImg('vc-img-hitch', hk === 'vehicle.hitchLength' ? ['Hitch', '', 'HitchArticulated'] + : hk === 'vehicle.frontHitchLength' ? ['VehicleHitch3ptFront', '', 'VehicleHitch3ptFront'] + : ['VehicleHitch3ptRear', '', 'VehicleHitch3ptRear']); setImg('vc-img-wheelbase', ['WheelbaseTractor', 'WheelbaseHarvester', 'WheelbaseArticulated']); setImg('vc-img-track', ['TrackWidthTractor', 'TrackWidthHarvester', 'TrackWidthArticulated']); setImg('vc-img-antside', ['AntennaTractorTop', 'AntennaHarvesterTop', 'AntennaArticulatedTop']); diff --git a/Tests/AgOpenWeb.RemoteServer.Tests/IconReferenceTests.cs b/Tests/AgOpenWeb.RemoteServer.Tests/IconReferenceTests.cs new file mode 100644 index 000000000..2cbb39156 --- /dev/null +++ b/Tests/AgOpenWeb.RemoteServer.Tests/IconReferenceTests.cs @@ -0,0 +1,63 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NUnit.Framework; + +namespace AgOpenWeb.RemoteServer.Tests; + +/// +/// Every picture the web client names is in wwwroot/icons: a literal /icons/X.png, and the +/// lists of picture names it picks from (the measurement diagrams, chosen by the vehicle or tool type). +/// A missing one is an empty box in a settings panel, which no other check sees (the hitch diagrams, +/// redrawn for #547, replaced two of AgOpenGPS's). +/// +[TestFixture] +public class IconReferenceTests +{ + [Test] + public void Every_picture_the_client_names_exists() + { + var www = Path.Combine(RepoRoot(), "Shared", "AgOpenWeb.RemoteServer", "wwwroot"); + // By the exact name: a Linux host serves files case-sensitively (a Mac's disk would find "About.png" for "about"). + var icons = Directory.GetFiles(Path.Combine(www, "icons"), "*.png").Select(Path.GetFileNameWithoutExtension).ToHashSet(StringComparer.Ordinal); + bool Exists(string n) => icons.Contains(n); + var names = new SortedSet(StringComparer.Ordinal); + foreach (var f in Directory.GetFiles(Path.Combine(www, "js"), "*.js").Append(Path.Combine(www, "index.html"))) + { + var src = File.ReadAllText(f); + foreach (Match m in Regex.Matches(src, @"/icons/([A-Za-z0-9_-]+)\.png")) + names.Add(m.Groups[1].Value); + // The lists of names a picture is chosen from (by the vehicle or tool type): every list in a + // setImg(...) call or on a line that builds an /icons/ path, and any list in which one name is + // a picture (a list of pictures, so every name in it must be one). + const string List = @"\[((?:\s*'[A-Za-z0-9_-]*'\s*,?)+)\]"; + static IEnumerable Items(Match list) => + Regex.Matches(list.Groups[1].Value, @"'([A-Za-z0-9_-]+)'").Select(n => n.Groups[1].Value); + var chosen = Regex.Matches(src, @"setImg\('[^']*',.*?\);", RegexOptions.Singleline).Select(m => m.Value) + .Concat(src.Split('\n').Where(l => l.Contains("/icons/", StringComparison.Ordinal))); + foreach (var text in chosen) + foreach (Match list in Regex.Matches(text, List)) + names.UnionWith(Items(list)); + foreach (Match list in Regex.Matches(src, List)) + if (Items(list).Any(Exists)) names.UnionWith(Items(list)); + } + Assert.That(names, Does.Contain("ToolHitch3ptRear").And.Contain("VehicleHitch3ptFront"), "the scan finds the hitch diagrams"); + var missing = names.Where(n => !Exists(n)).ToList(); + Assert.That(missing, Is.Empty, "named by the web client but not in wwwroot/icons"); + } + + private static string RepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AgOpenWeb.sln"))) + dir = dir.Parent; + Assert.That(dir, Is.Not.Null, "Could not locate repo root (AgOpenWeb.sln) from test base dir."); + return dir!.FullName; + } +} From 5168bcf3c1fa65ec4d6deca3fe1e19423b9559b1 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:34 +0100 Subject: [PATCH 16/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lv.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lv.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lv.json index 209e90d49..b2e1639df 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lv.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lv.json @@ -73,7 +73,6 @@ "Finish the U-turn first": "Vispirms pabeidziet U-pagriezienu", "Fix to fix": "Fiksēts", "Hitch": "Sakabe", - "Hitch length": "Sakabes garums", "Hotkeys": "Karstie taustiņi", "Hydraulic": "Hidraulika", "Imperial": "Imperiālā sistēma", @@ -770,7 +769,6 @@ "Whole Boundary": "Visa robeža", "Whole-boundary headland is not point-edited": "Visas robežas apgriešanās joslu nevar rediģēt ar punktiem", "Work switch": "Darba slēdzis", - "Working center": "Darba centrs", "XTE": "XTE", "XTE Chart": "XTE diagramma", "Yaw rate": "Leņķiskais ātrums", From fa6155bc5fc26b7686ae032909a522d7fcb79ce3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:35 +0100 Subject: [PATCH 17/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lt.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lt.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lt.json index 1f975b9b0..39563de4b 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lt.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/lt.json @@ -367,7 +367,6 @@ "Headland on/off": "Galulaukės įjungta/išjungta", "Help & Info": "Pagalba ir info", "Hitch / Wheelbase / Track": "Sukabinimo įtaisas / Ratų bazė / Vėžė", - "Hitch length": "Vilkties įtaiso ilgis", "Host": "Priegloba", "Host IPs": "Prieglogos IP adresai", "Hotkeys": "Spartieji klavišai", @@ -770,7 +769,6 @@ "Whole Boundary": "Visa riba", "Whole-boundary headland is not point-edited": "Visos ribos pakraštys nėra redaguojamas taškiniu būdu", "Work switch": "Darbinis jungtukas", - "Working center": "Dirbimo centras", "XTE": "XTE navigacijos nuokrypis", "Yaw rate": "Posūkio greitis", "Yellow": "Geltona", From 9c5f7a37ff5b359af26c4c71b1100e01eb1c50ea Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:35 +0100 Subject: [PATCH 18/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ Translated using Weblate (German) Currently translated at 90.4% (1022 of 1130 strings) Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/de/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/de.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/de.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/de.json index 882f374d0..75dc0bbc0 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/de.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/de.json @@ -425,7 +425,6 @@ "Green": "Grün", "Headland on/off": "Vorgewende ein/aus", "Help & Info": "Hilfe & Info", - "Hitch length": "Distanz zu Anhängevorrichtung", "Implement length": "Länge Anbaugerät", "Import \"{file}\" as the field boundary? This replaces the current outer boundary.": "Importiere \"{file}\" als Feldgrenze? Diese ersetzt die aktuelle Feldgrenze.", "Import boundary from a KML/KMZ file": "Importiere Feldgrenze von KML/KMZ Datei", @@ -724,7 +723,6 @@ "Toggle recorded-path display": "Anzeige des aufgezeichneten Pfads ein-/ausblenden", "Type a name, then New": "Tippe einen Namen ein, dann tippe auf Neu", "Wheel Track": "*Spurweite", - "Working center": "Arbeitspunkt", "Integral gain (Ki)": "Integral Faktor", "Gain / PWM": "Faktor / PWM", "Steering + section control": "Lenkung + Teilbreitenschaltung", @@ -996,7 +994,7 @@ "Passes": "*Durchgänge", "Passes (0 = all)": "*Überspringen (0=alle)", "{n} pass": "*{n} überspringen", - "First pass": "*Erster Durchgang", + "First pass": "Erster Durchgang", "Act": "Ist", "Actual remaining": "Effektiv verbleibend", "Actual": "*Effektiv netto", From 9c6f407eceac40c1003ed5350b4383dae51ac6fb Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:36 +0100 Subject: [PATCH 19/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/cs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/cs.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/cs.json index 1235e0522..20e0b98f5 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/cs.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/cs.json @@ -325,7 +325,6 @@ "Headland on/off": "Souvratě zap/vyp", "Help & Info": "Nápověda a informace", "Hitch": "Závěs", - "Hitch length": "Délka závěsu", "Host IPs": "Hostovací IP", "Hotkeys": "Klávesové zkratky", "How fast GPS corrects the IMU heading": "Jak rychle GPS koriguje kurz z IMU", From 9ec839ef1f08e4042d320033357b23042a1e2fa2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:36 +0100 Subject: [PATCH 20/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pt.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pt.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pt.json index 073dd9c6c..cf4439a1e 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pt.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pt.json @@ -348,7 +348,6 @@ "Headland on/off": "Rumo liga/desliga", "Help & Info": "Ajuda e informações", "Hitch / Wheelbase / Track": "Engate / Distância entre eixos / Bitola", - "Hitch length": "Comprimento do engate", "Host": "Hospedagem", "Host IPs": "IPs de hospedagem", "Hotkeys": "Teclas de atalho", @@ -712,7 +711,6 @@ "Whole Boundary": "Borda completa", "Whole-boundary headland is not point-edited": "A borda completa não está sujeita a edição por pontos.", "Work switch": "Trocar de trabalho", - "Working center": "Central de serviço", "XTE": "XTE", "Yellow": "Amarelo", "Yes": "Sim", From ab81538758667014bd73b7371c334c3bf4492292 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:36 +0100 Subject: [PATCH 21/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/hu.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/hu.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/hu.json index 75b206e17..cde88d2fd 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/hu.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/hu.json @@ -340,7 +340,6 @@ "Headland on/off": "Forgó be/ki", "Help & Info": "Segítség & Információ", "Hitch / Wheelbase / Track": "Horog / Tengelytáv / Nyomtáv", - "Hitch length": "Horog hossz", "Host": "Szerver", "Host IPs": "Szerver IPk", "Hotkeys": "Gyorsbillentyűk", @@ -701,7 +700,6 @@ "Whole Boundary": "Teljes határ", "Whole-boundary headland is not point-edited": "A teljes határ menti forgó pontonként nem szerkeszthető", "Work switch": "Munkakapcsoló", - "Working center": "Munka középpont", "XTE": "XTE", "Yellow": "Sárga", "Yes": "Igen", From 150d93441262640bde3226d8e72eb948b0646b99 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:37 +0100 Subject: [PATCH 22/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pl.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pl.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pl.json index e70e9447d..8ba7ed616 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pl.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/pl.json @@ -324,7 +324,6 @@ "Headland on/off": "Uwrocie wł./wył.", "Help & Info": "Pomoc i informacje", "Hitch / Wheelbase / Track": "Zaczep / Rozstaw osi / Rozstaw kół", - "Hitch length": "Długość zaczepu", "Host": "Serwer", "Host IPs": "Adresy IP urządzenia", "Hotkeys": "Skróty klawiszowe", @@ -687,7 +686,6 @@ "Whole Boundary": "Cała granica", "Whole-boundary headland is not point-edited": "Uwrocie całościowe nie podlega edycji punktowej", "Work switch": "Przycisk pracy", - "Working center": "Środek roboczy", "XTE": "XTE", "Yellow": "Żółty", "Yes": "Tak", From 9cfdf4b7e1fecdcaff22a808a5999ed124410220 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 14 Nov 2026 01:13:37 +0100 Subject: [PATCH 23/24] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: AgOpenGPS/AgOpenWeb Translate-URL: https://hosted.weblate.org/projects/agopengps/agopenweb/ --- Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/nl.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/nl.json b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/nl.json index 8992f95ca..5f066b61d 100644 --- a/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/nl.json +++ b/Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/nl.json @@ -2,7 +2,6 @@ "Steer response hold": "Stuurreactie vastgezet", "Reset Default {kind} profile?": "Zet Standaardprofiel {kind} terug?", "Move the line left by the step": "Verplaats de lijn een stapafstand naar links", - "Working center": "Werkcentrum", "Check the address and the internet connection.": "Controleer het adres en de internetverbinding.", "{system} observations (MSM{n})": "{system} observaties (MSM{n})", "Web Cards": "Webkaarten", @@ -638,7 +637,6 @@ "Help & Info": "Help & Info", "Hitch": "Hef", "Hitch / Wheelbase / Track": "Hef / Wielbasis / Spoor", - "Hitch length": "Heflengte", "Host": "Host", "Host IPs": "Host-IPs", "Hotkeys": "Sneltoetsen", From 832881fef78b5a7cbc3a65977ab6256b2159ca90 Mon Sep 17 00:00:00 2001 From: chriskinal <118364224+chriskinal@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:53:31 -0600 Subject: [PATCH 24/24] Jobs keep the machine they were worked with: the vehicle's hitch points and the chain of tools job.json gains rig: the vehicle's connectors and the tools in order from it, each measured from the connector it hangs on (ISO 11783-10), a copy of the loaded profiles taken when the job starts or resumes and whenever a profile is loaded or saved, written only when it changed. The web client keeps one tool, so AgOpenWeb writes a chain of one; the format takes more. A new member, so schemaVersion stays 1 (#547). Co-Authored-By: Claude Opus 5.5 --- Docs/FIELD_FILE_FORMATS.md | 7 + Shared/AgOpenWeb.Models/Job/Job.cs | 6 + Shared/AgOpenWeb.Models/Job/JobRig.cs | 163 ++++++++++++++++ .../Fields/JobJsonService.cs | 3 + .../Interfaces/IJobService.cs | 6 + Shared/AgOpenWeb.Services/JobService.cs | 9 + Shared/AgOpenWeb.ViewModels/MainViewModel.cs | 22 ++- Tests/AgOpenWeb.Services.Tests/JobRigTests.cs | 176 ++++++++++++++++++ .../JobRigRecordingTests.cs | 65 +++++++ 9 files changed, 454 insertions(+), 3 deletions(-) create mode 100644 Shared/AgOpenWeb.Models/Job/JobRig.cs create mode 100644 Tests/AgOpenWeb.Services.Tests/JobRigTests.cs create mode 100644 Tests/AgOpenWeb.ViewModels.Tests/JobRigRecordingTests.cs diff --git a/Docs/FIELD_FILE_FORMATS.md b/Docs/FIELD_FILE_FORMATS.md index a02184b36..aa4dea8fd 100644 --- a/Docs/FIELD_FILE_FORMATS.md +++ b/Docs/FIELD_FILE_FORMATS.md @@ -175,6 +175,13 @@ A job's worked area is stored as square binary tiles on the field's plane grid: CamelCase JSON: `schemaVersion` (1), `id` (GUID), `fieldName`, `taskName` (same as the folder name), `workType`, `notes`, `startedAt`, `endedAt`, `lastOpenedAt` (ISO 8601), `status` (`"InProgress"`, `"Done"` or `"Abandoned"`), `distanceTraveledMeters`, `areaWorkedHectares`, `uTurnCount`. A `jobs//` folder without `job.json` is not listed as a job. +`rig` (optional; absent in jobs saved before it was kept): the machine the job was worked with, a copy of the profiles, not their names only, kept up to date while the job is open. As ISO 11783-10, each part is measured from the connector it hangs on, in metres, distances positive in their own direction: + +- `vehicle`: `profileName`, `type` (`"Tractor"`, `"Harvester"`, `"Articulated"`), `hitchType` (ISO 11783 coupling type, DDI 157: -1 not available, 0 unknown), and its connectors from the rear axle: `drawbarHitch` and `rearThreePointHitch` (the centre of the lower link points) behind it, `frontThreePointHitch` ahead of it (0: none set). +- `tools`: the tools in order from the vehicle. Each has `profileName`, `connector` (`"RearThreePoint"`, `"Drawbar"`, `"FrontThreePoint"` on the vehicle, or `"PreviousTool"`: the rear connector of the tool before it), `mount` (`"FrontFixed"`, `"RearFixed"`, `"Trailing"`, `"TBT"`), `hitchType`, then its distances from its connector: `workingPointFromHitch` (a fixed tool; negative is on the vehicle's side of the connector), `trailingHitchLength` (a trailed tool: connector to its wheels), `tankTrailingHitchLength` (TBT: connector to the tank's wheels), `trailingToolToPivotLength` (its wheels to the working point, + behind), and `offset` (working point to the right of the vehicle's centre line), `length`, `width`, `sectionWidths` (left to right), `rearConnectorFromHitch` (its own rear connector behind its connector, for a tool hung behind it; absent when it has none). A distance the tool's `mount` doesn't use is 0. + +AgOpenWeb works with one tool and writes a chain of one; a reader should accept more. A job reopened in AgOpenWeb gets the machine loaded then. + `schemaVersion` follows the rules of `formatVersion` (section 3): members a reader doesn't know are kept by a writer, and the version goes up only for a change of meaning. A job whose `job.json` or coverage `manifest.json` is in a newer version than the reader knows is listed, but its coverage is not read and nothing is written to the job (AgOpenWeb tells the operator to start a new job). A `status` the reader doesn't know reads as `"InProgress"`. ### manifest.json diff --git a/Shared/AgOpenWeb.Models/Job/Job.cs b/Shared/AgOpenWeb.Models/Job/Job.cs index 30cf46aae..98e1ba1fd 100644 --- a/Shared/AgOpenWeb.Models/Job/Job.cs +++ b/Shared/AgOpenWeb.Models/Job/Job.cs @@ -77,4 +77,10 @@ public class Job /// Number of U-turns executed during this job. /// public int UTurnCount { get; set; } + + /// + /// The vehicle and the chain of tools the job was worked with (#547), a copy of the loaded + /// profiles kept up to date while the job is open; null for a job saved before it was kept. + /// + public JobRig? Rig { get; set; } } diff --git a/Shared/AgOpenWeb.Models/Job/JobRig.cs b/Shared/AgOpenWeb.Models/Job/JobRig.cs new file mode 100644 index 000000000..54743f254 --- /dev/null +++ b/Shared/AgOpenWeb.Models/Job/JobRig.cs @@ -0,0 +1,163 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Tool; + +namespace AgOpenWeb.Models.Job; + +/// +/// The machine a job was worked with: the vehicle's hitch points and the chain of tools hung on +/// them, in order from the vehicle (#547). It is a copy, not a profile name: a profile can be +/// edited or deleted later, and the job's coverage only makes sense with the tools that painted +/// it. As ISO 11783-10 chains a tractor's connectors and each implement's connector and elements, +/// each tool is measured from the connector it hangs on: the vehicle's, or the rear connector of +/// the tool before it. The web client works with one tool, so AgOpenWeb writes a chain of one; +/// a file from another program may carry more. +/// +public sealed class JobRig +{ + public RigVehicle Vehicle { get; set; } = new(); + + /// The tools in order from the vehicle; the first hangs on the vehicle. + public List Tools { get; set; } = new(); + + /// The vehicle and the one tool loaded now. + public static JobRig From(ConfigurationStore store) + { + var v = store.Vehicle; + var t = store.Tool; + var mount = RigGeometry.Mount(t); + int n = Math.Clamp(store.NumSections, 0, ToolConfig.MaxSections); + return new JobRig + { + Vehicle = new RigVehicle + { + ProfileName = store.ActiveVehicleProfileName ?? string.Empty, + Type = v.Type.ToString(), + HitchType = v.HitchType, + DrawbarHitch = v.HitchLength, + RearThreePointHitch = v.ThreePointHitchLength, + FrontThreePointHitch = v.FrontHitchLength, + }, + Tools = + { + new RigTool + { + ProfileName = store.ActiveToolProfileName ?? string.Empty, + Connector = mount switch + { + ToolMount.FrontFixed => RigConnector.FrontThreePoint, + ToolMount.RearFixed => RigConnector.RearThreePoint, + _ => RigConnector.Drawbar, + }, + Mount = mount, + HitchType = t.HitchType, + WorkingPointFromHitch = RigGeometry.IsRigid(mount) ? t.WorkingPointFromHitch : 0, + TrailingHitchLength = RigGeometry.IsRigid(mount) ? 0 : t.TrailingHitchLength, + TankTrailingHitchLength = mount == ToolMount.TBT ? t.TankTrailingHitchLength : 0, + TrailingToolToPivotLength = RigGeometry.IsRigid(mount) ? 0 : t.TrailingToolToPivotLength, + Offset = t.Offset, + Length = t.Length, + Width = store.ActualToolWidth, + SectionWidths = Enumerable.Range(0, n).Select(i => t.GetSectionWidth(i) / 100.0).ToList(), + }, + }, + }; + } + + /// The same machine, every value equal (to decide whether a job's copy is out of date). + public bool SameAs(JobRig? other) => + other != null && Vehicle.SameAs(other.Vehicle) + && Tools.Count == other.Tools.Count && Tools.Zip(other.Tools).All(p => p.First.SameAs(p.Second)); +} + +/// A vehicle's connectors, each its distance from the rear axle in metres (+ in its own direction). +public sealed class RigVehicle +{ + public string ProfileName { get; set; } = string.Empty; + + /// by name: Tractor, Harvester, Articulated. + public string Type { get; set; } = string.Empty; + + /// ISO 11783 coupling type (DDI 157) on the vehicle's side; -1 not available, 0 unknown. + public int HitchType { get; set; } + + /// Drawbar pin, behind the rear axle. + public double DrawbarHitch { get; set; } + + /// Rear 3-point hitch (the centre of the lower link points), behind the rear axle. + public double RearThreePointHitch { get; set; } + + /// Front 3-point hitch, ahead of the rear axle; 0 when the vehicle has none set. + public double FrontThreePointHitch { get; set; } + + public bool SameAs(RigVehicle o) => + ProfileName == o.ProfileName && Type == o.Type && HitchType == o.HitchType + && DrawbarHitch.Equals(o.DrawbarHitch) && RearThreePointHitch.Equals(o.RearThreePointHitch) + && FrontThreePointHitch.Equals(o.FrontThreePointHitch); +} + +/// Where a tool hangs: one of the vehicle's connectors, or the rear connector of the tool before it. +public enum RigConnector +{ + RearThreePoint, + Drawbar, + FrontThreePoint, + PreviousTool, +} + +/// +/// One tool of the chain, its distances in metres along the tool from the connector it hangs on, +/// as the tool profile keeps them. +/// +public sealed class RigTool +{ + public string ProfileName { get; set; } = string.Empty; + + public RigConnector Connector { get; set; } + + /// How it hangs: FrontFixed, RearFixed, Trailing or TBT. + public ToolMount Mount { get; set; } + + /// ISO 11783 coupling type (DDI 157) on the tool's side; -1 not available, 0 unknown. + public int HitchType { get; set; } + + /// A fixed tool: its working point from its connector (negative: on the vehicle's side of it). + public double WorkingPointFromHitch { get; set; } + + /// A trailed tool: its connector to its wheels. + public double TrailingHitchLength { get; set; } + + /// TBT: the connector to the tank's wheels (the tool then hangs on the tank). + public double TankTrailingHitchLength { get; set; } + + /// A trailed tool: its wheels to its working point (+ behind). + public double TrailingToolToPivotLength { get; set; } + + /// The working point to the side of the vehicle's centre line, + to the right. + public double Offset { get; set; } + + /// The tool's length along the direction of travel. + public double Length { get; set; } + + /// The working width: the sections together. + public double Width { get; set; } + + /// Each section's width, left to right. + public List SectionWidths { get; set; } = new(); + + /// A tool hung behind this one: its connector's distance behind this tool's connector; null when it has none. + public double? RearConnectorFromHitch { get; set; } + + public bool SameAs(RigTool o) => + ProfileName == o.ProfileName && Connector == o.Connector && Mount == o.Mount && HitchType == o.HitchType + && WorkingPointFromHitch.Equals(o.WorkingPointFromHitch) && TrailingHitchLength.Equals(o.TrailingHitchLength) + && TankTrailingHitchLength.Equals(o.TankTrailingHitchLength) && TrailingToolToPivotLength.Equals(o.TrailingToolToPivotLength) + && Offset.Equals(o.Offset) && Length.Equals(o.Length) && Width.Equals(o.Width) + && SectionWidths.SequenceEqual(o.SectionWidths) && Nullable.Equals(RearConnectorFromHitch, o.RearConnectorFromHitch); +} diff --git a/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs b/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs index eaa36c704..7b315d26e 100644 --- a/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs +++ b/Shared/AgOpenWeb.Services/Fields/JobJsonService.cs @@ -180,6 +180,7 @@ private sealed class JobDto public double DistanceTraveledMeters { get; set; } public double AreaWorkedHectares { get; set; } public int UTurnCount { get; set; } + public JobRig? Rig { get; set; } /// Members this build doesn't read, kept as they were. [JsonExtensionData] @@ -200,6 +201,7 @@ private sealed class JobDto DistanceTraveledMeters = j.DistanceTraveledMeters, AreaWorkedHectares = j.AreaWorkedHectares, UTurnCount = j.UTurnCount, + Rig = j.Rig, Other = j.UnknownProperties?.ToDictionary(p => p.Key, p => p.Value), }; @@ -217,6 +219,7 @@ private sealed class JobDto DistanceTraveledMeters = DistanceTraveledMeters, AreaWorkedHectares = AreaWorkedHectares, UTurnCount = UTurnCount, + Rig = Rig, UnknownProperties = Other, }; diff --git a/Shared/AgOpenWeb.Services/Interfaces/IJobService.cs b/Shared/AgOpenWeb.Services/Interfaces/IJobService.cs index a94e083b6..836e4465c 100644 --- a/Shared/AgOpenWeb.Services/Interfaces/IJobService.cs +++ b/Shared/AgOpenWeb.Services/Interfaces/IJobService.cs @@ -102,6 +102,12 @@ public interface IJobService /// Job? ActiveJob { get; } + /// + /// Keep the machine the active job is worked with (#547): the job's copy is replaced and its + /// job.json saved when it differs. Nothing without an active job. + /// + void RecordRig(JobRig rig); + /// /// Fired whenever changes (including to null). /// diff --git a/Shared/AgOpenWeb.Services/JobService.cs b/Shared/AgOpenWeb.Services/JobService.cs index 9fb874ae6..b701b126e 100644 --- a/Shared/AgOpenWeb.Services/JobService.cs +++ b/Shared/AgOpenWeb.Services/JobService.cs @@ -196,6 +196,15 @@ public bool DeleteJob(string fieldName, string taskName) return true; } + public void RecordRig(JobRig rig) + { + if (ActiveJob == null || rig.SameAs(ActiveJob.Rig)) return; + var fieldDir = ResolveFieldDir(ActiveJob.FieldName); + if (fieldDir == null) return; + ActiveJob.Rig = rig; + JobJsonService.Save(ActiveJob, fieldDir); // not to a newer build's job (Save leaves it) + } + public void SuspendCurrentJob() { if (ActiveJob == null) return; diff --git a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs index 3d8cb2c3b..5fe866286 100644 --- a/Shared/AgOpenWeb.ViewModels/MainViewModel.cs +++ b/Shared/AgOpenWeb.ViewModels/MainViewModel.cs @@ -329,8 +329,8 @@ public MainViewModel( // (load / save / picker dialog) so labels like CurrentProfileName // re-render. The store updates correctly on its own, but bindings // through computed properties on this VM need an explicit notify. - _configurationService.ProfileLoaded += (_, _) => RaiseProfileNameChanged(); - _configurationService.ProfileSaved += (_, _) => RaiseProfileNameChanged(); + _configurationService.ProfileLoaded += (_, _) => { RaiseProfileNameChanged(); RecordJobRig(); }; + _configurationService.ProfileSaved += (_, _) => { RaiseProfileNameChanged(); RecordJobRig(); }; _autoSteerService = autoSteerService; _smartWasService = smartWasService; _trackCopierService = trackCopierService; @@ -345,8 +345,9 @@ public MainViewModel( _jobService = jobService; // Refresh the field/job pill + status strip whenever the active job // changes (created, resumed, suspended). - _jobService.ActiveJobChanged += (_, _) => + _jobService.ActiveJobChanged += (_, job) => { + if (job != null) RecordJobRig(); OnPropertyChanged(nameof(CurrentJobTaskName)); OnPropertyChanged(nameof(CurrentFieldAndJobLabel)); RaiseStatusStripChanged(); @@ -3899,6 +3900,21 @@ public string CurrentProfileSummary /// status pill on ConfigurationPanel refresh after the picker dialog /// or any other profile change. /// + /// + /// The active job keeps the machine it is worked with (#547): the vehicle and tool loaded now, + /// when the job starts or resumes and whenever a profile is loaded or saved (closing a settings + /// panel saves it). The job's file is written only when the machine changed. + /// + private void RecordJobRig() + { + if (_jobService.ActiveJob == null) return; + try { _jobService.RecordRig(AgOpenWeb.Models.Job.JobRig.From(_configStore)); } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + _logger.LogWarning(ex, "[Job] Could not save the machine to job.json"); + } + } + private void RaiseProfileNameChanged() { OnPropertyChanged(nameof(CurrentProfileName)); diff --git a/Tests/AgOpenWeb.Services.Tests/JobRigTests.cs b/Tests/AgOpenWeb.Services.Tests/JobRigTests.cs new file mode 100644 index 000000000..c8ce9800a --- /dev/null +++ b/Tests/AgOpenWeb.Services.Tests/JobRigTests.cs @@ -0,0 +1,176 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json.Nodes; +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Job; +using AgOpenWeb.Models.Tool; +using AgOpenWeb.Services.Fields; + +namespace AgOpenWeb.Services.Tests; + +/// +/// A job keeps the machine it was worked with (#547): the vehicle's hitch points and the chain of +/// tools on them, each measured from the connector it hangs on, in job.json's rig. +/// +[TestFixture] +public class JobRigTests +{ + private string _root = null!; + + [SetUp] + public void SetUp() + { + _root = Path.Combine(Path.GetTempPath(), $"aow_jobrig_{Guid.NewGuid():N}"); + Directory.CreateDirectory(Path.Combine(_root, "north40")); + } + + [TearDown] + public void TearDown() + { + if (Directory.Exists(_root)) Directory.Delete(_root, recursive: true); + } + + private static ConfigurationStore Store(string type) + { + var s = new ConfigurationStore { ActiveVehicleProfileName = "Fendt 312", ActiveToolProfileName = "Drill 3 m" }; + s.Vehicle.HitchLength = 1.9; + s.Vehicle.ThreePointHitchLength = 1.2; + s.Vehicle.FrontHitchLength = 2.6; + s.Vehicle.HitchType = 3; + s.Tool.SetToolType(type); + s.Tool.WorkingPointFromHitch = 0.8; + s.Tool.TrailingHitchLength = 4.0; + s.Tool.TankTrailingHitchLength = 3.0; + s.Tool.TrailingToolToPivotLength = 0.5; + s.Tool.Offset = 0.25; + s.Tool.Length = 2.0; + s.Tool.HitchType = 5; + s.NumSections = 3; + s.Tool.SetSectionWidth(0, 100); + s.Tool.SetSectionWidth(1, 150); + s.Tool.SetSectionWidth(2, 50); + return s; + } + + [TestCase("rear", RigConnector.RearThreePoint, ToolMount.RearFixed)] + [TestCase("front", RigConnector.FrontThreePoint, ToolMount.FrontFixed)] + [TestCase("trailing", RigConnector.Drawbar, ToolMount.Trailing)] + [TestCase("tbt", RigConnector.Drawbar, ToolMount.TBT)] + public void The_copy_is_the_loaded_vehicle_and_tool(string type, RigConnector connector, ToolMount mount) + { + var rig = JobRig.From(Store(type)); + var t = rig.Tools.Single(); + bool rigid = mount is ToolMount.FrontFixed or ToolMount.RearFixed; + Assert.Multiple(() => + { + Assert.That(rig.Vehicle.ProfileName, Is.EqualTo("Fendt 312")); + Assert.That(rig.Vehicle.Type, Is.EqualTo("Tractor")); + Assert.That(rig.Vehicle.HitchType, Is.EqualTo(3)); + Assert.That(rig.Vehicle.DrawbarHitch, Is.EqualTo(1.9)); + Assert.That(rig.Vehicle.RearThreePointHitch, Is.EqualTo(1.2)); + Assert.That(rig.Vehicle.FrontThreePointHitch, Is.EqualTo(2.6)); + Assert.That(t.ProfileName, Is.EqualTo("Drill 3 m")); + Assert.That(t.Connector, Is.EqualTo(connector)); + Assert.That(t.Mount, Is.EqualTo(mount)); + Assert.That(t.HitchType, Is.EqualTo(5)); + // Only the distances the tool type uses: the others are the profile's leftovers. + Assert.That(t.WorkingPointFromHitch, Is.EqualTo(rigid ? 0.8 : 0)); + Assert.That(t.TrailingHitchLength, Is.EqualTo(rigid ? 0 : 4.0)); + Assert.That(t.TankTrailingHitchLength, Is.EqualTo(mount == ToolMount.TBT ? 3.0 : 0)); + Assert.That(t.TrailingToolToPivotLength, Is.EqualTo(rigid ? 0 : 0.5)); + Assert.That(t.Offset, Is.EqualTo(0.25)); + Assert.That(t.Length, Is.EqualTo(2.0)); + Assert.That(t.SectionWidths, Is.EqualTo(new[] { 1.0, 1.5, 0.5 })); + Assert.That(t.Width, Is.EqualTo(3.0).Within(1e-9)); + Assert.That(t.RearConnectorFromHitch, Is.Null); + }); + } + + [Test] + public void A_chain_of_tools_is_read_back_as_written() + { + var dir = Path.Combine(_root, "north40"); + var rig = JobRig.From(Store("rear")); + rig.Tools[0].RearConnectorFromHitch = 2.4; + rig.Tools.Add(new RigTool + { + ProfileName = "Roller", Connector = RigConnector.PreviousTool, Mount = ToolMount.Trailing, + TrailingHitchLength = 1.5, Width = 3, SectionWidths = { 3 }, + }); + JobJsonService.Save(new Job { FieldName = "north40", TaskName = "t1", Rig = rig }, dir); + + var back = JobJsonService.Load(dir, "t1")!.Rig; + Assert.That(back, Is.Not.Null); + Assert.That(back!.SameAs(rig), Is.True); + + // The names another program reads (Docs/FIELD_FILE_FORMATS.md). + var o = JsonNode.Parse(File.ReadAllText(JobJsonService.JobFilePath(dir, "t1")))!; + Assert.Multiple(() => + { + Assert.That((int)o["schemaVersion"]!, Is.EqualTo(1), "a new member does not change the version"); + Assert.That((double)o["rig"]!["vehicle"]!["rearThreePointHitch"]!, Is.EqualTo(1.2)); + Assert.That((string)o["rig"]!["tools"]![0]!["connector"]!, Is.EqualTo("RearThreePoint")); + Assert.That((string)o["rig"]!["tools"]![0]!["mount"]!, Is.EqualTo("RearFixed")); + Assert.That((double)o["rig"]!["tools"]![0]!["workingPointFromHitch"]!, Is.EqualTo(0.8)); + Assert.That((string)o["rig"]!["tools"]![1]!["connector"]!, Is.EqualTo("PreviousTool")); + Assert.That(o["rig"]!["tools"]![1]!["rearConnectorFromHitch"], Is.Null, "left out when the tool has none"); + }); + } + + [Test] + public void A_job_saved_before_has_no_machine_and_gets_none_written() + { + var dir = Path.Combine(_root, "north40"); + JobJsonService.Save(new Job { FieldName = "north40", TaskName = "old" }, dir); + Assert.That(JobJsonService.Load(dir, "old")!.Rig, Is.Null); + Assert.That(File.ReadAllText(JobJsonService.JobFilePath(dir, "old")), Does.Not.Contain("\"rig\"")); + } + + [Test] + public void The_active_job_keeps_the_machine_and_writes_only_a_change() + { + var svc = new JobService(() => _root); + var job = svc.CreateJob("north40", "drilling", ""); + var path = JobJsonService.JobFilePath(Path.Combine(_root, "north40"), job.TaskName); + var store = Store("rear"); + + svc.RecordRig(JobRig.From(store)); + Assert.That(JobJsonService.Load(Path.Combine(_root, "north40"), job.TaskName)!.Rig!.Tools[0].WorkingPointFromHitch, Is.EqualTo(0.8)); + + // The same machine again: no write (the file taken away stays away). + File.Delete(path); + svc.RecordRig(JobRig.From(store)); + Assert.That(File.Exists(path), Is.False, "the same machine is not written again"); + + // The operator moves the tractor's 3-point hitch: the job's copy follows. + store.Vehicle.ThreePointHitchLength = 1.4; + svc.RecordRig(JobRig.From(store)); + Assert.That(JobJsonService.Load(Path.Combine(_root, "north40"), job.TaskName)!.Rig!.Vehicle.RearThreePointHitch, Is.EqualTo(1.4)); + } + + [Test] + public void No_active_job_nothing_is_written() + { + var svc = new JobService(() => _root); + Assert.DoesNotThrow(() => svc.RecordRig(JobRig.From(Store("rear")))); + Assert.That(Directory.Exists(JobJsonService.JobsRoot(Path.Combine(_root, "north40"))), Is.False); + } + + [Test] + public void A_newer_builds_job_is_not_written() + { + var dir = Path.Combine(_root, "north40"); + var svc = new JobService(() => _root); + var job = svc.CreateJob("north40", "drilling", ""); + var path = JobJsonService.JobFilePath(dir, job.TaskName); + var o = JsonNode.Parse(File.ReadAllText(path))!; + o["schemaVersion"] = JobJsonService.CurrentSchemaVersion + 1; + File.WriteAllText(path, o.ToJsonString()); + var before = File.ReadAllText(path); + + svc.RecordRig(JobRig.From(Store("rear"))); + Assert.That(File.ReadAllText(path), Is.EqualTo(before)); + } +} diff --git a/Tests/AgOpenWeb.ViewModels.Tests/JobRigRecordingTests.cs b/Tests/AgOpenWeb.ViewModels.Tests/JobRigRecordingTests.cs new file mode 100644 index 000000000..734352130 --- /dev/null +++ b/Tests/AgOpenWeb.ViewModels.Tests/JobRigRecordingTests.cs @@ -0,0 +1,65 @@ +// AgOpenWeb +// Copyright (C) 2026 AgOpenWeb Contributors +// SPDX-License-Identifier: Apache-2.0 + +using AgOpenWeb.Models.Configuration; +using AgOpenWeb.Models.Job; +using NSubstitute; +using NUnit.Framework; + +namespace AgOpenWeb.ViewModels.Tests; + +/// +/// The active job keeps the machine it is worked with (#547): the view model hands the job service +/// the loaded vehicle and tool when a job becomes active and whenever a profile is loaded or saved. +/// +[TestFixture] +[NonParallelizable] // ConfigurationStore is a singleton +public class JobRigRecordingTests +{ + private MainViewModelBuilder _b = null!; + + [SetUp] + public void SetUp() + { + ConfigurationStore.SetInstance(new ConfigurationStore()); + var s = ConfigurationStore.Instance; + s.ActiveVehicleProfileName = "Fendt 312"; + s.Vehicle.ThreePointHitchLength = 1.2; + s.Tool.SetToolType("rear"); + s.Tool.WorkingPointFromHitch = 0.8; + _b = new MainViewModelBuilder(); + _b.Build(); + } + + private static bool IsLoaded(JobRig r) => + r.Vehicle.ProfileName == "Fendt 312" && r.Vehicle.RearThreePointHitch == 1.2 + && r.Tools.Count == 1 && r.Tools[0].WorkingPointFromHitch == 0.8; + + [Test] + public void A_job_that_becomes_active_gets_the_loaded_machine() + { + var job = new Job { FieldName = "north40", TaskName = "t1" }; + _b.JobService.ActiveJob.Returns(job); + _b.JobService.ActiveJobChanged += Raise.Event>(_b.JobService, job); + _b.JobService.Received(1).RecordRig(Arg.Is(r => IsLoaded(r))); + } + + [Test] + public void A_profile_loaded_or_saved_during_the_job_updates_it() + { + _b.JobService.ActiveJob.Returns(new Job { FieldName = "north40", TaskName = "t1" }); + _b.ConfigurationService.ProfileSaved += Raise.Event>(_b.ConfigurationService, "Fendt 312"); + _b.ConfigurationService.ProfileLoaded += Raise.Event>(_b.ConfigurationService, "Fendt 312"); + _b.JobService.Received(2).RecordRig(Arg.Is(r => IsLoaded(r))); + } + + [Test] + public void No_active_job_nothing_is_recorded() + { + _b.JobService.ActiveJob.Returns((Job?)null); + _b.ConfigurationService.ProfileSaved += Raise.Event>(_b.ConfigurationService, "Fendt 312"); + _b.JobService.ActiveJobChanged += Raise.Event>(_b.JobService, (Job?)null); + _b.JobService.DidNotReceive().RecordRig(Arg.Any()); + } +}