Find a legacy tower's Leonardo and its old launcher, and refuse to install while the old program holds the port

This commit is contained in:
pgdalmeida 2026-09-28 16:36:41 +02:00
parent 4b73199e8e
commit 2d2b498ff2
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
3 changed files with 83 additions and 15 deletions

View File

@ -69,12 +69,15 @@ probe_board() {
# RP2040 running the gimbal firmware (arduino-pico) : "Raspberry_Pi_Pico" # RP2040 running the gimbal firmware (arduino-pico) : "Raspberry_Pi_Pico"
# RP2040 as shipped, still on MicroPython : "MicroPython_Board" # RP2040 as shipped, still on MicroPython : "MicroPython_Board"
# ATmega32U4 (Delta fleet) : "Arduino_LLC_Arduino_Leonardo" # ATmega32U4 (Delta fleet) : "Arduino_LLC_Arduino_Leonardo"
# the same, running a legacy tower's firmware : "Unknown_LattePanda_Leonardo"
probe_mcu() { probe_mcu() {
local d found=() local d found=()
for d in /dev/serial/by-id/*; do for d in /dev/serial/by-id/*; do
[[ -e $d ]] || continue [[ -e $d ]] || continue
case "$d" in case "$d" in
*Raspberry_Pi_Pico*|*MicroPython_Board*|*Arduino_LLC_Arduino*|*Pico*) found+=("$d") ;; *Raspberry_Pi_Pico*|*MicroPython_Board*|*Arduino_LLC_Arduino*|*Pico*) found+=("$d") ;;
# The Delta's onboard Leonardo, as a legacy tower's firmware names it.
*LattePanda_Leonardo*) found+=("$d") ;;
esac esac
done done
(( ${#found[@]} == 1 )) && { echo "${found[0]}"; return; } (( ${#found[@]} == 1 )) && { echo "${found[0]}"; return; }

View File

@ -307,6 +307,21 @@ set -euo pipefail
S_MCU="$(probe_mcu)" S_MCU="$(probe_mcu)"
S_MCU_N=$(grep -c . <<< "$S_MCU" || true) S_MCU_N=$(grep -c . <<< "$S_MCU" || true)
# Another program on the motor MCU's port - a legacy tower's old program, started
# by start_gimbal.service - would fight this one for it, and the flash would
# fail half-way. Our own program is fine: it is stopped before anything needs
# the port.
if (( S_MCU_N == 1 )) && [[ -e $S_MCU ]]; then
for pid in $(fuser "$(readlink -f "$S_MCU")" 2>/dev/null); do
args="$(ps -o args= -p "$pid" 2>/dev/null)"
[[ $args == */fire_gimbal_control* ]] && continue
die "the motor MCU's port is held by another program: pid $pid ($args).
Stop it for good before installing. On a legacy tower it is started by
start_gimbal.service from ~/scripts/startup_gimbal.sh, which also runs the picture
transfer: comment out the old program's line there, keep the transfer, then
sudo systemctl restart start_gimbal"
done
fi
S_CAM="$(probe_camera_id)" S_CAM="$(probe_camera_id)"
S_LPM="$(probe_camera_lpm_path)" S_LPM="$(probe_camera_lpm_path)"
S_USBFS="$(cat /sys/module/usbcore/parameters/usbfs_memory_mb 2>/dev/null || echo '?')" S_USBFS="$(cat /sys/module/usbcore/parameters/usbfs_memory_mb 2>/dev/null || echo '?')"
@ -326,7 +341,7 @@ S_MODE="none"
case "$S_MCU" in case "$S_MCU" in
*MicroPython*) S_MODE="factory MicroPython image" ;; *MicroPython*) S_MODE="factory MicroPython image" ;;
*Raspberry_Pi_Pico*|*Pico*) S_MODE="gimbal firmware" ;; *Raspberry_Pi_Pico*|*Pico*) S_MODE="gimbal firmware" ;;
*Arduino*) S_MODE="gimbal firmware (Leonardo)" ;; *Arduino*|*Leonardo*) S_MODE="firmware on the Leonardo" ;;
esac esac
lsusb -d 2e8a:0003 >/dev/null 2>&1 && S_MODE="in its bootloader" lsusb -d 2e8a:0003 >/dev/null 2>&1 && S_MODE="in its bootloader"

View File

