Start the program at boot through fgc, so the dashboard still works
This commit is contained in:
parent
12b071c668
commit
53fa96fb54
|
|
@ -221,37 +221,53 @@ point at a different lockfile.
|
|||
|
||||
## 7. Install the systemd service
|
||||
|
||||
An **alternative** to section 6b, not a companion to it: systemd gives you start-at-boot and
|
||||
restart-on-crash, but no attachable dashboard — you get `journalctl` instead. Pick one. If you run both by
|
||||
accident the second one to start fails on the single-instance lock (section 6c), so the failure is loud
|
||||
rather than a pair of processes corrupting each other's state.
|
||||
`scripts/install-tower` does this (it asks first, and shows the unit it will
|
||||
write). The unit is the template `scripts/fire-gimbal-control.service`, and it
|
||||
runs the program **through `scripts/fgc`, inside tmux** - so a tower started at
|
||||
boot still has its dashboard (`scripts/fgc attach`), and `fgc status` / `peek` /
|
||||
`stop` work from any ssh session. It is not an alternative to the tmux workflow
|
||||
any more; it is how that workflow starts at boot.
|
||||
|
||||
By hand, fill in `@USER@`, `@REPO@` and `@ARGS@` (normally `--init --start`) and:
|
||||
|
||||
```bash
|
||||
sudo cp scripts/fire-gimbal-control.service /etc/systemd/system/
|
||||
sudoedit /etc/systemd/system/fire-gimbal-control.service
|
||||
sudo cp fire-gimbal-control.service /etc/systemd/system/
|
||||
sudo loginctl enable-linger ggs # NOT optional - see below
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable fire-gimbal-control
|
||||
```
|
||||
|
||||
Confirm/adjust these lines:
|
||||
**Lingering is required.** Without it `/run/user/<uid>` does not exist until
|
||||
someone logs in, so a program started at boot takes its single-instance lock in
|
||||
`/tmp`; a later ssh session looks in `/run/user`, finds nothing, reports "not
|
||||
running", and a second copy could start beside the first.
|
||||
|
||||
```ini
|
||||
User=ggs
|
||||
WorkingDirectory=/opt/fire_gimbal_control
|
||||
ExecStart=/opt/fire_gimbal_control/fire_gimbal_control --start
|
||||
Environment=FGC_CONFIG=/opt/fire_gimbal_control/config.ini
|
||||
EnvironmentFile=/etc/fire_gimbal_control/credentials.env
|
||||
```
|
||||
**`--init` homes the gimbal unattended at every boot.** That is what a tower in
|
||||
service needs - the firmware keeps no position across a power cycle, so without
|
||||
it the gimbal sits un-homed and captures nothing - but it is a deliberate choice,
|
||||
and the installer asks for the arguments.
|
||||
|
||||
(Use `ExecStart=... --init --start` for the first power-on if endstops must be found at boot.)
|
||||
MQTT credentials go in `/etc/fire_gimbal_control/credentials.env`
|
||||
(`FGC_MQTT_USER=` / `FGC_MQTT_PW=`, mode 0640 root:ggs). `fgc` reads it inside
|
||||
the tmux pane rather than taking it from the unit's environment: a tmux server
|
||||
that already exists hands a new session its own environment, not the caller's.
|
||||
|
||||
## 8. Enable and verify
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now fire-gimbal-control
|
||||
systemctl status fire-gimbal-control
|
||||
journalctl -u fire-gimbal-control -f # live logs
|
||||
sudo systemctl start fire-gimbal-control
|
||||
scripts/fgc status # running, lockfile under /run/user/<uid>
|
||||
scripts/install-tower --verify # every check, including test hardware
|
||||
```
|
||||
|
||||
`[verified]` on tower 121, 2026-09-23: the service came up by itself after a
|
||||
reboot with nobody logged in, with its lock under `/run/user/1000`; `fgc status`
|
||||
and the dashboard worked from a later ssh session; credentials reached the
|
||||
program intact (a password with `$`, a quote and a space); `systemctl stop` shut
|
||||
it down cleanly; and `install-tower --verify` passed after the reboot, USB
|
||||
preconditions included. 121 runs it with no arguments, so it does not home at
|
||||
boot - see the note on `--init` above.
|
||||
|
||||
Confirm end to end:
|
||||
- Images accumulating under the configured `[Paths] output_dir` (default
|
||||
`~ggs/.local/share/fire_gimbal_control/images/RGB/`).
|
||||
|
|
|
|||
|
|
@ -954,5 +954,6 @@ checks that the two files load together, and changes nothing.
|
|||
| Endstop survives homing | inspect the stop after a home; the axis must stall against it, not push through | ✅ at 189 mA, after failing at full torque |
|
||||
| Degree scale derived | startup logs `counts_per_deg` agreeing with the homed travel | ✅ 3552.87, saved with `;prev` history |
|
||||
| Images land | `.jxl` files appear under the configured output dir | ❌ needs a capture run |
|
||||
| Survives a disconnect | `scripts/fgc start`, close ssh, reconnect, `scripts/fgc status` | ❌ not attempted |
|
||||
| Survives a disconnect | `scripts/fgc start`, close ssh, reconnect, `scripts/fgc status` | ✅ runs under the boot service, independent of any ssh session |
|
||||
| Starts at boot | reboot with nobody logged in; `scripts/fgc status` from a later ssh | ✅ 2026-09-23, lock under `/run/user/1000` (lingering on) |
|
||||
| Reports to a broker | topics arrive at the configured broker | ❌ no broker on this tower yet |
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
# systemd unit template for Fire Gimbal Control.
|
||||
# Copy to /etc/systemd/system/, adjust the paths/user, then:
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now fire-gimbal-control
|
||||
# systemd unit for Fire Gimbal Control - a TEMPLATE. scripts/install-tower fills
|
||||
# in the @...@ fields and installs it; to do it by hand, replace them and copy it
|
||||
# to /etc/systemd/system/, then:
|
||||
# sudo loginctl enable-linger <user> # see below - not optional
|
||||
# sudo systemctl daemon-reload && sudo systemctl enable fire-gimbal-control
|
||||
#
|
||||
# Credentials are passed via the environment (not the config file). Prefer an
|
||||
# EnvironmentFile with 0600 perms over inline Environment= lines.
|
||||
# The unit runs the program through scripts/fgc, inside tmux, so a tower started
|
||||
# at boot still has the dashboard: `scripts/fgc attach` from any ssh session.
|
||||
# fgc returns once the program is up, hence oneshot + RemainAfterExit; stopping
|
||||
# the unit runs `fgc stop`, which shuts the camera and motors down cleanly.
|
||||
#
|
||||
# Lingering: without it /run/user/<uid> does not exist until someone logs in, so
|
||||
# a program started at boot takes its single-instance lock in /tmp, and a later
|
||||
# ssh session looks in /run/user, finds nothing, and `fgc status` says it is not
|
||||
# running - while a second copy could start beside it.
|
||||
#
|
||||
# @ARGS@ is normally `--init --start`: the firmware keeps no position across a
|
||||
# power cycle, so without --init the gimbal sits un-homed and captures nothing.
|
||||
# --init also means the gimbal HOMES UNATTENDED at every boot.
|
||||
#
|
||||
# MQTT credentials: /etc/fire_gimbal_control/credentials.env (FGC_MQTT_USER,
|
||||
# FGC_MQTT_PW), read by fgc inside the tmux pane rather than passed through this
|
||||
# unit - a tmux server that already exists would not hand a new session the
|
||||
# unit's environment.
|
||||
|
||||
[Unit]
|
||||
Description=Fire Gimbal Control
|
||||
|
|
@ -12,17 +29,14 @@ After=network-online.target
|
|||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ggs
|
||||
WorkingDirectory=/opt/fire_gimbal_control
|
||||
# --init is REQUIRED, not a convenience: the firmware keeps no state across a
|
||||
# power cycle, so both axes boot into RESET and refuse to move until homed. Without
|
||||
# it the service comes up and sits there, capturing nothing, with no obvious cause.
|
||||
ExecStart=/opt/fire_gimbal_control/fire_gimbal_control --init --start
|
||||
Environment=FGC_CONFIG=/opt/fire_gimbal_control/config.ini
|
||||
# EnvironmentFile=/etc/fire_gimbal_control/credentials.env # FGC_MQTT_USER / FGC_MQTT_PW
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
User=@USER@
|
||||
WorkingDirectory=@REPO@
|
||||
ExecStart=@REPO@/scripts/fgc start @ARGS@
|
||||
ExecStop=@REPO@/scripts/fgc stop
|
||||
TimeoutStartSec=120
|
||||
TimeoutStopSec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
|||
|
|
@ -621,34 +621,32 @@ fi
|
|||
plan test run "test hardware (moves nothing)"
|
||||
plan homing ask "asked at the end: home the gimbal and take a short capture"
|
||||
|
||||
UNIT_TEXT="[Unit]
|
||||
Description=Fire Gimbal Control
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
# fgc runs the program in a tmux session, so 'scripts/fgc attach' still opens
|
||||
# the dashboard. oneshot + RemainAfterExit: fgc start returns once it is up.
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
User=$USER
|
||||
WorkingDirectory=$REPO
|
||||
# --init homes at every start: the firmware keeps no position across a power cycle.
|
||||
ExecStart=$REPO/scripts/fgc start --init --start
|
||||
ExecStop=$REPO/scripts/fgc stop
|
||||
TimeoutStartSec=120
|
||||
TimeoutStopSec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target"
|
||||
if [[ -r $UNIT ]] && [[ "$(cat "$UNIT")" == "$UNIT_TEXT" ]] && [[ $S_SVC_ENABLED == enabled && $S_LINGER == yes ]]; then
|
||||
plan service skip "installed and enabled"
|
||||
# The unit is scripts/fire-gimbal-control.service with its @...@ fields filled.
|
||||
render_unit() {
|
||||
sed -e "s|@USER@|$USER|g" -e "s|@REPO@|$REPO|g" -e "s|@ARGS@|$1|g" \
|
||||
"$REPO/scripts/fire-gimbal-control.service"
|
||||
}
|
||||
# What an installed unit starts the program with, so a re-run keeps it.
|
||||
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
|
||||
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/})"
|
||||
else
|
||||
echo
|
||||
echo "The boot service starts the program at every boot WITH --init: it homes the"
|
||||
echo "gimbal unattended each time the tower powers up."
|
||||
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."
|
||||
if ask_yn service "Install and enable it?" y; then
|
||||
plan service change "write $UNIT, enable it, enable lingering for $USER"
|
||||
ask service_args "Arguments it starts the program with ('none' for none)" "$SVC_ARGS_DEFAULT"
|
||||
svc_args="$REPLY_VAL"; [[ $svc_args == none ]] && svc_args=""
|
||||
UNIT_TEXT="$(render_unit "$svc_args")"
|
||||
plan service change "write $UNIT (fgc start $svc_args), enable it, enable lingering for $USER"
|
||||
else plan service skip "declined"; fi
|
||||
fi
|
||||
|
||||
|
|
@ -820,7 +818,9 @@ if [[ $TEST_RC != 0 ]]; then
|
|||
elif ask_yn home "Someone is at the rig with a hand on the power - home now and take a short capture?" n; then
|
||||
stop_app
|
||||
before="$(latest_log)"
|
||||
if [[ $SVC_ON -eq 1 ]]; then sudo systemctl start fire-gimbal-control
|
||||
# Through the service when the service is what homes it, so the tower ends
|
||||
# up running under it; otherwise the same run by hand.
|
||||
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
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue