- BrainTools - https://www.braintools.ru -
Начну с того, что еще недавно мои познания в схемотехнике балансировали на уровне непонимания различия между транзистором и резистором. Из всего арсенала, хоть как-то применимого к embedded, за душой был только Rust. Но когда на радаре замаячил так называемый встриваемый AI (машинное обучение [1] и компьютерное зрение [2] прямо на копеечных микроконтроллерах), – спортивный интерес [3] взял верх. Не тратя недели на теорию, я просто доехал до радиорынка и вернулся с джентльменским набором первопроходца: ESP32, первым попавшимся экраном и энкодером. Амбиции по запуску нейросетей моментально разбились о суровую реальность первого вечера: как вообще заставить этот кусок текстолита ожить и вывести на дисплей хотя бы пару цифр?
Цифры при этом были вполне конкретные. У меня сразу несколько подписок на LLM, и у каждой свои лимиты: окно на пять часов, окно на неделю, месячный потолок. Сбрасываются они в разное время, живут на разных сайтах, а узнать остаток можно, только открыв нужную вкладку. Мне хотелось поднять глаза от клавиатуры и сразу увидеть, на что еще хватит квоты.
В итоге получился настольный монитор: нативное приложение не жрущее много памяти [4] для Windows, которое как раз собирает лимиты четырех подписок, и маленький экран с ESP32 показывающий их прямо перед глазами.
Система состоит из трёх частей:

Windows-приложение получает квоты Claude, Codex, Grok и Kimi и отдаёт их в виде одного JSON.
ESP32 раз в 30 секунд забирает этот JSON по Wi-Fi и рисует его на экране. Энкодер переключает подписки и страницы.
Телефон открывает тот же адрес и видит адаптивную страницу.
Windows-приложение, прошивка ESP32 и общая структура данных между ними написаны на Rust (страница dashboard – обычные HTML и JS). Для меня это было главным аргументом начать: единственное, что я умел, должно было заработать с обеих сторон.
Список деталей короткий:
плата на ESP32 с USB-мостом CH340, в Windows она появилась как COM-порт;
цветной экран 1.8″ SPI, 128×160;
энкодер KY-040;
светодиод и резисторы.
Первая неожиданность возникла с экраном. Исходный план рассчитывал на распространенный контроллер ST7789, но на полученном модуле красовалась маркировка 1.8 TFT SPI 128×160 V1.1, по которой точную модель чипа определить невозможно. Методом проб и ошибок я выбрал драйвер ST7735S – это рабочее предположение, подтвержденное практикой. Шелкография на плате тоже преподнесла сюрприз: вместо привычных для SPI обозначений DC и MOSI там были A0 и SDA, так что распиновку пришлось сопоставлять с даташитами вручную.
Вторая неожиданность была приятной: транзисторы мне не понадобились вообще. Весь железный экспресс-курс свелся к правильному расчету резисторов:
подсветке экрана нужен токоограничивающий резистор номиналом порядка 220 Ом;
индикаторному светодиоду связи нужен свой резистор;
сигнальные входы нельзя подключать к 5 В, к счастью у ESP32 логика [5] 3,3 В.
|
Экран TFT |
ESP32 |
Назначение |
|---|---|---|
|
VCC, GND |
3V3, GND |
Питание модуля |
|
CS |
GPIO27 |
Chip Select |
|
RESET |
GPIO25 |
Сброс дисплея |
|
A0 (DC) |
GPIO26 |
Data / Command |
|
SDA (MOSI) |
GPIO23 |
Линия данных SPI |
|
SCK |
GPIO18 |
Тактовая частота SPI |
Энкодер подключён к GPIO2 (CLK), GPIO4 (DT) и GPIO16 (SW) с внутренними подтяжками. Отдельный светодиод на GPIO22 горит, когда нет связи с приложением на ПК.
Прошивка крутится на esp-idf-svc и esp-idf-hal, то есть с полноценной поддержкой стандартной библиотеки std. За графику отвечают крейты embedded-graphics и драйвер mipidsi. Звучит прямолинейно, но сборка Rust под Xtensa на Windows с первого раза превращается в увлекательный квест:
Тулчейн. Для архитектуры Xtensa требуется отдельный форк компилятора Rust, который накатывается через утилиту espup. Дополнительно в системе должны стоять CMake, Ninja и Microsoft C++ Build Tools.
Генератор проекта. Свежий cargo-generate отказался собираться моим системным компилятором — пришлось явно фиксировать версию постарше.
Длинные пути. Сборка ESP-IDF в глубинах моих каталогов внезапно упала с фатальной ошибкой [6] Too long output directory и вежливой рекомендацией уложиться в десять символов. Инженерное решение выглядит забавно, но безотказно: корень сборки переехал в C:t.
Порт занят. Access is denied при прошивке означало не режим BOOT, а то, что старый монитор порта ещё открыт в другом окне терминала =).
BOOT. Периодический Failed to connect to ESP32 лечится классическим зажатием аппаратной кнопки BOOT на плате в момент инициализации соединения.
Когда пыль улеглась, плата успешно подняла Wi-Fi стек и приняла по сети первый JSON.
Соблазн стучаться в API провайдеров языковых моделей напрямую с ESP32 был велик, но от этой идеи я отказался почти сразу. Сложные цепочки OAuth2, постоянное TLS-рукопожатие, ротация refresh-токенов и парсинг четырех разнородных JSON-схем быстро сожрут скудные ресурсы микроконтроллера.
Поэтому токены живут только на Windows и шифруются пользовательским DPAPI. Микроконтроллер получает отфильтрованный открытый payload, где нет ничего, кроме цифр:
{
"updated_at": "2026-10-29T12:17:16Z",
"age_seconds": 8,
"stale_after_seconds": 900,
"providers": {
"kimi": {
"status": "ok",
"source": "Kimi Code OAuth usage",
"windows": [
{ "name": "5h", "remaining_percent": 0, "resets_at": "2026-10-29T10:30:54Z" }
]
}
}
}
У каждого сервиса своя схема, и все они разные:
Claude отдаёт utilization, то есть использованную долю, поэтому остаток приходится вычислять инверсией. Учитываются скользящие окна на 5 часов и 7 дней, а также кастомные лимиты моделей.
Codex отдает структурированные окна с точной длительностью и таймстемпом сброса. Помимо прямого входа, я предусмотрел режим работы через локальный read-only балансировщик codex-lb.
Grok держит метрики внутри биллинга: недельный процент потребления либо месячную квоту при общем плане.
Kimi отдаёт окно на пять часов и месячные лимиты. Недельного окна у новых планов, как следует из документации, больше нет, поэтому экран показывает то, что реально пришло.
Чтобы интерфейс не вводил в заблуждение, я заложил четыре строгих правила обработки данных:
Неизвестное не равно нулю. Если провайдер процент не сообщил, показывается --, а не 0%.
Время сброса не выдумывается. Если его нет в ответе, на экране RST unknown.
Устаревшее помечается. Если время сброса уже прошло или источник давно не отвечал, статус становится STALE, а цифры не подаются как свежие.
Одно исключение, которое я ввёл сам. Если исчерпан глобальный недельный пул (7d = 0), остаток 5-часового окна принудительно зануляется (0%), поскольку фактически отправка запросов заблокирована до общего сброса недели.
Кнопки входа открывают системный браузер. Для Claude и Codex это авторизация с PKCE и возвратом на локальный порт, для Grok и Kimi device flow с кодом на странице. Пароль в окно я не ввожу никогда. Токены уходят в DPAPI, а новый refresh-токен записывается атомарно до следующего запроса: потерять его при ротации значит потерять вход.
Здесь была и честная неприятность. Claude уже отвечал мне 429, и приложение показывает это как ошибку, но сохраняет последние известные цифры помеченными как устаревшие. Чтобы это было видно сразу, строка провайдера с ошибкой запроса подсвечивается красным:
Error, строка красная. Остальные строки показывают AuthRequired, и красными они не бывают: отсутствие входа это не сбой запроса.На экране 160×128 помещаются четыре строки. Обзор строится только из тех провайдеров, которые реально пришли в ответе, без заготовленных пустых мест. Подключу пятого, он появится сам. Для каждого видны остатки первых двух окон, статус и время их сброса по UTC.
Нажатие энкодера открывает детали провайдера, вращение листает страницы окон и провайдеров:
RST ММ-ДД ЧЧ:ММZДекодер энкодера получился самой «железной» частью кода. Он читает два сигнала по таблице переходов, и четыре перехода засчитываются как один щелчок:
const DELTAS: [i8; 16] = [0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0];
Кнопка декодера получила программный дебаунс на 20 мс, а сам опрос уехал в изолированный поток с интервалом в 1 мс чтобы не ронять системный стек Wi-Fi. После комфортного мира веб-разработки это отдельная ломка парадигмы: быстро выясняется, что ядра здесь – не абстрактные облачные vCPU, а вполне осязаемый кремний, где каждая задача должна тикать строго в своем ритме и на своем ядре, иначе плата мгновенно напомнит о себе паникой или таймером.
Тот же адрес открывается в браузере. Страница адаптивная и не загружает ничего со сторонних серверов, по умолчанию она сама обновляется раз в 30 секунд:
На широком экране карточки выстраиваются в ряд:
Мне хотелось, чтобы мое приложение не жрало системные ресурсы и просто весело в трее, поэтому десктопная часть написана на чистом Win32 API – без монструозных Electron, CEF или системных WebView, новомодных Tauri и бла-бла-бла. Замеры release-сборки с тремя активными подписками (Claude, Codex, Grok; до добавления Kimi):
память: около 17,9 МиБ working set и 2,8 МиБ private bytes;
CPU: за минуту наблюдения прирост составил 0 мс;
размер exe-шника: около 3,7 МиБ.
Естественно, нулевое потребление процессора фиксируется в покое – во время выполнения сетевых TLS-запросов и шифрования всплески активности есть, но они мгновенны и не заметны.
Для полноты картины приложение обзавелось собственной лаконичной иконкой в трее:
Остальные ограничения, о которых стоит помнить:
Эндпоинты неофициальные. Квоты читаются теми же адресами, что и агентные CLI. Они могут измениться или закрыться.
Правила сервисов. Сессии подписок переиспользуются сторонней утилитой. В документации того же Claude Code прямо указано, что OAuth-авторизация предназначена для их нативного софта, а интеграторам рекомендуется работать через классические API-ключи.
HTTP в локальной сети без шифрования. ESP32 забирает данные по открытому HTTP-протоколу. В пакетах передаются только проценты и даты без токенов и персональных данных, но выставлять такой сервис за пределы NAT не следует.
Докупить экран побольше!
Спроектировать и напечатать на 3D-принтере компактный корпус для рабочего стола.
Полный исходный код проекта, схемы соединений и пошаговое руководство по сборке опубликованы в репозитории: Тык [7].
Автор: AquaDll
Источник [8]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/37751
URLs in this post:
[1] обучение: http://www.braintools.ru/article/5125
[2] зрение: http://www.braintools.ru/article/6238
[3] интерес: http://www.braintools.ru/article/4220
[4] памяти: http://www.braintools.ru/article/4140
[5] логика: http://www.braintools.ru/article/7640
[6] ошибкой: http://www.braintools.ru/article/4192
[7] Тык: https://github.com/spgsroot/llm-quota-monitor
[8] Источник: https://habr.com/ru/articles/1101302/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1101302
Нажмите здесь для печати.