У меня есть умный телевизор Yandex. По моему субъективному мнению колонки и ТВ у Яндекса получились отличные. Иногда появляется желание сделать умный телевизор чуть умнее.
На очереди функция «датчик движения». Конечно же хотелось использовать CSI, поскольку он в отличие от грубых интегральных метрик вроде RSSI (уровень мощности сигнала)предоставляет детализированную оценку радиоканала на уровне отдельных поднесущих частот. Что в принципе позволяет пойти сильно дальше просто обнаружения присутствия.

Идея была такая: телевизор и роутер стоят на месте, а человек между ними меняет условия распространения сигнала. Причём сигнал приходит не только по прямой — он отражается от стен, мебели и других предметов. Когда кто‑то проходит, меняется сочетание этих путей. Остаётся понять, какие изменения телевизор позволит прочитать.

Самый доступный вариант — RSSI, уровень принимаемого сигнала. Android отдаёт его через WifiInfo.getRssi() в dBm. По сути, мы видим одно число: сейчас −30, через некоторое время −34. Можно увидеть изменение мощности, но нельзя понять по этому числу, что произошло с отдельными составляющими радиоканала.

CSI — Channel State Information — даёт сильно больше подробностей. Вместо одного показателя мощности можно получить оценки амплитуды и фазы на отдельных поднесущих OFDM, а при соответствующей поддержке — ещё и для разных пар передающих и принимающих антенн. Общая мощность может почти не измениться, хотя внутри этой картины уже что‑то произошло. Для поиска движения это интереснее, но такие данные ещё нужно очистить от помех и особенностей измерения.

У моей Яндекс ТВ Станции YNDX-00092 заявлен Wi‑Fi 802.11a/b/g/n/ac, 2T2R, в диапазонах 2,4 и 5 ГГц. Это есть в её характеристиках. Но две передающие и две принимающие радиоцепи сами по себе не означают, что стороннее приложение сможет получить CSI. Приёмник оценивает канал, чтобы принимать данные; выдавать эти оценки нашему APK он не обязан.

Здесь и возникла первая развилка. RSSI уже есть в обычном Android API. Для CSI общего публичного Android API нет: нужен способ извлечения данных, подходящий к конкретному Wi‑Fi‑чипу, его драйверу и прошивке. Например, Nexmon CSI работает с определёнными чипами Broadcom и версиями прошивок. Установить его как универсальную библиотеку на любой телевизор не получится.

Смотрим, что у нас за радиомодуль. На плате маркировка SKO.WB663U.10, беглый поиск говорит что это модуль от МТК MT7663 и драйвер wlan_mt7663_us.

Для первого опыта выбрал то, что можно проверить без переделки прошивки: RSSI. Приложение читает его дважды в секунду и рисует график. Это частота опроса приложения; сам драйвер может обновлять показания реже. Получился способ проверить исходную идею на уже имеющемся железе, прежде чем лезть глубже.

Без движения у меня держалось около −30 dBm. При проходе показания уходили примерно к −32…−35 dBm. Вернулся на место — сигнал снова близок к фону. Перепроверил покой: линия оставалась почти ровной.
На снимке ТВ приложение обнаружило три прохода. Это уже повод продолжить опыт.

 Слева - ТВ после проверки проходов, справа - фрагмент экрана телефона с включённой охраной и журналом. Это разные моменты тестирования.
Слева — ТВ после проверки проходов, справа — фрагмент экрана телефона с включённой охраной и журналом. Это разные моменты тестирования.

Почему в итоге RSSI

На этом месте можно было остановиться: RSSI работает, проходы видны. Но хотелось проверить, не лежат ли более подробные данные где‑нибудь за одной удачной ADB‑командой. C ADB у Яндекса есть некоторые трудности, поэтому эксперименты продолжились на другом подопытном с тем же радиомодулем.

В драйвере нашёл ICAP — заводской механизм внутреннего захвата. Первые текстовые команды возвращали ноль, но ничего полезного не отдавали. Оказалось, этот интерфейс не передаёт наружу результат обработки команды. Через бинарный протокол MediaTek QA удалось получить настоящий статус: сначала ожидание, затем завершение захвата. После этого прочитал I и Q для обеих радиоцепей.

Звучит как победа, но здесь легко выдать желаемое за действительное. I/Q — ещё не CSI. В проверенном режиме ADC это отсчёты радиотракта, а не готовые оценки канала по поднесущим. Чтобы получить из них такие оценки, нужно знать формат, частоту дискретизации и найти обучающую часть принимаемого пакета. Просто нарисовать график ненулевых чисел или сделать FFT недостаточно.

