598 lines
27 KiB
Bash
598 lines
27 KiB
Bash
# shellcheck shell=bash
|
|
#
|
|
# kit.sh - what every board's setup kit shares. Sourced, never run: each
|
|
# hardware/<board>/install.sh sets the KIT_* facts below and calls kit_main "$@".
|
|
#
|
|
# The facts a kit must set before sourcing:
|
|
# KIT_BOARD lattepanda-iota | lattepanda-3-delta (matches probe_board)
|
|
# KIT_FW_ENV the PlatformIO env for this board's MCU
|
|
# KIT_MCU_VID USB vendor of the motor MCU (2e8a RP2040, 2341 Arduino)
|
|
# KIT_DEFAULT_AXES what this board's towers are usually built with
|
|
# KIT_IMU_DEVICE where an IMU normally is on this board
|
|
# KIT_IMU_NOTE one comment line on that
|
|
# KIT_ENV_DEVICE the I2C bus an ambient sensor normally uses, or "" if none
|
|
# KIT_ENV_NOTE comment lines on that (newline-separated)
|
|
# KIT_DIR the kit's own directory (packages.txt, preconditions/, pins.ini)
|
|
#
|
|
# What it can work out for itself: the board, the motor MCU's stable by-id path,
|
|
# the camera's DEV_ id, whether the USB preconditions took. What it cannot, and
|
|
# takes from flags, is what was ASSEMBLED - a missing pitch axis and a broken one
|
|
# look identical to a probe - and every drivetrain value, which is written as a
|
|
# commented `; TODO` naming what to measure when it is not given. It never
|
|
# invents one: a wrong sense resistor cooks a driver, and a wrong homing current
|
|
# shears an endstop. Both have happened on this project.
|
|
|
|
ETC_DIR=/etc/fire_gimbal_control
|
|
HW_FILE="$ETC_DIR/hardware.ini"
|
|
|
|
say() { printf '>> %s\n' "$*"; }
|
|
warn() { printf '!! %s\n' "$*" >&2; }
|
|
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
|
need_root() { [[ $EUID -eq 0 ]] || die "$1 needs root - re-run with sudo"; }
|
|
|
|
# The per-axis keys this kit writes, in file order. Every one is a fact about the
|
|
# drivetrain that no probe can read.
|
|
KIT_AXIS_KEYS=(r_sense_ohm run_current_ma homing_current_ma travel_deg steps_per_rev
|
|
sw_mode enc_deviation has_encoder)
|
|
|
|
# What to measure, per key - the text of each `; TODO`.
|
|
kit_axis_todo() {
|
|
case "$1" in
|
|
r_sense_ohm) echo "the sense resistor fitted to THIS axis's driver board - read it off the board, do not assume" ;;
|
|
run_current_ma) echo "coil current for normal motion, mA RMS, from the motor's nameplate" ;;
|
|
homing_current_ma) echo "current while hunting an endstop. LOWER than run_current wherever the stop can be out-pushed: on tower 121 a screw-head stop was sheared straight past its mate at full torque" ;;
|
|
travel_deg) echo "mechanical angle between the hard stops. Rough is fine to start; every bearing inherits its error proportionally until 'gimbal heading setup' supersedes it" ;;
|
|
steps_per_rev) echo "measured full travel IN COUNTS - read it off a homing run. The only guard against a PHANTOM endstop" ;;
|
|
sw_mode) echo "TMC SW_MODE. 0 = no physical switches (StallGuard homing); set the bits if this axis has REFL/REFR wired" ;;
|
|
enc_deviation) echo "|XACTUAL - XENC| that means 'position untrustworthy'. Measure the free-running gap on THIS axis and set ~4-5x it (tower 121: gap ~200, set 1000). Re-measure whenever the homing current changes" ;;
|
|
has_encoder) echo "true if an encoder is fitted to this axis and aligned to XACTUAL (usable for hold and DIAG), else false" ;;
|
|
esac
|
|
}
|
|
|
|
# ---------------------------------------------------------------- probing ---
|
|
# Board, from DMI. A kit applied to the wrong board is a real mistake, not a
|
|
# nuisance: the pin map and the device paths are board-specific.
|
|
probe_board() {
|
|
local dmi=/sys/class/dmi/id/product_name
|
|
[[ -r $dmi ]] || { echo "unknown"; return; }
|
|
case "$(tr -d '\0' < "$dmi")" in
|
|
*IOTA*|*Iota*|*iota*) echo "lattepanda-iota" ;;
|
|
*"3 Delta"*) echo "lattepanda-3-delta" ;;
|
|
*Sigma*) echo "lattepanda-sigma" ;;
|
|
*) echo "unknown" ;;
|
|
esac
|
|
}
|
|
|
|
# The motor MCU's stable path. /dev/ttyACM0 is enumeration order and moves when
|
|
# any other USB serial device appears - and these towers have a 5G modem that
|
|
# claims three ttyUSB nodes. Match on what the MCU calls itself:
|
|
# RP2040 running the gimbal firmware (arduino-pico) : "Raspberry_Pi_Pico"
|
|
# RP2040 as shipped, still on MicroPython : "MicroPython_Board"
|
|
# ATmega32U4 (Delta fleet) : "Arduino_LLC_Arduino_Leonardo"
|
|
# the same, running a legacy tower's firmware : "Unknown_LattePanda_Leonardo"
|
|
probe_mcu() {
|
|
local d found=()
|
|
for d in /dev/serial/by-id/*; do
|
|
[[ -e $d ]] || continue
|
|
case "$d" in
|
|
*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
|
|
done
|
|
(( ${#found[@]} == 1 )) && { echo "${found[0]}"; return; }
|
|
(( ${#found[@]} == 0 )) && { echo ""; return; }
|
|
printf '%s\n' "${found[@]}"
|
|
}
|
|
|
|
# The camera's DEV_ id, which is what [Camera.<name>] id takes. Only Vimba knows
|
|
# it - it is NOT the USB serial number - so this needs the SDK installed.
|
|
#
|
|
# Two traps, both hit on tower 121:
|
|
# - The example is called ListCameras_VmbC, not ListCameras.
|
|
# - The SDK ships a Camera Simulator transport layer that is enabled by
|
|
# default and reports three cameras that do not exist (DEV_Cam1..3). Ids are
|
|
# therefore taken only from a block whose transport layer is the USB or GigE
|
|
# TL. A tower configured with DEV_Cam1 would "find its camera" and capture
|
|
# synthetic images forever.
|
|
probe_camera_id() {
|
|
local tool
|
|
for tool in /opt/VimbaX*/bin/ListCameras_VmbC /opt/VimbaX*/bin/ListCameras; do
|
|
[[ -x $tool ]] || continue
|
|
# Non-interactive shells do not source /etc/profile.d, so the GenTL path
|
|
# has to be set here or VmbStartup() fails with error -16 (no transport
|
|
# layer) on a perfectly good installation.
|
|
local cti
|
|
for cti in "${GENICAM_GENTL64_PATH:-}" /opt/VimbaX/cti /opt/VimbaX_*/cti; do
|
|
[[ -d $cti ]] && { export GENICAM_GENTL64_PATH="$cti"; break; }
|
|
done
|
|
"$tool" 2>/dev/null | awk '
|
|
/Camera ID/ { id = $NF }
|
|
/Transport Layer Path/ { if (id != "" && $NF !~ /Simulator/) { print id; exit } }
|
|
'
|
|
return
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
# The camera's USB3 port node, whose usb3_lpm_permit must read 0. `test hardware
|
|
# camera` needs this path in [Test] usb3_lpm_path to check it.
|
|
probe_camera_lpm_path() {
|
|
local p
|
|
for p in /sys/bus/usb/devices/*/port/usb3_lpm_permit; do
|
|
[[ -r $p ]] || continue
|
|
local dev; dev="$(basename "$(dirname "$(dirname "$p")")")"
|
|
[[ "$(cat "/sys/bus/usb/devices/$dev/idVendor" 2>/dev/null)" == 1ab2 ]] && { echo "$p"; return; }
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
probe_report() {
|
|
local board mcu cam lpm
|
|
board="$(probe_board)"; mcu="$(probe_mcu)"; cam="$(probe_camera_id)"; lpm="$(probe_camera_lpm_path)"
|
|
echo "board : $board"
|
|
echo "os : $(. /etc/os-release && echo "$PRETTY_NAME") ($(uname -r))"
|
|
echo "motor MCU : ${mcu:-NOT FOUND}"
|
|
echo "camera DEV_ id : ${cam:-NOT FOUND (needs the Vimba X SDK installed)}"
|
|
echo "alvium on usb : $(lsusb 2>/dev/null | grep -c '1ab2:' || true) device(s)"
|
|
echo "usbfs_memory_mb : $(cat /sys/module/usbcore/parameters/usbfs_memory_mb 2>/dev/null || echo '?') (want >= 1000)"
|
|
[[ -n $lpm ]] && echo "camera lpm_permit: $(cat "$lpm") (want 0) $lpm"
|
|
echo "i2c buses : $(ls /dev/i2c-* 2>/dev/null | tr '\n' ' ')"
|
|
echo "ttyS : $(ls /dev/ttyS[0-9] 2>/dev/null | tr '\n' ' ')"
|
|
}
|
|
|
|
# ------------------------------------------------------------------- deps ---
|
|
kit_packages() {
|
|
local line
|
|
while read -r line; do
|
|
line="${line%%#*}"; line="$(echo "$line" | tr -d '[:space:]')"
|
|
[[ -n $line ]] && echo "$line"
|
|
done < "$KIT_DIR/packages.txt"
|
|
}
|
|
|
|
do_deps() {
|
|
need_root "--deps"
|
|
local pkgs; mapfile -t pkgs < <(kit_packages)
|
|
say "installing ${#pkgs[@]} packages"
|
|
[[ $APT_UPDATE -eq 1 ]] && apt-get update
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}"
|
|
say "packages done"
|
|
}
|
|
|
|
# ---------------------------------------------------------- preconditions ---
|
|
# Everything in the kit's preconditions/: *.rules go to udev, *.conf to tmpfiles.
|
|
kit_precondition_targets() {
|
|
local f
|
|
for f in "$KIT_DIR"/preconditions/*; do
|
|
case "$f" in
|
|
*.rules) echo "$f /etc/udev/rules.d/$(basename "$f")" ;;
|
|
*.conf) echo "$f /etc/tmpfiles.d/$(basename "$f")" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
do_preconditions() {
|
|
need_root "--preconditions"
|
|
local src dst n=0
|
|
while read -r src dst; do
|
|
install -m 0644 "$src" "$dst"; n=$((n + 1))
|
|
done < <(kit_precondition_targets)
|
|
say "installed $n precondition file(s)"
|
|
|
|
udevadm control --reload
|
|
udevadm trigger --subsystem-match=usb --action=add
|
|
local conf
|
|
for conf in "$KIT_DIR"/preconditions/*.conf; do
|
|
[[ -e $conf ]] && systemd-tmpfiles --create "/etc/tmpfiles.d/$(basename "$conf")"
|
|
done
|
|
say "applied to the running system"
|
|
|
|
# Report rather than assume: a rule that did not take is the whole point of
|
|
# known-issues #19, and it fails quietly.
|
|
local now; now="$(cat /sys/module/usbcore/parameters/usbfs_memory_mb 2>/dev/null || echo '?')"
|
|
[[ $now == 1000 ]] && say "usbfs_memory_mb = $now" || warn "usbfs_memory_mb is $now, expected 1000"
|
|
local lpm; lpm="$(probe_camera_lpm_path)"
|
|
# The rule acts when the camera is (re)plugged, and a udev trigger does not
|
|
# always replay it (on staeffelsberg it did not): write what it would write.
|
|
if [[ -n $lpm && "$(cat "$lpm")" != 0 ]]; then echo 0 > "$lpm" 2>/dev/null || true; fi
|
|
if [[ -n $lpm ]]; then
|
|
[[ "$(cat "$lpm")" == 0 ]] && say "camera USB3 LPM disabled ($lpm)" \
|
|
|| warn "camera USB3 LPM still $(cat "$lpm") at $lpm"
|
|
else
|
|
warn "no Alvium camera on the bus - LPM rule untested until one is plugged in"
|
|
fi
|
|
|
|
# The MCU has to be writable for flashing over ssh; check it the same way
|
|
# rather than trusting that the rule took.
|
|
local mcu_dev
|
|
# `|| true`: an MCU with another vendor id (the LattePanda's own Leonardo,
|
|
# 3343) finds nothing here, and under set -e -o pipefail that ended the whole
|
|
# kit - and the installer with it - without a word.
|
|
mcu_dev="$(grep -l "^$KIT_MCU_VID\$" /sys/bus/usb/devices/*/idVendor 2>/dev/null | head -1 || true)"
|
|
if [[ -n $mcu_dev ]]; then
|
|
local node
|
|
node="$(printf '/dev/bus/usb/%03d/%03d' "$(cat "$(dirname "$mcu_dev")/busnum")" \
|
|
"$(cat "$(dirname "$mcu_dev")/devnum")")"
|
|
if [[ -w $node ]] || [[ $KIT_MCU_VID == 2341 ]]; then
|
|
say "MCU ($KIT_MCU_VID) at $node"
|
|
else
|
|
warn "MCU ($KIT_MCU_VID) at $node is not writable; flashing will need sudo"
|
|
ls -l "$node" >&2
|
|
fi
|
|
else
|
|
warn "no $KIT_MCU_VID MCU on the bus - the flashing rule is untested until one is plugged in"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------ vimba ---
|
|
# The Vimba X SDK is proprietary and login-walled, so it cannot be fetched by a
|
|
# script. Stage it on the device first (USB stick, or rsync from a machine that
|
|
# has it), then point this at the staged directory.
|
|
do_vimba() {
|
|
need_root "--vimba"
|
|
[[ -d $VIMBA_SRC ]] || die "--vimba: no such directory: $VIMBA_SRC"
|
|
[[ -x $VIMBA_SRC/cti/Install_GenTL_Path.sh ]] || die "--vimba: $VIMBA_SRC does not look like a Vimba X SDK (no cti/Install_GenTL_Path.sh)"
|
|
|
|
local name dest
|
|
name="$(basename "$VIMBA_SRC")"
|
|
dest="/opt/$name"
|
|
if [[ -d $dest && $FORCE -ne 1 ]]; then
|
|
say "$dest already present - not re-copying (--force to replace)"
|
|
else
|
|
say "installing $name to $dest"
|
|
rm -rf "$dest.new"
|
|
cp -a "$VIMBA_SRC" "$dest.new"
|
|
rm -rf "$dest"
|
|
mv "$dest.new" "$dest"
|
|
fi
|
|
# cp -a preserves the staging copy's ownership. The SDK is system software.
|
|
chown -R root:root "$dest"
|
|
ln -sfn "$dest" /opt/VimbaX
|
|
say "/opt/VimbaX -> $dest"
|
|
|
|
# GENICAM_GENTL64_PATH, or VmbStartup() fails with "Could not start Vimba X
|
|
# API". The SDK's own installer writes /etc/profile.d + the 1ab2 udev rule.
|
|
"$dest/cti/Install_GenTL_Path.sh"
|
|
|
|
# Non-interactive ssh shells do not source /etc/profile.d, but the loader
|
|
# needs the libraries findable regardless of how the app was started.
|
|
echo "$dest/api/lib" > /etc/ld.so.conf.d/vimbax.conf
|
|
ldconfig
|
|
say "registered $dest/api/lib with the dynamic loader"
|
|
|
|
udevadm control --reload && udevadm trigger --subsystem-match=usb --action=add
|
|
|
|
local id; id="$(probe_camera_id)"
|
|
if [[ -n $id ]]; then
|
|
say "camera found: $id"
|
|
else
|
|
warn "no camera reported by ListCameras yet"
|
|
warn "the SDK asks for a reboot before the transport layers are usable"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------ hardware.ini ---
|
|
# The centre lives in config.ini, not here - it is calibration, and hardware.ini
|
|
# is merged OVER config.ini, so a shared key would silently discard whatever
|
|
# `gimbal heading setup` had saved. All this file can do is say what the value
|
|
# has to be, which it can work out from the travel: the range is centre +/- half
|
|
# the travel, so a centre below half-travel puts the negative end under zero.
|
|
kit_center_for() {
|
|
local steps="$1"
|
|
echo $(( ((steps / 2) / 1000000 + 1) * 1000000 ))
|
|
}
|
|
|
|
center_note() {
|
|
local steps="$1" ax="$2"
|
|
[[ -z $steps ]] && return 0
|
|
local half=$(( steps / 2 ))
|
|
# Only when it matters: the 500000 default is fine for any travel under a
|
|
# million counts, and a note demanding a change nobody needs gets acted on.
|
|
(( half >= 500000 )) || return 0
|
|
echo "; NOTE: half this travel is $half counts, so config.ini needs"
|
|
echo "; [Motor] ${ax}_center_count = $(kit_center_for "$steps") (or anything above $half)"
|
|
echo "; The 500000 default would put the negative end at $(( 500000 - half ))."
|
|
}
|
|
|
|
# Emit "key = value", or a commented-out line naming what has to be measured.
|
|
#
|
|
# Commented rather than a placeholder string on purpose: these keys are numeric,
|
|
# and a non-numeric value makes the program refuse to start with a message about
|
|
# a config key. Absent, it starts and reports the axis as undeclared - naming the
|
|
# missing keys and which axis will not move, which is the useful failure.
|
|
kv_or_todo() {
|
|
local key="$1" value="$2" why="$3"
|
|
if [[ -n $value ]]; then
|
|
printf '%-18s = %s\n' "$key" "$value"
|
|
return
|
|
fi
|
|
# Wrapped, and not merely for looks: ini.h sets INI_MAX_LINE to 200, and a
|
|
# longer line is SPLIT rather than truncated, so its tail becomes a bogus
|
|
# line and the whole file fails to parse.
|
|
printf '; TODO %s\n' "$key"
|
|
printf '%s\n' "$why" | fold -s -w 72 | sed 's/^/; /' | sed 's/[[:space:]]*$//'
|
|
}
|
|
|
|
# A loom file's lines, keeping only the pins of what this tower declares: a
|
|
# yaw-only tower drops the pitch lines, one without the DHT11 or fan drops
|
|
# theirs. The firmware build refuses a pin for an undeclared part, so copying a
|
|
# loom whole stopped every yaw-only install on the standard Delta loom.
|
|
kit_loom_file() {
|
|
case "$1" in
|
|
standard) echo "$KIT_DIR/pins.ini" ;;
|
|
legacy) echo "$KIT_DIR/pins-legacy.ini" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
emit_loom() {
|
|
local file="$1" axes="$2" env="$3" cooling="$4"
|
|
awk -v axes=",${axes// /}," -v env="$env" -v cooling="$cooling" '
|
|
/^[[:space:]]*[;#]/ || /^[[:space:]]*\[/ || !index($0, "=") { print; next }
|
|
{
|
|
k = substr($0, 1, index($0, "=") - 1); gsub(/[[:space:]]/, "", k)
|
|
if (k ~ /^pitch_/ && index(axes, ",pitch,") == 0) next
|
|
if (k == "dht" && env != "dht11") next
|
|
if (k ~ /^fan_/ && cooling != "fan") next
|
|
print
|
|
}' "$file"
|
|
}
|
|
|
|
# `--pin k=v` values as a [Pins] block, or one of the kit's looms, or a TODO.
|
|
emit_pins() {
|
|
if [[ -n $LOOM ]]; then
|
|
local file; file="$(kit_loom_file "$LOOM")"
|
|
[[ -r $file ]] || die "--loom $LOOM: this kit has no $(basename "${file:-pins-$LOOM.ini}")"
|
|
emit_loom "$file" "$AXES" "$ENV_SENSOR" "$COOLING"
|
|
return
|
|
fi
|
|
if (( ${#PINS[@]} > 0 )); then
|
|
echo "; How the MCU is wired to each driver board. The firmware is BUILT from this"
|
|
echo "; (firmware/tools/gen_wiring.py); the reference loom is hardware/$KIT_BOARD/pins.ini."
|
|
echo "[Pins]"
|
|
local k
|
|
for k in "${PIN_ORDER[@]}"; do printf '%-8s = %s\n' "$k" "${PINS[$k]}"; done
|
|
return
|
|
fi
|
|
echo "; TODO [Pins]: the driver wiring. The firmware build REFUSES to run without it."
|
|
echo "; Copy hardware/$KIT_BOARD/pins.ini here if this tower follows that loom."
|
|
}
|
|
|
|
emit_hardware_ini() {
|
|
local board mcu cam
|
|
board="$(probe_board)"
|
|
mcu="${SERIAL_DEVICE:-$(probe_mcu)}"
|
|
cam="${CAMERA_ID:-$(probe_camera_id)}"
|
|
if [[ $(wc -l <<< "$mcu") -gt 1 ]]; then
|
|
warn "several candidate MCU ports found; pick one with --serial:"
|
|
warn "$mcu"
|
|
mcu="CHOOSE_ONE"
|
|
fi
|
|
|
|
cat <<EOF
|
|
; hardware.ini - what this machine IS. Generated by hardware/$KIT_BOARD/install.sh
|
|
; on $(date -Is) from what the machine reports, plus the assembly
|
|
; facts given to it. See config/hardware.example.ini for the full reference,
|
|
; and docs/hardware-config-plan.md for why this file exists.
|
|
|
|
[Board]
|
|
model = $board
|
|
firmware_env = $KIT_FW_ENV
|
|
|
|
[Hardware]
|
|
; DECLARED, not probed: these are what the tower was built with, so that a part
|
|
; that stops answering is a fault and not a silent reconfiguration.
|
|
axes = $AXES
|
|
imu = $IMU
|
|
env = $ENV_SENSOR
|
|
cooling = $COOLING
|
|
cameras = $CAMERAS
|
|
|
|
[Serial]
|
|
; The motor controller MCU, by stable id - /dev/ttyACM* moves when the modem
|
|
; enumerates first. The name changes when the MCU is flashed.
|
|
device = ${mcu:-NOT_FOUND_SET_THIS_BY_HAND}
|
|
baud = 115200
|
|
|
|
EOF
|
|
emit_pins
|
|
|
|
if [[ $IMU != none ]]; then
|
|
printf '\n[IMU]\n; %s\ndevice = %s\n' "$KIT_IMU_NOTE" "${IMU_DEVICE:-$KIT_IMU_DEVICE}"
|
|
fi
|
|
# A DHT11 is read by the motor MCU: there is no bus of the host's to name.
|
|
if [[ $ENV_SENSOR != none && $ENV_SENSOR != dht11 ]]; then
|
|
printf '\n[Env]\n'
|
|
printf '%s\n' "$KIT_ENV_NOTE" | sed 's/^/; /'
|
|
printf 'i2c_device = %s\ni2c_addr = 0x44\n' "${I2C_DEVICE:-${KIT_ENV_DEVICE:-SET_THIS_BY_HAND}}"
|
|
fi
|
|
|
|
# One [Motor.<axis>] block per axis. The firmware holds none of these itself -
|
|
# it asks, and refuses to move until answered - so an axis without a block
|
|
# here is an axis that will not turn.
|
|
local ax key
|
|
for ax in ${AXES//,/ }; do
|
|
ax="$(echo "$ax" | tr -d '[:space:]')"
|
|
[[ -z $ax ]] && continue
|
|
echo
|
|
echo "[Motor.$ax]"
|
|
for key in "${KIT_AXIS_KEYS[@]}"; do
|
|
kv_or_todo "$key" "${MOTOR[$ax.$key]:-}" "$(kit_axis_todo "$key")"
|
|
[[ $key == steps_per_rev ]] && center_note "${MOTOR[$ax.$key]:-}" "$ax"
|
|
done
|
|
cat <<'EOF'
|
|
; Speeds are DEGREES PER SECOND, not VMAX: a register value means different
|
|
; things on different gear ratios, so it is not portable between towers. These
|
|
; are deliberately gentle starting points; the host converts them per tower.
|
|
scan_speed_deg_s = 8.0
|
|
homing_speed_deg_s = 4.0
|
|
center_speed_deg_s = 6.0
|
|
EOF
|
|
done
|
|
|
|
local c
|
|
for c in ${CAMERAS//,/ }; do
|
|
c="$(echo "$c" | tr -d '[:space:]')"
|
|
[[ -z $c || $c == none ]] && continue
|
|
cat <<EOF
|
|
|
|
[Camera.$c]
|
|
; The Vimba DEV_ id of the physical camera fitted to this tower. Not the USB
|
|
; serial: read it with VimbaX's ListCameras, which is what this probe does.
|
|
id = ${cam:-NOT_FOUND_SET_THIS_BY_HAND}
|
|
EOF
|
|
done
|
|
}
|
|
|
|
do_hardware_ini() {
|
|
if [[ $TO_STDOUT -eq 1 ]]; then emit_hardware_ini; return; fi
|
|
need_root "--hardware-ini"
|
|
mkdir -p "$ETC_DIR"
|
|
if [[ -f $HW_FILE && $FORCE -ne 1 ]]; then
|
|
warn "$HW_FILE exists - leaving it alone (--force to replace)"
|
|
warn "the file it would write instead:"
|
|
emit_hardware_ini | sed 's/^/ /' >&2
|
|
return
|
|
fi
|
|
if [[ -f $HW_FILE ]]; then
|
|
cp -p "$HW_FILE" "$HW_FILE.bak-$(date +%Y%m%d-%H%M%S)"
|
|
say "backed the previous hardware.ini up"
|
|
fi
|
|
emit_hardware_ini > "$HW_FILE"
|
|
chmod 0644 "$HW_FILE"
|
|
say "wrote $HW_FILE"
|
|
if grep -n 'SET_THIS_BY_HAND\|CHOOSE_ONE\|^; TODO' "$HW_FILE" >/dev/null 2>&1; then
|
|
warn "it still has gaps - fill them before starting the app:"
|
|
grep -n 'SET_THIS_BY_HAND\|CHOOSE_ONE\|^; TODO' "$HW_FILE" | sed 's/^/ /' >&2
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------- main ---
|
|
kit_usage() {
|
|
cat <<EOF
|
|
hardware/$KIT_BOARD/install.sh - set up a $KIT_BOARD tower host. Run ON the device.
|
|
|
|
sudo ./install.sh everything: deps, preconditions, hardware.ini
|
|
sudo ./install.sh --deps apt packages only
|
|
sudo ./install.sh --preconditions udev + tmpfiles only, applied immediately
|
|
./install.sh --probe print what this machine looks like, change nothing
|
|
sudo ./install.sh --vimba DIR install a staged Vimba X SDK
|
|
./install.sh --stdout print the hardware.ini it would write
|
|
|
|
What was ASSEMBLED (a probe cannot tell missing from broken):
|
|
--axes yaw|yaw,pitch (default: $KIT_DEFAULT_AXES)
|
|
--imu none|mti --env none|sht41|dht11 --cameras rgb|none
|
|
--cooling none|fan a fan the motor MCU runs from its DHT11 (legacy towers)
|
|
Overrides for what is otherwise probed:
|
|
--serial PATH the MCU's /dev/serial/by-id path
|
|
--camera-id DEV_... the camera's Vimba id
|
|
--imu-device PATH --i2c-device PATH
|
|
Wiring ([Pins], which the firmware is built from):
|
|
--loom standard|legacy one of this kit's looms (pins.ini, pins-legacy.ini), keeping
|
|
only the lines for what is declared
|
|
--pin KEY=N one pin at a time instead (yaw_cs=1, spi_sck=2, ...)
|
|
The drivetrain, per axis - anything not given is written as a ; TODO:
|
|
--motor AXIS.KEY=VALUE KEY one of: ${KIT_AXIS_KEYS[*]}
|
|
--r-sense, --run-current, --homing-current, --travel-deg, --steps-per-rev,
|
|
--sw-mode the same, for the FIRST axis in --axes
|
|
Other:
|
|
--force overwrite an existing hardware.ini (backs it up first)
|
|
--no-apt-update skip 'apt-get update'
|
|
EOF
|
|
}
|
|
|
|
kit_main() {
|
|
AXES="$KIT_DEFAULT_AXES" IMU="none" ENV_SENSOR="none" CAMERAS="rgb" COOLING="none"
|
|
SERIAL_DEVICE="" CAMERA_ID="" IMU_DEVICE="" I2C_DEVICE="" LOOM=""
|
|
DO_DEPS=0 DO_PRE=0 DO_HW=0 DO_VIMBA=0 PROBE_ONLY=0 TO_STDOUT=0 FORCE=0 APT_UPDATE=1
|
|
VIMBA_SRC=""
|
|
declare -gA MOTOR=() PINS=()
|
|
PIN_ORDER=()
|
|
local explicit=0
|
|
local -A first_axis=() # the legacy single-axis flags, applied once --axes is known
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--deps) DO_DEPS=1; explicit=1 ;;
|
|
--preconditions) DO_PRE=1; explicit=1 ;;
|
|
--hardware-ini) DO_HW=1; explicit=1 ;;
|
|
--probe) PROBE_ONLY=1; explicit=1 ;;
|
|
--stdout) TO_STDOUT=1; DO_HW=1; explicit=1 ;;
|
|
--vimba) VIMBA_SRC="$2"; DO_VIMBA=1; explicit=1; shift ;;
|
|
--axes) AXES="$2"; shift ;;
|
|
--imu) IMU="$2"; shift ;;
|
|
--env) ENV_SENSOR="$2"; shift ;;
|
|
--cooling) COOLING="$2"; shift ;;
|
|
--cameras) CAMERAS="$2"; shift ;;
|
|
--serial) SERIAL_DEVICE="$2"; shift ;;
|
|
--camera-id) CAMERA_ID="$2"; shift ;;
|
|
--imu-device) IMU_DEVICE="$2"; shift ;;
|
|
--i2c-device) I2C_DEVICE="$2"; shift ;;
|
|
--loom) LOOM="$2"; shift ;;
|
|
--pin)
|
|
[[ $2 == *=* ]] || die "--pin takes KEY=N"
|
|
[[ -z ${PINS[${2%%=*}]+x} ]] && PIN_ORDER+=("${2%%=*}")
|
|
PINS[${2%%=*}]="${2#*=}"; shift ;;
|
|
--motor)
|
|
[[ $2 == *.*=* ]] || die "--motor takes AXIS.KEY=VALUE"
|
|
MOTOR[${2%%=*}]="${2#*=}"; shift ;;
|
|
--r-sense) first_axis[r_sense_ohm]="$2"; shift ;;
|
|
--run-current) first_axis[run_current_ma]="$2"; shift ;;
|
|
--homing-current) first_axis[homing_current_ma]="$2"; shift ;;
|
|
--travel-deg) first_axis[travel_deg]="$2"; shift ;;
|
|
--steps-per-rev) first_axis[steps_per_rev]="$2"; shift ;;
|
|
--sw-mode) first_axis[sw_mode]="$2"; shift ;;
|
|
--force) FORCE=1 ;;
|
|
--no-apt-update) APT_UPDATE=0 ;;
|
|
-h|--help) kit_usage; exit 0 ;;
|
|
*) die "unknown option: $1 (--help lists them)" ;;
|
|
esac
|
|
shift
|
|
done
|
|
[[ $explicit -eq 0 ]] && { DO_DEPS=1; DO_PRE=1; DO_HW=1; }
|
|
[[ -z $LOOM || $LOOM == standard || $LOOM == legacy ]] ||
|
|
die "--loom takes 'standard' or 'legacy' (or use --pin)"
|
|
[[ $COOLING == none || $COOLING == fan ]] || die "--cooling takes 'none' or 'fan'"
|
|
[[ $COOLING == none || $ENV_SENSOR == dht11 ]] ||
|
|
die "--cooling fan needs --env dht11: the fan runs from the DHT11 on the MCU"
|
|
[[ -n $LOOM && ${#PINS[@]} -gt 0 ]] && die "--loom and --pin are alternatives; give one"
|
|
|
|
local first key
|
|
first="$(echo "${AXES%%,*}" | tr -d '[:space:]')"
|
|
for key in "${!first_axis[@]}"; do
|
|
[[ -z ${MOTOR[$first.$key]+x} ]] && MOTOR[$first.$key]="${first_axis[$key]}"
|
|
done
|
|
for key in "${!MOTOR[@]}"; do
|
|
local ax="${key%%.*}" k="${key#*.}" ok=0 known
|
|
[[ ",${AXES// /}," == *",$ax,"* ]] || die "--motor $key: $ax is not in --axes $AXES"
|
|
for known in "${KIT_AXIS_KEYS[@]}"; do [[ $k == "$known" ]] && ok=1; done
|
|
[[ $ok -eq 1 ]] || die "--motor $key: unknown key (one of: ${KIT_AXIS_KEYS[*]})"
|
|
done
|
|
|
|
local board; board="$(probe_board)"
|
|
if [[ $board != "$KIT_BOARD" ]]; then
|
|
warn "this kit is for the $KIT_BOARD; this machine reports '$board'"
|
|
# --probe and --stdout change nothing on this machine. Refusing them on
|
|
# another board only means the generator can never be exercised off a
|
|
# tower, which is how a setup kit ends up untested.
|
|
[[ $PROBE_ONLY -eq 1 || $TO_STDOUT -eq 1 ]] ||
|
|
die "refusing to apply $KIT_BOARD preconditions to a $board"
|
|
fi
|
|
|
|
if [[ $PROBE_ONLY -eq 1 ]]; then probe_report; exit 0; fi
|
|
# --stdout emits the file and nothing else: it is meant to be redirected into one.
|
|
if [[ $TO_STDOUT -eq 1 ]]; then do_hardware_ini; exit 0; fi
|
|
|
|
say "$KIT_BOARD setup kit"
|
|
probe_report | sed 's/^/ /'
|
|
# if/fi rather than '[[ ]] && cmd': under `set -e` a false test as the last
|
|
# command of an AND list exits the script.
|
|
if [[ $DO_DEPS -eq 1 ]]; then do_deps; fi
|
|
if [[ $DO_VIMBA -eq 1 ]]; then do_vimba; fi
|
|
if [[ $DO_PRE -eq 1 ]]; then do_preconditions; fi
|
|
if [[ $DO_HW -eq 1 ]]; then do_hardware_ini; fi
|
|
say "done"
|
|
}
|