# ADB через Wi-Fi (TCP/IP) ARC умеет работать с устройствами не только по USB-кабелю, но и по Wi-Fi (ADB-over-TCP). Включается автоматически, **без участия пользователя**: USB остаётся основным транспортом, Wi-Fi — горячий резерв, который подхватывается при отключении кабеля. ## Поведение 1. Пользователь, как и раньше, подключает телефон по USB. 2. Фоновый `DeviceScreener`, обнаружив **простаивающее** USB-устройство без живого Wi-Fi-эндпоинта, автоматически: - читает IP телефона в сети Wi-Fi (`wlan0`) и его MAC; - переводит adbd в TCP-режим (`adb tcpip `); - подключается по сети (`adb connect ip:port`); - сохраняет эндпоинт в `settings` (ключ `wifi_endpoints`, JSON `{ android_id: { "endpoint": "ip:port", "mac": "aa:bb:.." } }`; старый формат — значение-строка `"ip:port"` — читается прозрачно). 3. После этого кабель можно отключить — устройство **остаётся онлайн** (статус привязан к `android_id`, а не к ADB-serial; меняется только `adbSerial` на `ip:port`). 4. При старте ARC и периодически воркер переподключает сохранённые эндпоинты (`adb connect`), так что устройство, у которого кабель уже отключён, само появляется онлайн. 5. Если беспроводной эндпоинт «завис», воркер чинит его пересозданием TCP (`adb disconnect` → `adb connect`): голый повторный `connect` вернул бы `already connected` и ничего бы не починил. `disconnect` ещё и расклинивает это «залипшее» состояние — после него последующие `connect` (в т.ч. из периодического reconnect) становятся реальными попытками. Срабатывает по двум сигналам: - эндпоинт виден в `adb devices` со статусом `offline`; - устройство ушло в debounced-offline (30 c не отвечает), хотя `adb devices` всё ещё держит его как `device` — частый реальный случай «полумёртвого» соединения при слабом Wi-Fi / уходе телефона в doze, когда команды виснут, а статус не меняется (принудительный `disconnect→connect`, «kick», на переходе в debounced-offline). Починка **пропускается** для устройств с активной/ожидающей задачей (см. инварианты). 6. Если `connect` по сохранённому IP не удался (адрес сменился по DHCP), воркер ищет устройство в сети по сохранённому **MAC** (ARP-таблица ОС, при включённой опции — с прогревом через ping-скан /24) и переподключается на новый IP, обновляя эндпоинт. ## Инварианты - **USB приоритетнее.** Если один `android_id` виден одновременно по USB-serial и по `ip:port`, каноническим `adbSerial` (по нему ходят банк-скрипты) выбирается USB. Wi-Fi-эндпоинт остаётся резервом и используется только когда USB пропадёт. - **Разрывающие операции — только на простаивающих устройствах.** `adb tcpip` перезапускает adbd, а принудительный `disconnect→connect` (kick) рвёт текущее соединение — поэтому ни bootstrap, ни kick не выполняются, если на устройстве есть активная/ожидающая задача (тот же сигнал занятости `EventDAO::getDevicesWithPendingEvents()`, по которому `EventHandler` не назначает задачи). Иначе разрыв оборвал бы выполняющийся bank-скрипт. - **Бесшовное отключение кабеля.** К моменту отключения USB Wi-Fi-эндпоинт уже поднят и виден в `adb devices`, поэтому перехода в офлайн не происходит. ## Конфигурация (`config.ini`, секция `[wifi]`) | Ключ | Назначение | Дефолт | |---|---|---| | `enabled` | Полностью вкл/выкл функционал | `true` | | `port` | TCP-порт adbd для tcpip-режима | `5555` | | `reconnectIntervalSecs` | Как часто переподключать сохранённые эндпоинты | `30` | | `bootstrapRetryIntervalSecs` | Мин. интервал между попытками bootstrap одного устройства | `120` | | `macRediscoverEnabled` | Искать устройство по MAC при смене IP (DHCP) | `true` | | `macRediscoverIntervalSecs` | Мин. интервал между попытками поиска по MAC | `300` | | `subnetSweepEnabled` | Прогрев ARP ping-сканом /24 (медленно/шумно) | `false` | ## Ограничения - **Перезагрузка телефона** сбрасывает tcpip-режим (adbd возвращается в USB-only). Wi-Fi-резерв поднимется заново при следующем подключении кабеля. - **Смена IP** (новый адрес по DHCP) обрабатывается поиском по MAC: при неуспешном `adb connect` воркер находит устройство в ARP-таблице по сохранённому MAC и переподключается на новый IP. Если устройства нет в ARP, помогает либо включённый `subnetSweepEnabled` (ping-скан /24 — медленно, шумно), либо повторный bootstrap по USB. Поиск по MAC требует, чтобы ARC и устройство были в одной L2-подсети (ARP не маршрутизируется), и работает только когда MAC был снят при bootstrap. - Требуется, чтобы телефон и десктоп были в одной сети Wi-Fi (маршрутизируемой до `ip:port` устройства). - **Чтение MAC** идёт через `ip addr show wlan0` (та же команда, что и для IP), фолбэк — `cmd wifi status`. Оба проходят без root, включая MIUI/HyperOS (где `cat /sys/...` и `ip link show` отдают Permission denied). Обычно это рандомизированный per-SSID MAC, но он стабилен для сети и совпадает с тем, что видит ARP-таблица ПК. Если MAC снять не удалось — эндпоинт хранится без него: теряется только восстановление при смене IP, остальное (bootstrap, reconnect, kick) работает. - **Скриншоты по Wi-Fi медленнее.** Основной путь захвата — http через APK arc-shot (байты идут мимо adb, см. `[screenshot]` в config.ini); при его недоступности — fallback на `exec-out screencap`, который отдаёт полноразмерный PNG через adb и по сети тяжелее USB. Для wireless-serial таймаут fallback-захвата поднят (12 c против 10 c по USB). Интервал превью задаётся `[screenshot]` (`previewIntervalHttpSecs` для http, `previewIntervalSecs` для exec-out) и от транспорта Wi-Fi не зависит напрямую. На детект online/offline не влияет; на слабом линке отдельные кадры всё равно могут не успевать. - **Залипшее соединение vs. статус `offline`.** На практике мёртвый Wi-Fi часто остаётся в `adb devices` как `device` (команды виснут), а не переходит в `offline` — поэтому основной триггер починки именно debounced-offline, а не статус `offline` (см. Поведение). ## Требования к сети Не требуют доработок ARC — это настройки сети/развёртывания. **Обязательно (иначе Wi-Fi-ADB не работает):** - ПК с ARC и устройства — в одной L2-подсети (или маршрутизируемой до `ip:5555`). - На роутере **отключить AP isolation (изоляцию клиентов Wi-Fi)** — иначе порт `5555` между узлами недоступен. - Единый `adb` (бандл `tools/adb/...`), один adb-сервер — чтобы списки устройств у ARC и фоновых операций не расходились. **Опционально (предсказуемый адрес устройства — снижает число переподключений):** смену IP по DHCP ARC и так переживает поиском по MAC (см. Ограничения), поэтому ниже — не требования, а способы сделать адрес стабильным: | Способ | Где настраивать | Работает на Android <11 | |---|---|---| | DHCP-резервация по MAC | роутер | да | | Статический IP на самом устройстве | каждое устройство | да | | mDNS-имя (`adb connect <имя>.local`) | устройство + сеть | нет (только 11+; **в ARC не используется**) | | Поиск по MAC силами ARC (`macRediscoverEnabled`) | — (уже в ARC) | да | ## Безопасность `adb tcpip` открывает порт **5555 на самом телефоне** (на его Wi-Fi-IP), а не на ПК. Парк из N устройств = N эндпоинтов `ip:5555`, различаются по IP. Это отладочный доступ к устройству, достижимый для **любого узла той же L2-подсети**. Доступ всё ещё проходит RSA-авторизацию adb (чужой ключ упрётся в запрос «Allow debugging?» на экране), но сама открытая поверхность + контекст (это **банковские приложения**) требуют сетевой гигиены. На уровне приложения порт закрыть нельзя — на нерутованном устройстве это свойство adbd, поэтому безопасность здесь **почти целиком инфраструктурная**: - **Выделенная/изолированная Wi-Fi-сеть для фермы** — без выхода в недоверенный сегмент. На роутере **отключить AP isolation** (иначе узлы не видят порт 5555 друг друга — это и про работоспособность), но саму сеть держать сегментированной (VLAN/гостевую-наоборот). - **Защита `adbkey` управляющего ПК.** RSA-ключ этой машины — фактический «пароль» ко **всему** парку (особенно при «всегда разрешать с этого компьютера»). Хранить только на ней, доступ к машине ограничить. - **Ограничение на уровне роутера/файрвола** — сузить, кто вообще достаёт до `:5555` устройств (по возможности — только управляющий ПК). - **Поиск по MAC / `subnetSweepEnabled`** сканируют подсеть — ещё одна причина держать ферму в выделённой сети (в чужой/общей сети скан нежелателен). > Главный практический долг по безопасности — организационный: выделенная сеть, AP > isolation off + сегментация, защита `adbkey`. В коде ARC тут рычагов нет. ## Где в коде - `android/adb/AdbUtils` — низкоуровневые обёртки (`isWirelessSerial`, `getDeviceWifiIp`, `getDeviceMac`, `enableTcpip`, `connectWireless`, `disconnectWireless`). - `android/adb/WifiAdbManager` — авто-bootstrap (`tryBootstrap`), переподключение (`reconnectKnown`: починка `offline`-эндпоинтов через disconnect→connect и поиск по MAC при смене IP), принудительный kick залипшего эндпоинта (`kickEndpoint`), хранение эндпоинтов (`loadEndpoints`/`forget`). - `services/DeviceScreener` — интеграция в цикл опроса (reconnect c починкой stale → dedup USB/Wi-Fi → kick на debounced-offline (с guard по занятости) → bootstrap простаивающих; увеличенный таймаут/интервал screencap для wireless). - `views/device/DeviceWidget` — пассивный бейдж «Wi-Fi» на карточке устройства; показывается **только когда устройство онлайн** по Wi-Fi (у офлайн-карточки не горит, чтобы не вводить оператора в заблуждение).