Leave a newly installed tower in manual: home at boot, capture only after auto is chosen

This commit is contained in:
pgdalmeida 2026-09-28 16:20:39 +02:00
parent da9a9867ed
commit 4b73199e8e
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
2 changed files with 66 additions and 21 deletions

View File

@ -103,9 +103,20 @@ one yes. In order:
6. builds and flashes the firmware (6) - **blocked** while any drivetrain value
is still a `; TODO`, because that axis would refuse to move
7. `test hardware`
8. the boot service ([deployment.md](deployment.md) section 4), if wanted
8. the boot service ([deployment.md](deployment.md) section 4), if wanted. A new
tower gets `--init`: it homes at every boot, then **waits in manual and
captures nothing** until someone chooses auto
9. asks whether someone is at the rig; only on a yes does it home the gimbal and
take a short capture - through the service, when the service is what homes it
take **one** picture (a snapshot), then leave it in manual - through the
service, when the service is what homes it
**Nothing starts capturing by itself.** Choose auto yourself: key `a` on the
dashboard, or `mode auto` over MQTT. Once the tower is proven, make it boot into
auto, as 111 does:
```bash
scripts/install-tower --answer service_args="--init --start"
```
Every question has a key, so any can be answered up front - `--answer
tower_name=north`, `--answer yaw.r_sense_ohm=0.075` - or from a file of

View File

