Files
arcs/res/arcs-mini/adb_download.sh
2026-08-13 16:50:52 +08:00

488 lines
14 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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}"