Введение
В заключительной статье цикла собираем итоговый проект — kiosk-панель умного дома на Raspberry Pi, объединяющая темы всего цикла: GPIO для локальных датчиков и реле, аппаратно ускоренный QML-интерфейс через EGLFS, автозапуск через systemd, и связь с MQTT-инфраструктурой из статьи 320 для управления удалёнными ESP32-узлами, логически замыкая Raspberry Pi-цикл на уже написанную IoT-тему.
Концепция
Архитектура проекта: панель на Raspberry Pi подписывается на MQTT-топики ESP32-узлов (статья 315) для отображения показаний датчиков по всему дому, отображает локальное реле/освещение через прямой GPIO-доступ (статья 343), запускается в kiosk-режиме (статья 348) с полноэкранным QML-интерфейсом, и поддерживает удалённое обновление по сети (статья 349) для развёртывания новых версий интерфейса без необходимости физического доступа к устройству.
Пример кода: Итоговый мини-проект
// main.qml — основной интерфейс панели, объединяющий MQTT-данные и локальное GPIO-управление
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
width: 800
height: 480 // типичное разрешение небольшого сенсорного дисплея для панели умного дома
GridView {
anchors.fill: parent
cellWidth: 200
cellHeight: 150
model: roomTemperatureModel // заполняется из C++ через MQTT-подписку
delegate: Rectangle {
width: 190; height: 140
color: "#2c3e50"
radius: 8
Column {
anchors.centerIn: parent
Text { text: model.roomName; color: "white"; font.pixelSize: 18 }
Text { text: model.temperature + "°C"; color: "#3498db"; font.pixelSize: 32 }
}
}
}
Switch {
anchors.bottom: parent.bottom
anchors.right: parent.right
text: "Свет в гостиной"
onToggled: gpioController.setLivingRoomLight(checked) // прямой вызов C++ через контекст
}
}
// homepanel.h — связующий C++ слой между MQTT, GPIO и QML-интерфейсом
class HomePanel : public QObject
{
Q_OBJECT
public:
HomePanel(QObject *parent = nullptr) : QObject(parent)
{
// MQTT-клиент, аналогичный MqttSensorClient из статьи 315 —
// подписка на топики всех ESP32-узлов дома
connect(&m_mqttClient, &MqttSensorClient::readingReceived,
this, &HomePanel::onReadingReceived);
m_mqttClient.connectToHost("mqtt.home.local");
// Прямой GPIO-доступ для локального реле освещения, аналогично статье 343
m_lightRelay = std::make_unique<GpioController>("gpiochip0", 27);
m_lightRelay->setupAsOutput();
}
Q_INVOKABLE void setLivingRoomLight(bool on)
{
m_lightRelay->setValue(on);
}
private slots:
void onReadingReceived(const QString &roomName, double temperature)
{
m_roomTemperatureModel->updateRoom(roomName, temperature);
}
private:
MqttSensorClient m_mqttClient;
std::unique_ptr<GpioController> m_lightRelay;
RoomTemperatureModel *m_roomTemperatureModel;
};
# kiosk-app.service — итоговая конфигурация автозапуска, объединяющая все настройки цикла
[Unit]
Description=Smart Home Panel
After=network-online.target # важно дождаться сети ПЕРЕД стартом — приложению нужен MQTT-брокер
Wants=network-online.target
[Service]
Type=simple
User=pi
Environment=QT_QPA_PLATFORM=eglfs
ExecStart=/home/pi/SmartHomePanel/SmartHomePanel
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Пояснения к коду
HomePanel объединяет два независимых источника данных в одном связующем C++ классе — MqttSensorClient (статья 315) для удалённых показаний от ESP32-узлов по всему дому, и прямой GpioController (статья 343) для локального реле, физически подключённого непосредственно к самой панели — оба пути выставлены в QML через Q_INVOKABLE методы и модели данных, давая интерфейсу единообразный доступ к разнородным по природе источникам данных и управления. Конфигурация systemd-сервиса явно зависит от network-online.target (а не просто multi-user.target, как в более простом примере статьи 348) — это критично важная деталь именно для этого проекта: приложению нужно установленное сетевое соединение для подключения к MQTT-брокеру при самом первом запуске, и без этой зависимости сервис мог бы попытаться запуститься до того, как сеть будет полностью готова, особенно на медленно загружающихся сетевых интерфейсах (Wi-Fi, требующий времени на ассоциацию с точкой доступа).
Подводные камни
- Отсутствие явной обработки временной потери сетевого соединения уже во время работы приложения (а не только при самом старте, для чего предусмотрена зависимость
network-online.target) — если Wi-Fi-соединение временно прерывается во время работы панели (что для домашней сети случается регулярно), MQTT-клиент должен корректно обнаружить разрыв и автоматически переподключиться при восстановлении сети, аналогично подводным камням из статьи 314 основного MQTT-цикла, и интерфейс должен явно показывать пользователю состояние «данные могут быть устаревшими» при потере соединения, а не молча отображать последние, потенциально устаревшие значения как актуальные. - Смешивание прямого, синхронного доступа к GPIO из слота, вызываемого из QML (
setLivingRoomLight), без вынесения в отдельный поток — хотя одна операцияgpiod_line_set_value()сама по себе очень быстрая и не блокирует интерфейс заметно, общий принцип, разбиравшийся для gRPC (статья 327) и сетевых вызовов, остаётся в силе: любое потенциально медленное взаимодействие с аппаратным обеспечением, вызываемое непосредственно из UI-потока, стоит сначала явно проверить на реальную задержку, прежде чем считать его безопасным для вызова напрямую из обработчика событий интерфейса. - Хранение пароля/учётных данных MQTT-брокера непосредственно в исходном коде приложения (упрощённо показанное в примере как просто
connectToHost()без аутентификации) — для реального домашнего проекта, особенно если панель должна поддерживать TLS и аутентификацию для MQTT-соединения (статья 318), учётные данные должны браться из защищённого конфигурационного файла или хранилища, а не быть зашиты непосредственно в исходный код, который может быть случайно опубликован или передан третьим лицам вместе с проектом. - Развёртывание единственной, не резервируемой панели управления домом как единственной точки управления всеми устройствами, без какого-либо резервного способа управления тем же освещением/устройствами при выходе из строя самой панели Raspberry Pi (физическая поломка SD-карты, зависание системы) — для критичных функций (например, освещение, а не просто отображение показаний температуры) стоит сохранить независимый от панели способ управления (обычный физический выключатель, параллельный кнопке на самой панели), чтобы единая точка отказа панели не оставляла пользователя без какого-либо способа управления базовыми функциями дома.
Цикл «Raspberry Pi + Qt» завершён — план дополнен до 350 статей; далее последуют циклы STM32/bare-metal (351-360) и CAN-шина/автомобильная диагностика (361-370).