#!/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 service 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 -Ei '1ab2|2341|arduino|allied' || echo "(no Alvium or Arduino on lsusb)"
ls /dev/serial/by-id/ 2>/dev/null | sed 's/^/by-id: /'

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"
systemctl is-enabled fire-gimbal-control 2>/dev/null | sed 's/^/service enabled: /'
systemctl is-active fire-gimbal-control 2>/dev/null | sed 's/^/service active : /'
systemctl cat fire-gimbal-control 2>/dev/null | grep -E '^(ExecStart|WorkingDirectory|User|Environment)=' | sed 's/^/  /'
for c in /opt/fire_gimbal_control/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"
PORT="$(ls /dev/serial/by-id/*Arduino* 2>/dev/null | head -1)"
[[ -n $PORT ]] || { echo "no Arduino on /dev/serial/by-id"; exit 1; }
WAS_ACTIVE="$(systemctl is-active fire-gimbal-control 2>/dev/null || true)"
if [[ $WAS_ACTIVE == active ]]; then
    echo "pausing the old service (it holds the port)"
    sudo systemctl stop fire-gimbal-control
    trap 'echo "restarting the old service"; sudo systemctl start fire-gimbal-control' EXIT
    sleep 2
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"