@ -34,14 +34,14 @@ set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$here/.." && pwd)"
FW="$(cd "$REPO/.." && pwd)/firmware"
# The two overrides exist to rehearse a FRESH install with --dry-run on a tower
# The overrides exist to rehearse a FRESH install with --dry-run on a tower
# that is already configured, without its real files being in the way.
ETC="${INSTALL_TOWER_ETC:-/etc/fire_gimbal_control}"
HW="$ETC/hardware.ini"
CRED="$ETC/credentials.env"
INSTALL_LOG="$ETC/install.log"
CFG="${INSTALL_TOWER_CONFIG:-$REPO/config.ini}"
UNIT=/etc/systemd/system/fire-gimbal-control.service
UNIT="${INSTALL_TOWER_UNIT:-/etc/systemd/system/fire-gimbal-control.service}"
LOGDIR="${XDG_DATA_HOME:-$HOME/.local/share}/fire_gimbal_control/logs"
BIN="$REPO/build/fire_gimbal_control"
FGC_CONFIG="$REPO/build/fgc-config"
@ -186,6 +186,17 @@ py_serial() {
app_running() { "$FGC" status >/dev/null 2>&1; }
# One command line into the running program, whether it shows the dashboard or
# the plain console: the dashboard needs ':' to open its command line first.
app_cmd() {
local pane; pane="$(tmux list-panes -t "=${FGC_SESSION:-fgc}" -F '#{pane_id}' 2>/dev/null | head -1)"
[[ -n $pane ]] || return 1
if tmux capture-pane -p -t "$pane" | grep -q '╭'; then
tmux send-keys -t "$pane" ':'; sleep 0.5
fi
tmux send-keys -t "$pane" -l "$1"; tmux send-keys -t "$pane" Enter
}
# The arguments the running app was started with, so a restart puts it back the
# way it was. From the process itself, not its log: an older build writes no log
# file, and restarting tower 111 without its --init --start would have left it
@ -341,15 +352,8 @@ run_test() {
# The app holds the serial port, so a test runs INSIDE it when it is up, and
# as a one-shot run of the binary when it is not.
if app_running; then
local pane before after restore=""
pane="$(tmux list-panes -t "=${FGC_SESSION:-fgc}" -F '#{pane_id}' | head -1)"
local before after restore=""
before="$(ls -t "$LOGDIR"/test_*.log 2>/dev/null | head -1)"
app_cmd() { # one command line into the running program, dashboard or console
if tmux capture-pane -p -t "$pane" | grep -q '╭'; then
tmux send-keys -t "$pane" ':'; sleep 0.5
fi
tmux send-keys -t "$pane" -l "$1"; tmux send-keys -t "$pane" Enter
}
# Tests run in manual mode only - auto and pan refuse them. Note the mode,
# switch for the test, and put it back afterwards, pan's arguments and all.
app_cmd "mode"; sleep 1
@ -759,9 +763,13 @@ installed_args() {
[[ -r $UNIT ]] && sed -n 's|^ExecStart=.*/scripts/fgc start *||p' "$UNIT" | head -1
}
UNIT_TEXT=""
SVC_ARGS_DEFAULT="--init --start"
[[ -r $UNIT ]] && SVC_ARGS_DEFAULT="$(installed_args)"
[[ -z $SVC_ARGS_DEFAULT ]] && SVC_ARGS_DEFAULT=none
# A new tower homes at every boot but captures nothing until a person has chosen
# auto: --init, not --init --start. Once it is proven, the operator makes it boot
# into auto by re-running with --answer service_args="--init --start". A service
# of ours keeps the arguments it has; one that is not ours (a legacy tower's old
# program) gets the same safe default.
SVC_ARGS_DEFAULT="--init"
[[ -r $UNIT && -n $(installed_args) ]] && SVC_ARGS_DEFAULT="$(installed_args)"
if [[ -r $UNIT && $S_SVC_ENABLED == enabled && $S_LINGER == yes && -z ${ANS[service_args]+x} ]] &&
[[ "$(cat "$UNIT")" == "$(render_unit "$(installed_args)")" ]]; then
plan service skip "installed and enabled (fgc start ${SVC_ARGS_DEFAULT/#none/})"
@ -770,6 +778,8 @@ else
echo "The boot service starts the program at every boot. With --init it HOMES THE"
echo "GIMBAL UNATTENDED each time the tower powers up - which is what a tower in"
echo "service needs, since the firmware keeps no position across a power cycle."
echo "With --init alone it then WAITS IN MANUAL and captures nothing; add --start"
echo "(--init --start) to boot straight into auto once the tower is proven."
if ask_yn service "Install and enable it?" y; then
ask service_args "Arguments it starts the program with ('none' for none)" "$SVC_ARGS_DEFAULT"
svc_args="$REPLY_VAL"; [[ $svc_args == none ]] && svc_args=""
@ -808,6 +818,15 @@ put_back() {
local rc=$?
rm -rf "$WORK"
[[ $STOPPED_BY_US -eq 1 ]] && ! app_running || return $rc
# The service stopped at the start may no longer be the one installed now: on
# a legacy tower it was the old program's, and this run replaced its unit.
# Starting the new one here would home the gimbal with nobody asked - the
# step this installer only takes after a person at the rig says yes.
if [[ $STOPPED_SVC -eq 1 && ${CHANGED[*]:-} == *service* ]]; then
warn "the program is not running: the boot service was replaced by this run, and"
warn "starting it homes the gimbal. With someone at the rig: sudo systemctl start fire-gimbal-control"
return $rc
fi
warn "stopped before finishing - restarting the program as it was"
if [[ $STOPPED_SVC -eq 1 ]]; then sudo systemctl start fire-gimbal-control
else
@ -974,30 +993,39 @@ echo "Homing drives each axis into its endstops. On a new or changed drivetrain
echo "is the step that can break something, so it needs a person at the rig."
if [[ $TEST_RC != 0 ]]; then
warn "not offered: test hardware did not pass"
elif ask_yn home "Someone is at the rig with a hand on the power - home now and take a short capture?" n; then
elif ask_yn home "Someone is at the rig with a hand on the power - home now and take one picture?" n; then
stop_app
before="$(latest_log)"
# Through the service when the service is what homes it, so the tower ends
# up running under it; otherwise the same run by hand.
# up running under it; otherwise the same run by hand. Never --start here:
# the tower is left in manual, and auto is a person's choice (a service
# already set to --start is the exception - that choice was made).
if [[ $SVC_ON -eq 1 && " $(installed_args) " == *" --init "* ]]; then sudo systemctl start fire-gimbal-control
else ( cd "$REPO" && "$FGC" start --init --start ) > /dev/null; fi
else ( cd "$REPO" && "$FGC" start --init ) > /dev/null; fi
say "homing (this can take a few minutes)"
log=""; for _ in $(seq 1 30); do log="$(latest_log)"; [[ -n $log && $log != "$before" ]] && break; sleep 1; done
outdir=""; ok=0
outdir=""; ok=0; snapped=0
for _ in $(seq 1 420); do
grep -q 'Gimbal homed' "$log" 2>/dev/null && ok=1
outdir="$(sed -n 's/.*capture output dir: //p' "$log" 2>/dev/null | tail -1)"
# In manual, one snapshot proves the camera; in auto the sweep does.
if [[ $ok -eq 1 && $snapped -eq 0 ]] && grep -q 'Entering control loop' "$log" 2>/dev/null &&
! grep -q '\] mode auto' "$log" 2>/dev/null; then
sleep 2; app_cmd "snapshot" && snapped=1
fi
if [[ $ok -eq 1 && -n $outdir ]] && find "$outdir" -name '*.jxl' -newer "$log" 2>/dev/null | grep -q .; then ok=2; break; fi
app_running || break
sleep 1
done
grep -E 'homed .* travel|counts_per_deg|Gimbal homed|ERROR' "$log" 2>/dev/null | tail -6 | sed 's/^/ /'
case $ok in
2) HOMED="homed and captured ($(find "$outdir" -name '*.jxl' -newer "$log" | wc -l) image(s) in $outdir)"; say "$HOMED" ;;
2) HOMED="homed and captured ($(find "$outdir" -name '*.jxl' -newer "$log" | wc -l) image(s) in $outdir)"
[[ $snapped -eq 1 ]] && HOMED+="; left in manual - nothing more is captured until someone chooses auto"
say "$HOMED" ;;
1) HOMED="homed, but no image within the wait"; warn "$HOMED" ;;
*) HOMED="did not home - read the log above"; warn "$HOMED" ;;
esac
# Left running: that is the tower in service.
# Left running, in the mode it came up in.
fi
# Put the program back if it is not running. Not under the service unless it
@ -1038,6 +1066,12 @@ open=0
while read -r t; do [[ -n $t ]] && { echo " - hardware.ini: $t"; open=1; }; done < <(hw_todos)
[[ $HOMED == homed* ]] || { echo " - homing: $HOMED"; open=1; }
[[ $HOMED == homed* ]] && { echo " - calibration: run 'gimbal heading setup' to tie yaw to true north"; open=1; }
if [[ -r $UNIT && " $(installed_args) " != *" --start "* ]]; then
echo " - capture: the tower waits in MANUAL at every boot and captures nothing. Choose auto"
echo " yourself (key a, or 'mode auto'); once it is proven, make it boot into auto:"
echo " scripts/install-tower --answer service_args=\"--init --start\""
open=1
fi
[[ $(ini_get "$CFG" Features enable_mqtt 2>/dev/null) == true ]] &&
{ echo " - MQTT: confirm topics reach the broker (a tower with no broker runs degraded, reporting to nobody)"; open=1; }
echo " - reboot once and run: scripts/install-tower --verify (the USB preconditions must survive it)"