Захват завершался после отдельного перехода в режим ICAP. Проверил и обычное соединение с роутером: ТВ был подключён к сети 5G на канале 36, счётчик принятых пакетов рос, но четыре запроса статуса за 12 секунд так и показали ожидание. Но рабочей команды для постоянного наблюдения у меня не появилось.

Другой зацепкой стали профили beamforming в памяти PFMU. Команды чтения отвечали, данные по поднесущим возвращались. Только проверенные профили на стороне beamformer были помечены как недействительные, а прочитанный профиль не менялся за шесть опросов. Ненулевое содержимое памяти не означает, что перед нами свежие измерения канала.

Дальше полез в файл прошивки MT7663. У него четыре области, и все помечены как зашифрованные. Загрузчик драйвера передаёт их радиомодулю в таком виде. Поэтому открыть файл в дизассемблере и посмотреть, что означают триггеры захвата, не получилось. Попробовал штатный запрос дампа: всего 48 байт по адресу загрузки кода. Запрос закончился ошибкой, данных не получил.

На этом я остановился. Не потому, что MT7663 в принципе не способен оценивать канал, а потому, что на моей связке чипа, драйвера и прошивки не нашёл способа получать эти оценки для приложения. Дальше — отдельный стенд, разбор прошивки или документация MediaTek. Это уже другой объём работы, чем установить APK и проверить проходы в комнате.

Кое‑что полезное всё же нашлось: через драйвер можно читать RSSI по двум антеннам, SNR и статистику приёма. Это возможное продолжение эксперимента, но в текущем приложении используется обычный RSSI из Android API. Улучшение точности на дополнительных данных я пока не проверял.

Так что CSI остался как след исходной задумки. А датчик работает на RSSI: эти данные доступны приложению, и на моей расстановке их оказалось достаточно, чтобы заметить проходы. Для домашнего прототипа я выбрал результат, который смог проверить.

Собираем прототип. Как отличать проход от обычного скачка

Сначала кажется, что достаточно поставить порог: изменилось на несколько децибел — отправляй уведомление. Но хочется не получать тревогу от одного случайного значения и не считать один длинный провал десятью проходами.

В текущей версии измеряю среднее значение фона и его разброс. Для обычной чувствительности порог — максимум из 3 дБ и трёх стандартных отклонений плюс 1 дБ. Изменение должно подтвердиться двумя опросами подряд. После события жду возвращения сигнала к фону, прежде чем разрешить следующее.

На этом этапе интереснее понять, насколько хорошо работает простое правило и где оно начинает ошибаться.

Калибровка занимает примерно минуту: 30 секунд стоять вне зоны без движения, затем три раза пройти между роутером и ТВ за следующие 30 секунд. Если приложение не видит достаточно отличающихся от фона событий, предлагает повторить проверку.

Как события попадают на телефон

Хотелось получить уведомления, но не хотелось заводить сервер, который потом придётся обслуживать. Home Assistant для моего случая добавлял лишнюю настройку. С навыком Алисы тоже нужен обработчик запросов. Для прототипа выбрал ntfy.

На ТВ и телефон ставится один APK. На телевизоре выбираешь режим датчика, на телефоне — пульта. Сканируешь QR‑код, и устройства получают каналы обмена и ключ. Пользователю не нужно заходить на сайт ntfy или ставить его приложение.

Команды и события идут по отдельным случайным каналам, содержимое шифруется AES-256-GCM. Собственного сервера проекта нет, но доставка зависит от внешнего сервиса. Он видит время, IP и размер обмена.

С телефона можно включить и выключить охрану. Сначала приложение ждёт ответ ТВ. При включении даёт 30 секунд на выход, затем получает подтверждение перехода в режим охраны.

У бесплатного ntfy есть квота — 250 сообщений в сутки. Поэтому измерения остаются на ТВ, уведомление о движении отправляется не чаще раза в минуту. Остальные срабатывания записываются в локальный журнал. Служебные сообщения тоже приходится считать: автоматический статус идёт раз в 30 минут, для быстрой проверки есть кнопка обновления.

Что такой датчик умеет и чего пока не умеет

Мои проходы приложение поймало. Но если я прошёл и сел на диван, оно не обязано продолжать видеть моё присутствие. RSSI может вернуться к фону, хотя человек никуда не ушёл.

Поэтому сейчас это скорее экспериментальный датчик движения, тестирование концепции, а не полноценный датчик присутствия. Как итог, технология выглядит рабочей, позволяет использовать стационарное андроид устройство как датчик движения например в Home Assistant.

Код собирал с помощью ИИ‑ассистента если кому интересно Исходники и тестовый APK выложил на GitHub. CSI из этого телевизора я пока не получил.
Если кто‑то уже вытаскивал оценки канала из MT7663, особенно с драйвером GEN4M, буду рад подсказкам. А домашний эксперимент продолжу на RSSI — без ещё одной коробочки на стене.