Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Как ESP32, MQTT и PowerShell заменили мне умную розетку

Дата публикации: 15-09-2026 10:17:17

Уезжал в долгий отпуск, а дома оставалась рабочая машина с RTX 5090: GPU периодически нужен для локальных моделей. Оставлять её включённой на несколько недель не хотелось, просить родню жать кнопку, тем более.В итоге дома круглосуточно работает только плата ESP32 за пять долларов от USB-зарядки для телефона. Жму кнопку в веб-панели, плата будит ПК по Wake-on-LAN и подтверждает включение в Telegram. После загрузки Windows сама цепляется к MQTT-брокеру и исполняет любые команды: от «выключись» до «выполни python-скрипт и пришли вывод». Отдельной командой поднимается reverse SSH, и локальная Ollama становится доступной как облачный эндпоинт.В статье разбираем, почему включение и управление сделаны двумя раздельными каналами, как устроена прошивка с deep sleep (плата еле тёплая и живёт месяцами), зачем нужен дебаунс команд на 5 секунд и где у схемы честные слабые места. Плюс найденные по дороге грабли: кириллица в Telegram из PowerShell, Wi-Fi, который греет плату сильнее, чем кажется, и авто-выключение через час простоя, если я сам забыл про включённый ПК. Читать далее

Основное содержимое страницы с новостью.

Уровень сложностиСредний

Время на прочтение8 мин

Охват и читатели6.8K

Кейс

Выключил ПК перед отпуском и управляю им с телефона: ESP32 за $5, Wake-on-LAN и два канала команд

Уезжал в долгосрочный отпуск, а дома оставалась рабочая машина с RTX 5090. Я точно знал, что GPU понадобится: локальные модели, обработка медиатеки, эксперименты. Оставлять компьютер включённым на несколько недель не хотелось: недели потребления впустую, плюс машина, к которой никто не присматривает. Просить родню нажать кнопку тоже не вариант. ПК стоит не у них, и «перезагрузи, если завис» они не осилят.

Итог такой: дома круглосуточно работает только ESP32 за пять долларов, питающаяся от обычной USB-зарядки. ПК включается с телефона из любой точки мира примерно за минуту, после загрузки исполняет любые команды и возвращает вывод в Telegram. Ниже разбираю, как это устроено: почему тут два раздельных канала, как я обошёлся без проброса портов и где у схемы слабые места.

Архитектура: будильник и пульт, это разные вещи

Главное решение проекта: включение и управление разделены. Не ради красоты. Просто два сценария происходят в разных состояниях машины. Wake-on-LAN нужен, когда ПК выключен: работает только BIOS, никакой Windows и никакого агента. Команды, наоборот, нужны, когда Windows уже загрузилась. Попытка уместить оба сценария в один механизм и ломается по классике: agent-based wake-up требует работающий ПК, а его как раз нет.

Поэтому тут два независимых канала:

              Телефон (браузер)
                     │ HTTPS
              Веб-панель на VPS
               │              │
      файл-команда       MQTT-брокер
               │              │
            ESP32       Windows-агент
        (deep sleep,      (PowerShell:
         опрос 5 мин)     HTTP :8080 + MQTT)
               │              │
         Wake-on-LAN     произвольные
               │            команды
               └────► Домашний ПК ◄────┘
  • Канал-«будильник»: панель на VPS пишет команду в файл. Дома ESP32 просыпается по таймеру, забирает команду по HTTPS и шлёт ПК магический пакет Wake-on-LAN. Работает, когда ПК полностью выключен.

  • Канал-«пульт»: едва Windows просыпается, она сама цепляется к MQTT-брокеру на том же VPS и слушает, что я напишу. Сюда прилетает всё: и «выключись», и произвольный скрипт, вывод которого потом читаешь в Telegram.

Из этого же решения растёт и спокойствие за безопасность: чтобы дотянуться до домашней сети, снаружи нет ни одного проброшенного порта. Вся магия живёт на VPS, а дома устройства коннектятся сами.

ESP32: один цикл вместо вечного loop

Прошивка не крутит вечный loop(). Плата живёт в deep sleep, просыпается по таймеру раз в 5 минут или по кнопке BOOT (GPIO0, низкий уровень), делает одно дело и сразу засыпает. Выход из deep sleep технически равен аппаратному reset, поэтому вся логика сидит в setup():

