Pi как веб-сервер: пульт управления в браузере

Представь: ты открываешь страницу на телефоне, нажимаешь кнопку — и дома загорается свет. Это веб-сервер на твоём Pi.

Веб-интерфейс — лучший пульт для умного дома: он работает на любом телефоне без установки приложений. Pi легко превращается в маленький сайт.

Pi может сам стать веб-сервером — программой, которая отдаёт страницы и принимает команды по сети. На Python для этого есть лёгкий фреймворк Flask. Открыл адрес Pi в браузере — получил страницу управления.

Почему именно веб-страница, а не отдельное приложение? Потому что браузер уже стоит на всём: на твоём телефоне, на ноутбуке родителей, на планшете младшей сестры. Тебе не нужно ничего устанавливать и обновлять — достаточно открыть адрес, и пульт управления домом готов. Это огромное удобство: один и тот же интерфейс работает на любом устройстве, где есть браузер. Представь, что вместо того чтобы носить кучу пультов от телевизора, кондиционера и гирлянды, у тебя одна страница со всеми кнопками, и она всегда под рукой.

Идея клиент-серверного общения проще, чем кажется. Есть две роли: клиент (браузер на телефоне) задаёт вопросы, а сервер (Flask на Pi) на них отвечает. Браузер спрашивает: "дай мне страницу" или "включи свет" — сервер выполняет и отвечает. Они общаются по протоколу HTTP, тому же самому, по которому ты открываешь любой сайт в интернете. Только здесь сайт крошечный и живёт у тебя дома на плате размером с банковскую карту.

Как работает под капотом

Веб-сервер слушает порт (например 5000) и ждёт запросы. Браузер отправляет запрос "дай мне страницу /" — сервер отвечает HTML. Нажатие кнопки шлёт запрос "/light/on" — сервер ловит его и дёргает GPIO.

   Телефон (браузер)            Raspberry Pi (Flask)
   +--------------+   запрос /light/on   +------------------+
   | [ВКЛ] [ВЫКЛ] | -------------------> | маршрут /light/on |
   |              | <------------------- |  -> led.on()      |
   +--------------+    ответ "ок"        +------------------+

Сердце сервера — цикл обработки: программа крутится бесконечно, ждёт, пока придёт запрос, разбирает его адрес (путь), находит подходящий обработчик, выполняет его и отправляет ответ обратно. Затем снова возвращается в ожидание следующего запроса. Этот цикл и делает сервер "всегда готовым": он не выполняется один раз и завершается, а живёт постоянно, как продавец за прилавком, который обслуживает покупателей одного за другим.

   ожидание -> пришёл запрос -> разобрать путь
       ^                              |
       |                       найти обработчик
       |                              |
   отправить ответ <-- выполнить обработчик

Код с реальным GPIO — пример для Pi (Flask плюс gpiozero):

from flask import Flask
from gpiozero import LED

app = Flask(__name__)
led = LED(17)

@app.route("/light/on")
def light_on():
    led.on()
    return "Свет включён"

app.run(host="0.0.0.0", port=5000)

А маршрутизацию запросов — какой адрес какое действие вызывает — отладим на чистом Python. Это сердце любого веб-сервера. Попробуй сам ▶

# Простейший роутер: сопоставляем путь запроса действию
routes = {
    "/light/on":  lambda: "Свет ВКЛючен",
    "/light/off": lambda: "Свет ВЫКЛючен",
    "/status":    lambda: "Система работает",
}

def handle(path):
    handler = routes.get(path)
    if handler is None:
        return "404: маршрут не найден"
    return handler()

# имитируем входящие запросы
for path in ["/light/on", "/status", "/light/off", "/unknown"]:
    print(f"{path:12} -> {handle(path)}")

Обрати внимание: словарь routes — это, по сути, таблица "адрес — действие". Именно так устроена маршрутизация в настоящих фреймворках, только декоратор @app.route прячет эту таблицу от глаз. Когда ты понимаешь, что под капотом просто словарь, фреймворк перестаёт быть магией.

Часто страница должна не только выполнять команду, но и показывать текущее состояние: горит ли свет, какая температура. Смоделируем сервер, который хранит состояние и меняет его по запросам. Попробуй сам ▶

# Сервер с состоянием: помнит, включён ли свет
state = {"light": False}

def handle(path):
    if path == "/light/on":
        state["light"] = True
        return "Свет включён"
    if path == "/light/off":
        state["light"] = False
        return "Свет выключен"
    if path == "/status":
        return "горит" if state["light"] else "выключен"
    return "404: маршрут не найден"

for path in ["/status", "/light/on", "/status", "/light/off", "/status"]:
    print(f"{path:12} -> {handle(path)}")

Частые ошибки

  • Сервер виден только на Pi. Без host="0.0.0.0" Flask слушает только сам Pi, и с телефона не зайти.
  • Забыли про 404. Неизвестный путь должен возвращать понятную ошибку, а не падать.
  • Открыли наружу без защиты. Управление светом из интернета без пароля — плохая идея.
  • Долгое действие внутри обработчика. Если обработчик надолго заснёт, сервер не ответит на другие запросы — тяжёлую работу выноси отдельно.
  • Команда меняет данные через обычную ссылку. Открытие адреса в браузере не должно случайно что-то ломать — для изменений принято использовать отдельные кнопки/формы.

Best practices

  • Запускай с host="0.0.0.0", чтобы сервер был доступен в локальной сети.
  • Всегда обрабатывай неизвестные маршруты (404).
  • Для постоянной работы запускай сервер как сервис (об этом — дальше).
  • Держи обработчики короткими: приняли запрос — быстро ответили.
  • Показывай на странице текущее состояние, а не только кнопки — так пользователь видит, сработала ли команда.

Итоги. Flask превращает Pi в веб-сервер: страница в браузере становится пультом умного дома. В основе — бесконечный цикл обработки и маршрутизация запросов через таблицу "адрес — действие", которую легко отладить на чистом Python, в том числе с хранением состояния. Дальше сделаем так, чтобы программа работала всегда.

Проверьте себя
1. Что делает веб-сервер на Raspberry Pi?
AРаздаёт Wi-Fi
BСлушает порт, принимает запросы из браузера и отвечает страницами или выполняет команды
CЗаряжает Pi
DХранит файлы на microSD
2. Зачем при запуске Flask на Pi указывать host="0.0.0.0"?
AДля скорости
BЧтобы сервер был доступен другим устройствам в сети, а не только самому Pi
CЭто адрес роутера
DЧтобы включить шифрование