@ -8,7 +8,7 @@
# bash survey-legacy-tower the machine, the old install, the camera # bash survey-legacy-tower the machine, the old install, the camera
# bash survey-legacy-tower --firmware also ask the old firmware for its # bash survey-legacy-tower --firmware also ask the old firmware for its
# registers, travel and north calibration: # registers, travel and north calibration:
# pauses the old service for ~20 s # pauses the old program for ~20 s
# #
# The report is printed and saved to ~/legacy-survey-<date>.txt. # The report is printed and saved to ~/legacy-survey-<date>.txt.
# #
@ -37,8 +37,9 @@ echo "memory : $(free -h | awk '/^Mem/ {print $2}')"
echo "internet : gitea:2222 $(reach git.ggs-solutions.eu 2222) github $(reach github.com 443) pypi $(reach pypi.org 443) platformio $(reach dl.registry.platformio.org 443)" echo "internet : gitea:2222 $(reach git.ggs-solutions.eu 2222) github $(reach github.com 443) pypi $(reach pypi.org 443) platformio $(reach dl.registry.platformio.org 443)"
hr "USB" hr "USB"
lsusb 2>/dev/null | grep -Ei '1ab2|2341|arduino|allied' || echo "(no Alvium or Arduino on lsusb)" lsusb 2>/dev/null | grep -viE 'root hub' | sed 's/^/ /'
ls /dev/serial/by-id/ 2>/dev/null | sed 's/^/by-id: /' ls /dev/serial/by-id/ 2>/dev/null | sed 's/^/by-id: /'
ls /dev/ttyACM* 2>/dev/null | sed 's/^/acm : /'
hr "Vimba X and the camera" hr "Vimba X and the camera"
ls -d /opt/VimbaX* 2>/dev/null || echo "no /opt/VimbaX*" ls -d /opt/VimbaX* 2>/dev/null || echo "no /opt/VimbaX*"
@ -49,11 +50,44 @@ for tool in /opt/VimbaX*/bin/ListCameras_VmbC /opt/VimbaX*/bin/ListCameras; do
break break
done done
hr "the old install" hr "the old install - how it runs"
systemctl is-enabled fire-gimbal-control 2>/dev/null | sed 's/^/service enabled: /' echo "-- systemd units that look like it (system and user):"
systemctl is-active fire-gimbal-control 2>/dev/null | sed 's/^/service active : /' { systemctl list-unit-files --no-legend 2>/dev/null; systemctl --user list-unit-files --no-legend 2>/dev/null; } |
systemctl cat fire-gimbal-control 2>/dev/null | grep -E '^(ExecStart|WorkingDirectory|User|Environment)=' | sed 's/^/ /' grep -iE 'gimbal|fire[-_]|fwt|tower|camera|zkms' | sed 's/^/ /' || echo " (none)"
for c in /opt/fire_gimbal_control/config.ini "$HOME"/*/config.ini; do for u in $(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' | grep -iE 'gimbal|fire[-_]|fwt|tower'); do
echo " -- $u: $(systemctl is-active "$u" 2>/dev/null)"
systemctl cat "$u" 2>/dev/null | grep -E '^(ExecStart|WorkingDirectory|User)=' | sed 's/^/ /'
# The script a legacy unit runs (start_gimbal.service -> startup_gimbal.sh):
# it starts the old program AND the picture transfer, so it matters which.
for f in $(systemctl cat "$u" 2>/dev/null | sed -n 's/^ExecStart=//p' | tr ' ' '\n' | grep -E '\.sh$'); do
[[ -r $f ]] || continue
echo " --- $f:"
grep -vE '^[[:space:]]*$' "$f" | head -25 | sed -E 's/(pass(word)?|pw)([=: ]).*/\1\3*****/I' | sed 's/^/ /'
for g in $(grep -oE '/[^ #&;]+\.sh' "$f" | sort -u); do
[[ -r $g && $g != "$f" ]] || continue
echo " --- $g (first lines):"
grep -vE '^[[:space:]]*$' "$g" | head -8 | sed -E 's/(pass(word)?|pw)([=: ]).*/\1\3*****/I' | sed 's/^/ /'
done
done
done
echo "-- processes that look like it:"
ps -eo pid,user,etime,args 2>/dev/null | grep -iE 'gimbal|fire_|fwt|vimba|zkms|tower|inotifywait' | grep -vE 'grep|survey-legacy|avahi' | sed 's/^/ /' || echo " (none)"
echo "-- what holds the serial ports:"
for d in /dev/ttyACM*; do
[[ -e $d ]] || continue
pids="$(fuser "$d" 2>/dev/null | tr -s ' ')"
if [[ -n ${pids// /} ]]; then for p in $pids; do echo " $d: pid $p $(ps -o args= -p "$p" 2>/dev/null)"; done
else echo " $d: free"; fi
done
echo "-- other ways it could start:"
crontab -l 2>/dev/null | grep -v '^#' | sed 's/^/ cron: /'
ls ~/.config/autostart/ 2>/dev/null | sed 's/^/ autostart: /'
tmux ls 2>/dev/null | sed 's/^/ tmux: /'
screen -ls 2>/dev/null | grep -E '^\s+[0-9]' | sed 's/^/ screen: /'
docker ps --format '{{.Names}} {{.Image}} {{.Status}}' 2>/dev/null | sed 's/^/ docker: /'
echo "-- home directory:"
ls -1 "$HOME" 2>/dev/null | sed 's/^/ /'
for c in /opt/fire_gimbal_control/config.ini "$HOME"/*/config.ini "$HOME"/*/*/config.ini; do
[[ -r $c ]] || continue [[ -r $c ]] || continue
echo "-- $c (passwords masked)" echo "-- $c (passwords masked)"
grep -vE '^[[:space:]]*(;|#|$)' "$c" | sed -E 's/^(mqtt_pw[[:space:]]*=).*/\1 *****/' | sed 's/^/ /' grep -vE '^[[:space:]]*(;|#|$)' "$c" | sed -E 's/^(mqtt_pw[[:space:]]*=).*/\1 *****/' | sed 's/^/ /'
@ -62,15 +96,31 @@ done
[[ $FIRMWARE -eq 1 ]] || { hr "firmware"; echo "not asked (run with --firmware)"; echo; echo "saved: $OUT"; exit 0; } [[ $FIRMWARE -eq 1 ]] || { hr "firmware"; echo "not asked (run with --firmware)"; echo; echo "saved: $OUT"; exit 0; }
hr "the old firmware" hr "the old firmware"
PORT="$(ls /dev/serial/by-id/*Arduino* 2>/dev/null | head -1)" # The Leonardo by name: an Arduino-branded one, or the LattePanda's own ("Unknown_LattePanda_Leonardo").
[[ -n $PORT ]] || { echo "no Arduino on /dev/serial/by-id"; exit 1; } PORT="$(ls /dev/serial/by-id/*Leonardo* /dev/serial/by-id/*Arduino* 2>/dev/null | head -1)"
WAS_ACTIVE="$(systemctl is-active fire-gimbal-control 2>/dev/null || true)" [[ -n $PORT ]] || { echo "no Leonardo on /dev/serial/by-id - see the USB section above"; exit 1; }
if [[ $WAS_ACTIVE == active ]]; then echo "port: $PORT"
echo "pausing the old service (it holds the port)" # Something may hold it: the old program. The two units known to start it are
sudo systemctl stop fire-gimbal-control # paused here and started again at the end - start_gimbal.service (the legacy
trap 'echo "restarting the old service"; sudo systemctl start fire-gimbal-control' EXIT # towers' launcher, which also runs the picture transfer) and
# fire-gimbal-control. Anything else is named, and left for a person to stop.
PAUSED=()
for u in start_gimbal fire-gimbal-control; do
[[ $(systemctl is-active "$u" 2>/dev/null) == active ]] || continue
echo "pausing $u.service (it runs the program that holds the port)"
sudo systemctl stop "$u" && PAUSED+=("$u")
done
if (( ${#PAUSED[@]} )); then
trap 'for u in "${PAUSED[@]}"; do echo "restarting $u.service"; sudo systemctl start "$u"; done' EXIT
sleep 2 sleep 2
fi fi
holders="$(fuser "$(readlink -f "$PORT")" 2>/dev/null | tr -s ' ')"
if [[ -n ${holders// /} ]]; then
echo "the port is in use by:"
for p in $holders; do echo " pid $p $(ps -o args= -p "$p" 2>/dev/null)"; done
echo "stop that program first (it is the old one, reading the same port), then run this again."
exit 1
fi
python3 - "$PORT" <<'PYEOF' python3 - "$PORT" <<'PYEOF'
import os, sys, termios, time, math import os, sys, termios, time, math