void setup() {
  // Кнопку BOOT читаем максимально рано — пока она зажата после wake по ext0
  bool buttonAtWake = (digitalRead(0) == LOW);
  wakeCount++;  // RTC_DATA_ATTR: переживает deep sleep

  setCpuFrequencyMhz(80);      // ниже тактовая → меньше нагрев (минимум для WiFi)
  connectWiFi();               // modem sleep + TX power 11dBm

  if (WiFi.status() != WL_CONNECTED) { goToSleep(); return; }

  if (buttonAtWake) processCommandFromFile(true);   // ручной запуск
  else              processCommandFromFile(false);  // автоопрос

  goToSleep();  // timer + ext0(GPIO0) → esp_deep_sleep_start()
}

void loop() { delay(1000); }  // сюда не доходим

С нагревом я намучился на ранней версии без deep sleep: плата просто жила в loop с постоянным Wi-Fi и была тёплой всегда, рука не обманет. Сначала думал, что дело в мощности радиомодуля, и выкрутил WiFi.setTxPower на максимум. Сигнал стал отличный, но греться плата не перестала, только довольнее.

Итоговых настроек три, и вместе они решают проблему. Тактовая частота 80 МГц, это минимум для работающего Wi-Fi. WiFi.setSleep(true) укладывает радиомодуль спать между маяками точки доступа. И WiFi.setTxPower(WIFI_POWER_11dBm): с запасом хватает для домашней сети, проверено. На практике плата от USB-зарядки для телефона еле тёплая. Поначалу я трогал её пальцем, проверяя, живая ли вообще. Так и живут месяцами.

Состояние, которое переживает сон

Первая мысль, которая приходит в схеме «проснулся, сделал, уснул»: а что, если уснуть на полпути? Отправил Wake-on-LAN, а ПК ещё грузится. В обычной прошивке состояние пропало бы при reset. Тут спасает RTC RAM: переменные с RTC_DATA_ATTR переживают deep sleep, питание на этот блок не пропадает.

RTC_DATA_ATTR bool wakingPc = false;  // «будим ПК» — живёт через deep sleep

// в обработчике команды 'start':
if (isPcOnline()) {                    // TCP-подключение к агенту :8080
  if (wakingPc) {
    wakingPc = false;
    sendTelegramMessage("✅ ПК включён!");
  }
  return;                              // уже включён — WOL не нужен
}
sendWakeOnLan();                       // выключен → будим
wakingPc = true;                       // и запоминаем это

Логика команды start не «отправь пакет», а «добейся включения». Плата повторяет цикл WOL каждые 5 минут, пока агент не ответит, и только тогда снимает флаг и пишет в Telegram. Уснуть на полпути здесь невозможно по построению.

Wake-on-LAN: серия на два broadcast-адреса

Магический пакет тривиален: 6 байт 0xFF и MAC-адрес, повторённый 16 раз. Неочевидны две детали. Первая: часть роутеров и драйверов пропускает только ограниченный broadcast 255.255.255.255, часть только направленный (192.168.1.255), поэтому пакет летит на оба. Вторая: одиночный пакет иногда теряется, ARP ещё не прогрелся, сетевка только просыпается. Отсюда серия с паузой 100 мс:

for (int attempt = 0; attempt < 5; attempt++) {
  udp.beginPacket("255.255.255.255", 9);
  udp.write(packet, 102);
  udp.endPacket();
  udp.beginPacket(bcast, port9);   // направленный: localIP | ~subnetMask
  udp.write(packet, 102);
  udp.endPacket();
  delay(100);
}

Вместо ответа от сетевой карты я проверяю следствие: пробую TCP-подключение к порту HTTP-агента. Агент отвечает, значит Windows загрузилась.

Честно про TLS

Команда забирается по HTTPS, но с WiFiClientSecure::setInsecure(), без проверки сертификата. Для чтения короткого файла с командой риск подмены невысок: атакующему проще перехватить команду в MQTT-транспорте или украсть токен агента. Но это осознанный компромисс. Держать якорь сертификата на ESP32 и обновлять его, отдельная эксплуатационная история, и в списке «что улучшить» она у меня первая.

Канал команд: почему файл, а не проброс портов

Классический вопрос: «зачем файл, можно же пробросить UDP-порт 9 на ПК и слать WOL из интернета». Можно, но:

  • проброс порта внутрь домашней сети это постоянная дыра в периметре, которую надо помнить и обновлять;

  • одиночный пакет из интернета часто теряется: ARP ещё не прогрелся, ретраи с телефона не сделаешь;

  • никакой обратной связи: пакет «ушёл» не значит, что ПК «включился».

