diff --git a/docs/tower-installation.md b/docs/tower-installation.md index ffbfe26..457fac0 100644 --- a/docs/tower-installation.md +++ b/docs/tower-installation.md @@ -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 diff --git a/scripts/install-tower b/scripts/install-tower index a57d98b..d234b76 100755 --- a/scripts/install-tower +++ b/scripts/install-tower @@ -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)"