198 lines
9.0 KiB
Bash
Executable File
198 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# survey-legacy-tower - read what a legacy tower (the old host program and the
|
|
# TMC-API Arduino sketch) is running, BEFORE it is migrated, so its
|
|
# hardware.ini is written from its own values rather than another tower's.
|
|
# Run it ON the legacy tower. Changes nothing on disk.
|
|
#
|
|
# bash survey-legacy-tower the machine, the old install, the camera
|
|
# bash survey-legacy-tower --firmware also ask the old firmware for its
|
|
# registers, travel and north calibration:
|
|
# pauses the old program for ~20 s
|
|
#
|
|
# The report is printed and saved to ~/legacy-survey-<date>.txt.
|
|
#
|
|
# The firmware part talks the old sketch's own serial protocol:
|
|
# ? endstop positions and the travel between them (set by its last home)
|
|
# y its north calibration from EEPROM (the same `y` the old host sends
|
|
# after every home - it reloads the stored value, it stores nothing)
|
|
# id<n> read TMC5160 register n (write-only ones answer from the library's copy)
|
|
#
|
|
set -uo pipefail
|
|
|
|
FIRMWARE=0
|
|
[[ ${1:-} == --firmware ]] && FIRMWARE=1
|
|
OUT="$HOME/legacy-survey-$(date +%Y%m%d-%H%M%S).txt"
|
|
exec > >(tee "$OUT") 2>&1
|
|
|
|
hr() { printf '\n== %s ==\n' "$*"; }
|
|
reach() { timeout 6 bash -c "</dev/tcp/$1/$2" 2>/dev/null && echo "ok" || echo "NO"; }
|
|
|
|
hr "machine"
|
|
echo "board : $(tr -d '\0' < /sys/class/dmi/id/product_name 2>/dev/null || echo '?')"
|
|
echo "os : $(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME") ($(uname -r))"
|
|
echo "user : $USER groups: $(id -nG)"
|
|
echo "disk / : $(df -h / | awk 'NR==2 {print $4 " free of " $2}')"
|
|
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)"
|
|
|
|
hr "USB"
|
|
lsusb 2>/dev/null | grep -viE 'root hub' | sed 's/^/ /'
|
|
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"
|
|
ls -d /opt/VimbaX* 2>/dev/null || echo "no /opt/VimbaX*"
|
|
for tool in /opt/VimbaX*/bin/ListCameras_VmbC /opt/VimbaX*/bin/ListCameras; do
|
|
[[ -x $tool ]] || continue
|
|
cti="$(dirname "$(dirname "$tool")")/cti"
|
|
GENICAM_GENTL64_PATH="$cti" "$tool" 2>/dev/null | grep -E 'Camera (ID|Name)|Model|Transport Layer Path' | sed 's/^/ /'
|
|
break
|
|
done
|
|
|
|
hr "the old install - how it runs"
|
|
echo "-- systemd units that look like it (system and user):"
|
|
{ systemctl list-unit-files --no-legend 2>/dev/null; systemctl --user list-unit-files --no-legend 2>/dev/null; } |
|
|
grep -iE 'gimbal|fire[-_]|fwt|tower|camera|zkms' | sed 's/^/ /' || echo " (none)"
|
|
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
|
|
echo "-- $c (passwords masked)"
|
|
grep -vE '^[[:space:]]*(;|#|$)' "$c" | sed -E 's/^(mqtt_pw[[:space:]]*=).*/\1 *****/' | sed 's/^/ /'
|
|
done
|
|
|
|
[[ $FIRMWARE -eq 1 ]] || { hr "firmware"; echo "not asked (run with --firmware)"; echo; echo "saved: $OUT"; exit 0; }
|
|
|
|
hr "the old firmware"
|
|
# The Leonardo by name: an Arduino-branded one, or the LattePanda's own ("Unknown_LattePanda_Leonardo").
|
|
PORT="$(ls /dev/serial/by-id/*Leonardo* /dev/serial/by-id/*Arduino* 2>/dev/null | head -1)"
|
|
[[ -n $PORT ]] || { echo "no Leonardo on /dev/serial/by-id - see the USB section above"; exit 1; }
|
|
echo "port: $PORT"
|
|
# Something may hold it: the old program. The two units known to start it are
|
|
# paused here and started again at the end - start_gimbal.service (the legacy
|
|
# 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
|
|
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'
|
|
import os, sys, termios, time, math
|
|
|
|
port = sys.argv[1]
|
|
fd = os.open(port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
|
|
a = termios.tcgetattr(fd)
|
|
a[0] = 0; a[1] = 0; a[2] = termios.CS8 | termios.CREAD | termios.CLOCAL; a[3] = 0
|
|
a[4] = a[5] = termios.B115200
|
|
termios.tcsetattr(fd, termios.TCSANOW, a)
|
|
buf = b""
|
|
|
|
def lines_for(seconds):
|
|
global buf
|
|
out, end = [], time.time() + seconds
|
|
while time.time() < end:
|
|
try:
|
|
buf += os.read(fd, 4096)
|
|
except BlockingIOError:
|
|
time.sleep(0.02)
|
|
while b"\n" in buf:
|
|
line, buf = buf.split(b"\n", 1)
|
|
out.append(line.decode(errors="replace").strip())
|
|
return out
|
|
|
|
def ask(cmd, seconds=0.8):
|
|
lines_for(0.2) # drop what was already streaming
|
|
os.write(fd, cmd.encode() + b"\n")
|
|
got = lines_for(seconds)
|
|
return [l for l in got if l and not l.startswith("$")], [l for l in got if l.startswith("$")]
|
|
|
|
_, status = ask("", 1.0)
|
|
print("status line :", status[-1] if status else "(none - is this the legacy firmware?)")
|
|
|
|
reply, _ = ask("?")
|
|
print("? (endstops) :", " | ".join(reply) or "(no answer)")
|
|
nums = [int(x) for x in reply if x.lstrip("-").isdigit()]
|
|
if len(nums) >= 3:
|
|
r, l = nums[0], nums[1]
|
|
print(f" right {r} left {l} travel {abs(l - r)} counts = {abs(l - r) / 528.253966:.1f} deg at 528.25 counts/deg")
|
|
|
|
reply, _ = ask("y")
|
|
print("y (north) :", " | ".join(reply) or "(no answer)")
|
|
|
|
REGS = [("GCONF", 0x00), ("GLOBALSCALER", 0x0B), ("IHOLD_IRUN", 0x10), ("TPOWERDOWN", 0x11),
|
|
("XACTUAL", 0x21), ("AMAX", 0x26), ("VMAX", 0x27), ("DMAX", 0x28), ("SW_MODE", 0x34),
|
|
("ENCMODE", 0x38), ("X_ENC", 0x39), ("ENC_CONST", 0x3A), ("ENC_DEVIATION", 0x3D),
|
|
("CHOPCONF", 0x6C), ("COOLCONF", 0x6D), ("PWMCONF", 0x70)]
|
|
vals = {}
|
|
print("registers :")
|
|
for name, n in REGS:
|
|
reply, _ = ask(f"id{n}")
|
|
v = None
|
|
for x in reply:
|
|
if x.startswith("READ"):
|
|
try: v = int(x.split(",")[-1])
|
|
except ValueError: pass
|
|
vals[name] = v
|
|
shown = "(no answer)" if v is None else f"{v} = 0x{v & 0xFFFFFFFF:08X}"
|
|
print(f" {name:14s} 0x{n:02X} {shown}")
|
|
|
|
gs, ii = vals.get("GLOBALSCALER"), vals.get("IHOLD_IRUN")
|
|
if ii is not None:
|
|
irun, ihold = (ii >> 8) & 0x1F, ii & 0x1F
|
|
scale = (gs or 256) / 256.0
|
|
print(f"current : IRUN {irun}, IHOLD {ihold}, GLOBALSCALER {gs if gs else '0 (=256, full)'}")
|
|
for rs in (0.075, 0.05):
|
|
i_run = scale * (irun + 1) / 32 * 0.325 / rs / math.sqrt(2) * 1000
|
|
print(f" with a {rs * 1000:.0f} mOhm sense resistor: run {i_run:.0f} mA RMS"
|
|
" (read the resistor off the board before trusting either)")
|
|
os.close(fd)
|
|
PYEOF
|
|
echo
|
|
echo "saved: $OUT"
|