Daily MaverickEx-Google DeepMind researcher adds to warnings that AI could ‘kill all humans’וואלהצה"ל חיסל את מפקד הפלוגה במטה המבצעים בחמאס, יחד מחבל נוסףESPNCeltics offseason recap and early-season preview: Tatum returns as the catalystRTP Desporto12h30 Benfica a 100% para Amorim, Ramos e Diego MoreiraPunchKenya to host 2029 World Athletics Championships in African firstThe Jerusalem PostPolice investigating threats against prominent Munich Holocaust survivor and AfD opponentInquirerWATCH: Ombudsman lawyer takes the witness standCollider'Super Troopers 3' Officially Rides Onto Digital With New Blooper Reel [Exclusive]SCMP ChinaChina mulls building nuclear-powered tank with 450km-range railgun in 2 decadesSouth China Morning PostChina bets on chips and AI in new 5-year road map to challenge US tech dominanceBusiness AMNieuwe nucleaire raket Sentinel bereikt belangrijke mijlpaal en is klaar voor testvlucht in 2027VarietySamsung’s Galaxy Watch Ultra2 Is Up to $250 Off With This Special Trade-In Deal
The Daily Newsstand · Free, Always
Tuesday, September 15, 2026

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

Translate

Выключил ПК перед отпуском и управляю им с телефона: 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).

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.