Файловый канал решает всё разом: панель пишет команду в файл на VPS, ESP32 сам забирает её изнутри сети, делает серию пакетов и подтверждает включение. Внутрь сети не смотрит никто. Цена канала в задержке до 5 минут, это период опроса. Для сценария «включи ПК к вечеру» неважно. Когда нужно срочно, в панели есть кнопка, поднимающая удалённый доступ через командный канал.

С командами на живой ПК та же философия обратной связи: панель публикует их не напрямую в машину, а в MQTT-брокер на VPS.

Windows: три скрипта и start.bat

После загрузки Windows автоматически стартует start.bat, поднимающий скрипты, на которых держится вся удалённость. Их три, плюс крошечный HTTP-агент на порту 8080, в который стучится ESP32, проверяя, что Windows загрузилась.

MQTT-агент (server_mktt5.ps1) слушает топик брокера. С MQTT-клиентами в PowerShell вышло не по учебнику: нормальных нативных нет. Поэтому агент запускает mosquitto_sub.exe дочерним процессом, перенаправляет стандартный вывод и читает топик построчно. Команды приходят в JSON вида {"command":"cmd","param1":"Get-Process"}:

if ($line -match "^$([regex]::Escape($MQTT_TOPIC))\s+(.+)$") {
    $msg = $matches[1] | ConvertFrom-Json
    $cmd = $msg.command
    $param = $msg.param1

    # дебаунс: тот же command:param в течение 5 секунд игнорируем
    $cmdKey = "$cmd`:$param"
    if ($LastCommands.ContainsKey($cmdKey)) {
        if (($now - $LastCommands[$cmdKey]).TotalSeconds -lt 5) { continue }
    }
    $LastCommands[$cmdKey] = $now
    $cmdResult = Execute-Command -cmd $cmd -param $param
}

Дебаунс тут не украшение, а необходимость. Брокер может доставить сообщение дважды, retained-сообщение перечитывается при переподключении, да и кнопку в панели легко нажать два раза. Пять секунд на ключ «command:param» закрывают все эти случаи.

«cmd» в этом агенте на самом деле полноценный PowerShell, а не командная строка: [scriptblock]::Create($param) плюс вызов с 2>&1 | Out-String. То есть удалённо выполняется скрипт любой сложности, а первые 500 символов вывода уходят отчётом в Telegram. Рядом ключевые слова попроще: shutdown, restart, lock, sleep, hibernate, и запуск файлов: exe, bat, ps1, python.

Отдельная грабля, на которой я обломал кириллицу: Invoke-RestMethod с обычной строкой отправлял Telegram кракозябры. Лечение: отправлять тело байтами UTF-8 с явным charset:

$utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
Invoke-RestMethod -Uri $TELEGRAM_WEBHOOK -Method Post `
    -Body $utf8Bytes -ContentType "application/json; charset=utf-8"

monitor.ps1 отвечает на вопрос «как ты там вообще?». CPU с загрузкой и числом ядер, RAM, GPU через nvidia-smi (утилизация, температура, VRAM), диски, аптайм. Из отпуска это самая уютная команда: шлёшь в MQTT {"command":"ps1","param1":"D:\\PyCharm\\monitor.ps1"} и через несколько секунд читаешь в Telegram, что 5090 холодная и VRAM свободна.

server_stop.ps1 — авто-выключение, теперь с мордой. Это WinForms-приложение в трее: иконка, по клику показывает, сколько осталось до порога. Раз в 30 секунд смотрит [System.Windows.Forms.SystemInformation]::IdleTime, за 10 минут до часа простоя прилетает balloon-предупреждение, по порогу открывается модальное окно с большой кнопкой CANCEL и таймером на 30 секунд. Не передёрнул мышью, не нажал: Stop-Computer -Force. P/Invoke даже не понадобился, у WinForms есть готовое IdleTime, а скрипт и так живёт в пользовательской сессии.

Telegram через свой n8n, а не напрямую

Все уведомления, «будим ПК», «ПК включён», «команда выполнена», «ESP32 перезапустился, cause=Deep-sleep», уходят не в Bot API, а в личный n8n-webhook, который уже пересылает их в Telegram.

Зачем прослойка. Первая причина прозаична: Для Telegram нужен VPN, а при включении ПК его нет. В схеме есть два участника, которые шлют сообщения без всякого VPN. Первый, ESP32 с его урезанным TLS-стеком. Второй, Windows-агент сразу после загрузки системы, когда VPN ещё не поднялся. Прокси на VPS блокировку снимает: плата и агенты стучатся на свой сервер по HTTPS, а дальше он сам общается с Telegram.

Вторая причина: надёжность на расстоянии. Если в цепочке что-то сломается (webhook, токен, правила фильтрации), чинить из отпуска можно только то, что на VPS, а это как раз n8n. Заменить токен бота, поменять формат пересылки, добавить дублирование в Notion: всё правкой workflow, без перепрошивки платы и без доступа к ПК. Прямая интеграция «прошивка → Bot API» лишила бы меня этой возможности. Бонусом все сообщения системы собираются в одном месте, а у ESP32 есть диагностика по запросу: команда статуса возвращает Wi-Fi/RSSI, температуру чипа (temperatureRead()), доступность ПК и человекочитаемую причину последнего reset (esp_reset_reason(): brownout, watchdog, deep-sleep wake, сразу видно, что происходило с платой).

Безопасность: где жёстко, где честно слабо
  • Внутрь сети не проброшен ни один порт. Весь внешний доступ: HTTPS-панель с авторизацией на VPS.

  • Будильник: файл с коротким словом команды, забираемый по HTTPS. Модель угроз проста: кто имеет доступ к панели, тот управляет ПК. Перехват «на лету» даёт разве что слово start.

  • Токен агента ходит в query-параметре. Не красота, но для LAN-трафика от ESP32 до ПК приемлемо: DNS-логи домашнего роутера не та угроза, ради которой стоит тащить TLS на HttpListener.

  • MQTT-брокер закрыт логином и паролем, топик один. Для параноидального режима есть TLS на брокере.

Экономика и потребление

Компонент

Роль

Стоимость

ESP32 (DevKit)

будильник: опрос команды, WOL

~$5

USB-зарядка

питание платы 24/7

уже было

VPS

панель + MQTT + файл команд

~$5/мес, уже был

PowerShell-агенты

исполнение команд

скрипты

В deep sleep плата потребляет единицы микроампер. Даже с подъёмами по таймеру раз в 5 минут за год набегают копейки. Сравните с «оставить 5090-машину включённой на 3 недели»: по факту машина нужна на час в день, и один этот простой GPU съедает больше электроэнергии, чем вся система управления за год.

Ограничения, о которых стоит знать до отъезда
  1. Wake-on-LAN надо один раз включить в BIOS/UEFI сетевой карты. Единственная настройка «в железе», и о ней легко забыть. Тестируйте до выезда.

  2. Домашний интернет должен жить. Нет роутера, нет будильника. Зато ESP32 честно рапортует о недоступности Wi-Fi в статусе.

  3. Зависшую Windows не управляются. Если система повисла так, что агенты мертвы, командный канал молчит, а WOL бесполезен: машина уже включена. Единственное лекарство от hard-hang: обесточка, то есть умная розетка. Это следующая ступень.

  4. Задержка будильника до 5 минут, период опроса. Когда нужно мгновенно, панель дёргает командный канал и поднимает удалённый доступ.


Исходники прошивки в личном репозитории; если наберётся интерес, выложу отдельным проектом. Железо: любая отладочная плата ESP32 DevKit (я брал классическую WROOM-32).

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Как я уделал Microsoft, но это никому не нужно (кайфую сам)06.8930-09-2026
2Home Assistant Transplant09.9111-07-2026
3Claude Code на Windows: что я прописал агенту, чтобы он перестал спотыкаться16.707-10-2026
4LAN Overview 202609.407-02-2026
5[Перевод] Как я запустил Bad Apple с частотой 20 fps на E‑Ink дисплее за $71011.9227-09-2026
6Консоль починки сети по аудиокабелю: когда SSH уже бесполезен08.2601-10-2026
7Я написал верификатор eBPF для микроконтроллера. А потом ядро Linux объяснило мне, где я неправ09.1823-09-2026
8Мобильник почти из ничего: Как ИИ помог собрать телефон из деталей с маркетплейсов06.3327-09-2026
9Локальные нейросети без Python, CUDA и терминала: делаю программу, в которой всё настраивается само07.0127-09-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 14.53. Источник: habr.com.