488 lines
14 KiB
Bash
Executable File
488 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
set -u
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
# 可通过环境变量覆盖的运行参数。
|
||
MAX_DEVICES="${MAX_DEVICES:-10}"
|
||
RECOVERY_TIMEOUT="${RECOVERY_TIMEOUT:-120}"
|
||
POLL_INTERVAL="${POLL_INTERVAL:-2}"
|
||
# 使用一个极少出现在普通文本中的分隔符,方便在 bash 中安全拆字段。
|
||
DEVICE_FIELD_SEP=$'\037'
|
||
|
||
# 从脚本所在目录向上查找仓库根目录,确保在任意位置执行都能定位到资源文件。
|
||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||
REPO_ROOT=""
|
||
search_dir="$SCRIPT_DIR"
|
||
while [ "$search_dir" != "/" ]; do
|
||
if [ -f "$search_dir/res/arcs-mini/adb_download.sh" ]; then
|
||
REPO_ROOT="$search_dir"
|
||
break
|
||
fi
|
||
search_dir=$(dirname "$search_dir")
|
||
done
|
||
if [ -z "$REPO_ROOT" ]; then
|
||
echo -e "${RED}错误: 无法定位仓库根目录${NC}"
|
||
exit 1
|
||
fi
|
||
cd "$REPO_ROOT" || exit 1
|
||
|
||
# 用法:
|
||
# ./adb_download.sh
|
||
# 默认不烧录 boot,也不会执行 `upgrade enter`
|
||
# ./adb_download.sh boot
|
||
# 会额外烧录 boot,并在 push 前执行 `root;listenai;upgrade enter`
|
||
INCLUDE_BOOT=0
|
||
if [ "$#" -gt 1 ]; then
|
||
echo -e "${RED}错误: 参数过多${NC}"
|
||
echo "用法: $0 [boot]"
|
||
exit 1
|
||
fi
|
||
|
||
case "${1:-}" in
|
||
"")
|
||
;;
|
||
boot)
|
||
INCLUDE_BOOT=1
|
||
;;
|
||
-h|--help|help)
|
||
echo "用法: $0 [boot]"
|
||
echo " 不带参数: 烧录 ap/tone/wake_word/emoji/respak/app,不烧录 boot"
|
||
echo " boot: 额外烧录 boot,并执行 upgrade enter"
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo -e "${RED}错误: 不支持的参数: $1${NC}"
|
||
echo "用法: $0 [boot]"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# 完整烧录表。默认会跳过第 1 项 boot;传入 `boot` 参数时才会包含它。
|
||
ALL_LOCAL_FILES=(
|
||
"res/arcs-mini/boot.bin"
|
||
"res/arcs-mini/ap.bin"
|
||
"res/arcs-mini/tone.bin"
|
||
"res/arcs-mini/wake_word.bin"
|
||
"res/arcs-mini/emoji.bin"
|
||
"res/arcs-mini/respak.bin"
|
||
"build/arcs-mini.bin"
|
||
)
|
||
|
||
ALL_REMOTE_PATHS=(
|
||
"/RAW/NAND/0"
|
||
"/RAW/NAND/40000"
|
||
"/RAW/NAND/100000"
|
||
"/RAW/NAND/200000"
|
||
"/RAW/NAND/380000"
|
||
"/RAW/NAND/440000"
|
||
"/RAW/NAND/600000"
|
||
)
|
||
|
||
if [ "${#ALL_LOCAL_FILES[@]}" -ne "${#ALL_REMOTE_PATHS[@]}" ]; then
|
||
echo -e "${RED}错误: 本地文件和目标地址数量不一致${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
declare -a LOCAL_FILES=()
|
||
declare -a REMOTE_PATHS=()
|
||
|
||
# 默认只在烧录表第 0 项确实是 boot.bin 时跳过它。
|
||
start_idx=0
|
||
if [ "$INCLUDE_BOOT" -eq 1 ]; then
|
||
echo "当前模式: 包含 boot 烧录"
|
||
elif [ "${#ALL_LOCAL_FILES[@]}" -gt 0 ] && [[ "${ALL_LOCAL_FILES[0]}" == *"/boot.bin" ]]; then
|
||
start_idx=1
|
||
fi
|
||
|
||
for idx in "${!ALL_LOCAL_FILES[@]}"; do
|
||
if [ "$idx" -lt "$start_idx" ]; then
|
||
continue
|
||
fi
|
||
LOCAL_FILES+=("${ALL_LOCAL_FILES[$idx]}")
|
||
REMOTE_PATHS+=("${ALL_REMOTE_PATHS[$idx]}")
|
||
done
|
||
|
||
if [ "${#LOCAL_FILES[@]}" -eq 0 ]; then
|
||
echo -e "${RED}错误: 没有待烧录文件,请检查 ALL_LOCAL_FILES / INCLUDE_BOOT 配置${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
echo "本次待烧录文件:"
|
||
for idx in "${!LOCAL_FILES[@]}"; do
|
||
echo "- ${LOCAL_FILES[$idx]} -> ${REMOTE_PATHS[$idx]}"
|
||
done
|
||
|
||
echo "检查待烧录文件..."
|
||
for local_path in "${LOCAL_FILES[@]}"; do
|
||
if [ ! -f "$local_path" ]; then
|
||
echo -e "${RED}错误: 本地文件不存在: $local_path${NC}"
|
||
exit 1
|
||
fi
|
||
done
|
||
echo -e "${GREEN}文件检查完成${NC}"
|
||
|
||
# 先识别当前是否运行在 WSL 中,后续会尝试复用 Windows 侧的 adb。
|
||
adb_cmd=""
|
||
is_wsl=0
|
||
if uname -a | grep -qi "WSL"; then
|
||
is_wsl=1
|
||
echo "检测到WSL环境"
|
||
fi
|
||
|
||
declare -a adb_candidates=()
|
||
declare -A seen_adb_candidates=()
|
||
|
||
# 收集可用的 adb 候选路径,并做去重。
|
||
add_adb_candidate() {
|
||
local candidate="$1"
|
||
[ -z "$candidate" ] && return
|
||
if [ -z "${seen_adb_candidates[$candidate]:-}" ]; then
|
||
adb_candidates+=("$candidate")
|
||
seen_adb_candidates["$candidate"]=1
|
||
fi
|
||
}
|
||
|
||
if [ -n "${ADB_CMD:-}" ]; then
|
||
add_adb_candidate "$ADB_CMD"
|
||
fi
|
||
|
||
if type -P adb >/dev/null 2>&1; then
|
||
add_adb_candidate "$(type -P adb)"
|
||
fi
|
||
|
||
if [ "$is_wsl" -eq 1 ] && type -P powershell.exe >/dev/null 2>&1; then
|
||
adb_win_path=$(powershell.exe -Command "(Get-Command adb).Source" 2>/dev/null | tr -d '\r')
|
||
adb_win_path_wsl=$(echo "$adb_win_path" | sed -e 's/\\/\//g' -e 's/^\(.\):/\/mnt\/\L\1/')
|
||
add_adb_candidate "$adb_win_path_wsl"
|
||
fi
|
||
|
||
add_adb_candidate "adb"
|
||
|
||
# 逐个探测 adb,优先选择当前能看到更多设备的那个实例。
|
||
best_adb_cmd=""
|
||
best_adb_count=-1
|
||
for candidate in "${adb_candidates[@]}"; do
|
||
if ! "$candidate" version >/dev/null 2>&1; then
|
||
continue
|
||
fi
|
||
candidate_count=$("$candidate" devices -l 2>/dev/null | tr -d '\r' | awk 'NR > 1 && $2 == "device" {c++} END {print c + 0}')
|
||
echo "ADB候选: $candidate (检测到设备: $candidate_count)"
|
||
if [ "$candidate_count" -gt "$best_adb_count" ]; then
|
||
best_adb_cmd="$candidate"
|
||
best_adb_count="$candidate_count"
|
||
fi
|
||
done
|
||
|
||
if [ -z "$best_adb_cmd" ]; then
|
||
echo -e "${RED}错误: ADB工具不可用, 请确保已安装 Android SDK Platform-Tools 并将其添加到环境变量中${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
adb_cmd="$best_adb_cmd"
|
||
echo "选择ADB工具: $adb_cmd (当前可见设备: $best_adb_count)"
|
||
"$adb_cmd" version
|
||
|
||
# 统一收敛 adb 输出,去掉回车和空字符,降低不同平台下的解析差异。
|
||
collect_devices_raw() {
|
||
"$adb_cmd" devices -l 2>/dev/null | tr -d '\r\000'
|
||
}
|
||
|
||
# 从 `adb devices -l` 中提取 serial / usb / transport_id 三个字段。
|
||
scan_connected_devices() {
|
||
local line serial state usb tid
|
||
|
||
while IFS= read -r line; do
|
||
[ -z "$line" ] && continue
|
||
[[ "$line" == "List of devices attached"* ]] && continue
|
||
|
||
if [[ ! "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then
|
||
continue
|
||
fi
|
||
|
||
serial="${BASH_REMATCH[1]}"
|
||
state="${BASH_REMATCH[2]}"
|
||
if [ "$state" != "device" ]; then
|
||
continue
|
||
fi
|
||
|
||
usb=""
|
||
tid=""
|
||
|
||
if [[ "$line" =~ usb:([^[:space:]]+) ]]; then
|
||
usb="${BASH_REMATCH[1]}"
|
||
fi
|
||
if [[ "$line" =~ transport_id:([^[:space:]]+) ]]; then
|
||
tid="${BASH_REMATCH[1]}"
|
||
fi
|
||
|
||
printf "%s%s%s%s%s\n" "$serial" "$DEVICE_FIELD_SEP" "$usb" "$DEVICE_FIELD_SEP" "$tid"
|
||
done < <(collect_devices_raw)
|
||
}
|
||
|
||
# 普通模式设备依赖 transport_id 发送 recovery 指令;
|
||
# 已经处于 recovery 的设备通常以 BOOT-* 形式出现。
|
||
declare -a normal_transport_ids=()
|
||
declare -a initial_boot_serials=()
|
||
declare -A transport_to_usb=()
|
||
declare -A seen_tid=()
|
||
declare -A seen_boot=()
|
||
missing_tid_count=0
|
||
mapfile -t scanned_devices < <(scan_connected_devices)
|
||
|
||
for scanned_line in "${scanned_devices[@]}"; do
|
||
IFS="$DEVICE_FIELD_SEP" read -r serial usb tid <<< "$scanned_line"
|
||
[ -z "$serial" ] && continue
|
||
|
||
if [[ "$serial" == BOOT-* ]]; then
|
||
if [ -z "${seen_boot[$serial]:-}" ]; then
|
||
initial_boot_serials+=("$serial")
|
||
seen_boot["$serial"]=1
|
||
fi
|
||
continue
|
||
fi
|
||
|
||
if [ -z "$tid" ]; then
|
||
missing_tid_count=$((missing_tid_count + 1))
|
||
continue
|
||
fi
|
||
|
||
if [ -z "${seen_tid[$tid]:-}" ]; then
|
||
normal_transport_ids+=("$tid")
|
||
transport_to_usb["$tid"]="$usb"
|
||
seen_tid["$tid"]=1
|
||
fi
|
||
done
|
||
|
||
target_count=$(( ${#normal_transport_ids[@]} + ${#initial_boot_serials[@]} ))
|
||
|
||
if [ "$target_count" -eq 0 ]; then
|
||
echo -e "${RED}错误: 没有检测到可用设备(状态必须是 device)${NC}"
|
||
echo "当前 \"${adb_cmd} devices -l\" 输出:"
|
||
"$adb_cmd" devices -l
|
||
echo "脚本解析后的设备行(serial | usb | transport_id):"
|
||
if [ "${#scanned_devices[@]}" -eq 0 ]; then
|
||
echo "(empty)"
|
||
else
|
||
printf '%s\n' "${scanned_devices[@]}" | sed $'s/\x1f/ | /g'
|
||
fi
|
||
exit 1
|
||
fi
|
||
|
||
if [ "$target_count" -gt "$MAX_DEVICES" ]; then
|
||
echo -e "${RED}错误: 检测到 $target_count 台设备,超过 MAX_DEVICES=$MAX_DEVICES${NC}"
|
||
echo "如需继续请设置更大值,例如: MAX_DEVICES=$target_count ./z_thw_temp/download_multi_transport.sh"
|
||
exit 1
|
||
fi
|
||
|
||
echo "检测到设备总数: $target_count"
|
||
echo "- 普通模式设备(需进recovery): ${#normal_transport_ids[@]}"
|
||
echo "- 已在recovery(BOOT-*)设备: ${#initial_boot_serials[@]}"
|
||
if [ "$missing_tid_count" -gt 0 ]; then
|
||
echo -e "${YELLOW}警告: 有 $missing_tid_count 台普通模式设备缺少 transport_id,已忽略${NC}"
|
||
fi
|
||
|
||
# 对普通模式设备并发发送进入 recovery 的命令,尽量减少多机等待时间。
|
||
if [ "${#normal_transport_ids[@]}" -gt 0 ]; then
|
||
echo "发送进入recovery命令..."
|
||
declare -A reboot_pid_to_tid=()
|
||
|
||
for tid in "${normal_transport_ids[@]}"; do
|
||
(
|
||
if "$adb_cmd" -t "$tid" reboot recovery >/dev/null 2>&1; then
|
||
echo "[transport_id:$tid] 已发送: reboot recovery"
|
||
exit 0
|
||
fi
|
||
|
||
if "$adb_cmd" -t "$tid" shell recovery >/dev/null 2>&1; then
|
||
echo "[transport_id:$tid] 已发送: shell recovery"
|
||
exit 0
|
||
fi
|
||
|
||
echo "[transport_id:$tid] 进入recovery失败" >&2
|
||
exit 1
|
||
) &
|
||
pid=$!
|
||
reboot_pid_to_tid["$pid"]="$tid"
|
||
done
|
||
|
||
reboot_failures=0
|
||
for pid in "${!reboot_pid_to_tid[@]}"; do
|
||
if ! wait "$pid"; then
|
||
reboot_failures=$((reboot_failures + 1))
|
||
fi
|
||
done
|
||
|
||
if [ "$reboot_failures" -gt 0 ]; then
|
||
echo -e "${RED}错误: 有 $reboot_failures 台设备发送recovery命令失败${NC}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
declare -a selected_boot_serials=()
|
||
declare -A selected_boot_set=()
|
||
declare -A tid_to_boot_serial=()
|
||
|
||
# 记录本次要参与烧录的 BOOT 设备,避免重复加入。
|
||
add_selected_boot() {
|
||
local serial="$1"
|
||
if [ -z "${selected_boot_set[$serial]:-}" ]; then
|
||
selected_boot_serials+=("$serial")
|
||
selected_boot_set["$serial"]=1
|
||
fi
|
||
}
|
||
|
||
for boot_serial in "${initial_boot_serials[@]}"; do
|
||
add_selected_boot "$boot_serial"
|
||
done
|
||
|
||
# recovery 之后 serial 可能变化,优先按 USB 端口把普通模式设备和 BOOT 设备重新对应起来;
|
||
# 如果拿不到 USB 信息,再回退到“未分配的 BOOT 设备顺序匹配”。
|
||
echo "等待设备进入recovery并识别BOOT序列号..."
|
||
deadline=$((SECONDS + RECOVERY_TIMEOUT))
|
||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||
mapfile -t scan_lines < <(scan_connected_devices)
|
||
declare -a scan_boot_serials=()
|
||
declare -A scan_boot_by_usb=()
|
||
|
||
for line in "${scan_lines[@]}"; do
|
||
IFS="$DEVICE_FIELD_SEP" read -r serial usb tid <<< "$line"
|
||
if [[ "$serial" == BOOT-* ]]; then
|
||
scan_boot_serials+=("$serial")
|
||
if [ -n "$usb" ]; then
|
||
scan_boot_by_usb["$usb"]="$serial"
|
||
fi
|
||
fi
|
||
done
|
||
|
||
for tid in "${normal_transport_ids[@]}"; do
|
||
if [ -n "${tid_to_boot_serial[$tid]:-}" ]; then
|
||
continue
|
||
fi
|
||
|
||
usb="${transport_to_usb[$tid]:-}"
|
||
if [ -n "$usb" ] && [ -n "${scan_boot_by_usb[$usb]:-}" ]; then
|
||
boot_serial="${scan_boot_by_usb[$usb]}"
|
||
tid_to_boot_serial["$tid"]="$boot_serial"
|
||
add_selected_boot "$boot_serial"
|
||
fi
|
||
done
|
||
|
||
for tid in "${normal_transport_ids[@]}"; do
|
||
if [ -n "${tid_to_boot_serial[$tid]:-}" ]; then
|
||
continue
|
||
fi
|
||
|
||
for boot_serial in "${scan_boot_serials[@]}"; do
|
||
if [ -z "${selected_boot_set[$boot_serial]:-}" ]; then
|
||
tid_to_boot_serial["$tid"]="$boot_serial"
|
||
add_selected_boot "$boot_serial"
|
||
break
|
||
fi
|
||
done
|
||
done
|
||
|
||
if [ "${#selected_boot_serials[@]}" -ge "$target_count" ]; then
|
||
break
|
||
fi
|
||
|
||
remaining=$((target_count - ${#selected_boot_serials[@]}))
|
||
echo "仍在等待 $remaining 台设备进入recovery..."
|
||
sleep "$POLL_INTERVAL"
|
||
done
|
||
|
||
if [ "${#selected_boot_serials[@]}" -lt "$target_count" ]; then
|
||
echo -e "${RED}错误: recovery设备数量不足,期望 $target_count,实际 ${#selected_boot_serials[@]}${NC}"
|
||
echo "当前识别到的BOOT设备:"
|
||
for serial in "${selected_boot_serials[@]}"; do
|
||
echo "- $serial"
|
||
done
|
||
exit 1
|
||
fi
|
||
|
||
echo "本次烧录目标设备:"
|
||
for serial in "${selected_boot_serials[@]}"; do
|
||
echo "- $serial"
|
||
done
|
||
|
||
# 单台设备的完整烧录流程:进入升级态、按顺序 push 文件、最后重启。
|
||
flash_one_device() {
|
||
local serial="$1"
|
||
local idx local_path remote_path
|
||
|
||
if [ "$INCLUDE_BOOT" -eq 1 ]; then
|
||
echo "[$serial] 执行升级准备命令"
|
||
if ! "$adb_cmd" -s "$serial" shell "root;listenai;upgrade enter" >/dev/null; then
|
||
echo "[$serial] 执行升级准备命令失败" >&2
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
for idx in "${!LOCAL_FILES[@]}"; do
|
||
local_path="${LOCAL_FILES[$idx]}"
|
||
remote_path="${REMOTE_PATHS[$idx]}"
|
||
|
||
echo "[$serial] push $local_path -> $remote_path"
|
||
if ! "$adb_cmd" -s "$serial" push "$local_path" "$remote_path" >/dev/null; then
|
||
echo "[$serial] push失败: $local_path" >&2
|
||
return 1
|
||
fi
|
||
done
|
||
|
||
if "$adb_cmd" -s "$serial" shell reboot hard >/dev/null 2>&1; then
|
||
echo "[$serial] 烧录完成,设备正在重启"
|
||
return 0
|
||
fi
|
||
|
||
if "$adb_cmd" -s "$serial" reboot >/dev/null 2>&1; then
|
||
echo "[$serial] 烧录完成,已执行adb reboot"
|
||
return 0
|
||
fi
|
||
|
||
echo "[$serial] 文件已push,但重启命令失败" >&2
|
||
return 2
|
||
}
|
||
|
||
# 多台设备并发烧录,最后统一汇总结果。
|
||
echo "开始并发烧录..."
|
||
declare -A flash_pid_to_serial=()
|
||
for serial in "${selected_boot_serials[@]}"; do
|
||
flash_one_device "$serial" &
|
||
pid=$!
|
||
flash_pid_to_serial["$pid"]="$serial"
|
||
done
|
||
|
||
declare -a success_serials=()
|
||
declare -a failed_serials=()
|
||
for pid in "${!flash_pid_to_serial[@]}"; do
|
||
serial="${flash_pid_to_serial[$pid]}"
|
||
if wait "$pid"; then
|
||
success_serials+=("$serial")
|
||
else
|
||
failed_serials+=("$serial")
|
||
fi
|
||
done
|
||
|
||
echo
|
||
echo "烧录结果汇总:"
|
||
echo "- 成功: ${#success_serials[@]} 台"
|
||
for serial in "${success_serials[@]}"; do
|
||
echo " [OK] $serial"
|
||
done
|
||
|
||
echo "- 失败: ${#failed_serials[@]} 台"
|
||
for serial in "${failed_serials[@]}"; do
|
||
echo " [FAIL] $serial"
|
||
done
|
||
|
||
if [ "${#failed_serials[@]}" -gt 0 ]; then
|
||
echo -e "${RED}存在失败设备,请检查USB连接、授权弹窗和设备日志${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
echo -e "${GREEN}全部设备烧录完成${NC}"
|