diff --git a/README.md b/README.md index 9a25363..a3a8663 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,18 @@ side drives which balloon, pitch sign, hover level) is measured on the ship by the **setup wizard**, which the install flow takes you straight into: 1. **Wiring** — the wizard pulses each free side; you say which burner fired. -2. **Sign test** — the ship lifts off by itself, finds its own lift power, then - *rocks* between front and aft pushes. The sign comes from - `rate(+push) − rate(−push)`, which cancels any constant lean exactly, so a - ship hanging crooked measures fine — that lean **is** the thing being - measured. It then learns the differential trim that levels the ship, saves - it as a warm-start, and descends to hover. No config editing, no freefall. - Takes 2–4 minutes. +2. **Sign test** — the ship lifts off by itself, finds its own lift power, + waits until buoyancy has righted it and the rates have steadied, and only + *then* takes its attitude reference. It rocks between front and aft pushes; + the sign comes from `rate(+push) − rate(−push)`, which cancels any constant + lean exactly, so a ship hanging crooked measures fine — that lean **is** + what is being measured. It then learns the differential trim that levels + the ship, saves it as a warm-start, and descends to hover. No config + editing, no freefall. Takes 2–4 minutes. + +A hull built to fly usually lies over on the ground; that is fine and +expected. The ground attitude is never used as a reference — nothing is +measured until the ship is airborne and steady. After that the ship is ready: `FLY` from the menu or `ENGAGE` from the cockpit console. Pitch and altitude keep self-learning in flight. The wizard can be diff --git a/files/autopilot/autopilot/main.lua b/files/autopilot/autopilot/main.lua index 22df7dd..d46e599 100644 --- a/files/autopilot/autopilot/main.lua +++ b/files/autopilot/autopilot/main.lua @@ -154,8 +154,12 @@ end -- mellom de to dyttene kansellerer enhver konstant skjev-drift eksakt. Og fordi vi VEKSLER mellom -- dyttene gynger skipet rundt utgangspunktet i stedet for aa akkumulere tilt -- selvbegrensende. -- +-- BAKKE-ATTITYDEN BRUKES ALDRI: skipet er bygget for aa FLY, ikke for aa staa, saa paa bakken +-- ligger det gjerne paa siden. Vi venter til oppdriften har reist det opp OG ratene har roet seg +-- foer attityde-referansen settes -- ellers ville skipets egen oppretting blitt lest som respons. +-- -- 1/5 trapper seg luftbaaren fra bunnen (laerer ~hover paa kjoepet, ingen fast liftLevel) --- 2/5 klatrer kontrollert til trygg maalehoyde (fart-P-hold rundt laert hover) +-- 2/5 klatrer til maalehoyde, lar skipet RETTE SEG OPP, setter attityde-referanse i lufta -- 3/5 GYNGE-PROBE: front-dytt <-> bak-dytt, 2 sykluser; aksen som svarer blir pitchIndex. -- Svak respons -> auto sterkere dytt. Maaler ALLE gimbal-akser (byggeretning likegyldig) -- 4/5 BALANSE: med fortegnet kjent laeres differanse-trimmen som holder skipet vannrett @@ -196,12 +200,21 @@ local function runSigntest() if type(a) ~= "table" or type(a[1]) ~= "number" then return nil end if NAX == 0 then NAX = math.min(3, #a) - a0 = {} - for i = 1, NAX do axRings[i] = utils.newRing(1.0); a0[i] = a[i] end + for i = 1, NAX do axRings[i] = utils.newRing(1.0) end end return a end + -- Attityde-referansen settes AaLDRI paa bakken: et skip bygget for aa FLY staar ikke stoett, + -- det ligger gjerne paa siden. Den attityden er meningsloes -- og oppdriftens egen oppretting + -- etter avgang ville blitt lest som baade "tilt" og "rate-respons". Vi setter referansen + -- FOERST naar skipet har rettet seg opp i lufta og ratene har roet seg. + local function setAttitudeRef() + if not lastAx then return end + a0 = {} + for i = 1, NAX do a0[i] = lastAx[i] end + end + local function axRates() local r = {} for i = 1, NAX do r[i] = axRings[i]:slope() end @@ -338,13 +351,32 @@ local function runSigntest() local okC, cErr = recenter("2/5", "initial climb") if not okC then return bail(abortMsg(cErr)) end - local tSettle = now() - while now() - tSettle < 8 do - velTick(0, 0, "settle0") - hud("2/5", "Settling (learning true hover)...", { - string.format("hover ~%.2f %.0fs left", hover, math.max(0, 8 - (now() - tSettle))), + + -- La skipet RETTE SEG OPP i lufta foer vi maaler noe som helst. Et flybygget skrog ligger + -- gjerne paa siden paa bakken; oppdriften reiser det opp av seg selv naar det kommer i lufta. + -- Vi venter til ALLE akse-ratene har roet seg -- og setter FOERST DA attityde-referansen. + local tR, calmR, worst = now(), 0, 0 + while now() - tR < 120 and now() < deadline do + velTick(0, 0, "righting") + worst = 0 + for i = 1, NAX do worst = math.max(worst, math.abs(axRings[i]:slope())) end + if worst < 0.5 then calmR = calmR + cfg.dt else calmR = 0 end + hud("2/5", "Letting the ship right itself...", { + "(a flight-built hull lies over on the ground;", + " buoyancy levels it once airborne)", + string.format("worst rate %.2f deg/s steady %.1f/5.0 s", worst, calmR), + string.format("hover ~%.2f", hover), }) + if calmR >= 5.0 then break end + local rel = est.altitude - h0 + if rel < BAND_LO or rel > BAND_HI then + local okD, dErr = recenter("2/5", "altitude drift") + if not okD then return bail(abortMsg(dErr)) end + end end + setAttitudeRef() -- NAA er referansen meningsfull: skipets faktiske flyattityde + local settled = {} + for i = 1, NAX do settled[i] = (lastAx and lastAx[i]) or 0 end -- rate-stabil maaling PER AKSE: hold hoyden (vDes=0) med gitt differanse til ratene roer -- seg. Returnerer tabell {avgRate per akse}. Lag-bevisst: minHold >= loft-etterslepet. @@ -476,6 +508,9 @@ local function runSigntest() term.clear(); term.setCursorPos(1, 1) print("==== SIGN TEST: SUCCESS ====") print(string.format("pitch = gimbal axis a%d, sign %+d", axis, sign)) + local sline = "" + for i = 1, NAX do sline = sline .. string.format(" a%d %+.0f", i, settled[i]) end + print("airborne attitude:" .. sline) print(string.format("balance trim %+.2f (saved warm-start)", 2 * levelTrim)) if balanced then print("Ship levelled out under this trim.") @@ -483,6 +518,12 @@ local function runSigntest() print(string.format("NOTE: still %+.0f deg off level -- limited", pitchNow)) print("pitch authority. FLY keeps learning it.") end + if math.abs(settled[axis]) > 30 then + print("") + print("WARNING: it settled far from level even") + print("with balanced lift -- the hull may not be") + print("righting fully. Check balloon spacing.") + end print(string.format("hover ~%.1f (saved as baseline)", hover)) print("") print("Ship is holding hover. Ready to FLY.") @@ -797,9 +838,12 @@ local function runSetup() print("Two steps, fully guided:") print(" 1) Wiring -- map front/aft burner sides") print(" 2) Sign test -- short auto-flight that") - print(" measures pitch response + hover") + print(" measures pitch response + balance") print("") - print("Ship must be ON THE GROUND, open sky above.") + print("Just needs open sky above. It's FINE if the") + print("ship lies over on the ground -- it rights") + print("itself airborne, and nothing is measured") + print("until it has.") print("") write("[Enter] = start, q = cancel: ") if read():lower() == "q" then return end @@ -811,10 +855,11 @@ local function runSetup() term.clear(); term.setCursorPos(1, 1) print("==== STEP 2: SIGN TEST ====") print("") - print("The ship now lifts off BY ITSELF, gives") - print("the front balloon a small push, verifies") - print("the response both ways, and comes back") - print("down to hover. Takes 1-3 minutes.") + print("The ship lifts off BY ITSELF, waits until") + print("it has righted and steadied in the air,") + print("rocks front/aft to find the pitch axis,") + print("learns the trim that levels it, then comes") + print("back down to hover. Takes 2-4 minutes.") print("") write("[Enter] = lift off, q = cancel: ") if read():lower() == "q" then return end