Compare commits
2
Commits
938700cf86
...
28ca67f42c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28ca67f42c | ||
|
|
8c5ebe2967 |
@@ -1,31 +1,32 @@
|
|||||||
# Home Automation Platform
|
# Платформа домашней автоматизации
|
||||||
|
|
||||||
A microservice-based home automation platform, built as a portfolio project
|
Pet-проект на микросервисной архитектуре для портфолио, демонстрирующий Go
|
||||||
demonstrating Go (concurrency, message queues, MQTT), PHP/Laravel (API,
|
(конкурентность, очереди сообщений, MQTT), PHP/Laravel (API, авторизация)
|
||||||
authorization), and polyglot persistence (PostgreSQL, ClickHouse, Redis).
|
и работу с разными БД по назначению (PostgreSQL, ClickHouse, Redis).
|
||||||
|
|
||||||
**This is not a "growbox project."** A growbox is the first implemented
|
**Это не «проект гроубокса».** Гроубокс — первая реализованная **зона**
|
||||||
**zone** with a first set of **device types** (light, pump, fan, temperature
|
с первым набором **типов устройств** (свет, полив, вентиляция, датчик
|
||||||
sensor). The core system — device management, telemetry, automation rules,
|
температуры). Ядро системы — управление устройствами, телеметрия, правила
|
||||||
users, notifications — operates purely on the abstractions *zone*, *device*,
|
автоматизации, пользователи, уведомления — оперирует исключительно
|
||||||
*device type*, *sensor reading*, *command*, *rule*. Nothing growbox-specific
|
абстракциями *зона*, *устройство*, *тип устройства*, *показание датчика*,
|
||||||
is hardcoded in the core: adding a "living room" zone with a "smart plug"
|
*команда*, *правило*. Ничего специфичного для гроубокса не зашито в ядре:
|
||||||
device type should require zero changes to service code, only new rows in
|
добавление зоны «гостиная» с типом устройства «умная розетка» не должно
|
||||||
|
требовать изменений в коде сервисов — только новые строки в
|
||||||
`device_types` / `zones` / `devices`.
|
`device_types` / `zones` / `devices`.
|
||||||
|
|
||||||
## Architecture
|
## Архитектура
|
||||||
|
|
||||||
```
|
```
|
||||||
ESP32 (or emulator)
|
ESP32 (или эмулятор)
|
||||||
│ MQTT
|
│ MQTT
|
||||||
▼
|
▼
|
||||||
ingest-service (Go) ──► ClickHouse (raw telemetry)
|
ingest-service (Go) ──► ClickHouse (сырая телеметрия)
|
||||||
│
|
│
|
||||||
▼ RabbitMQ ("new reading" event)
|
▼ RabbitMQ (событие "новое показание")
|
||||||
rule-engine-service (Go) ──► PostgreSQL (rules, cached)
|
rule-engine-service (Go) ──► PostgreSQL (правила, кэшируются)
|
||||||
│ gRPC (on rule match)
|
│ gRPC (при срабатывании правила)
|
||||||
▼
|
▼
|
||||||
device-control-service (Go) ──► MQTT (command) ──► device
|
device-control-service (Go) ──► MQTT (команда) ──► устройство
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
Redis (device shadow: desired/reported state, status, last_seen)
|
Redis (device shadow: desired/reported state, status, last_seen)
|
||||||
@@ -33,68 +34,69 @@ Redis (device shadow: desired/reported state, status, last_seen)
|
|||||||
Laravel API (PHP) ──► PostgreSQL / ClickHouse / Redis
|
Laravel API (PHP) ──► PostgreSQL / ClickHouse / Redis
|
||||||
│ gRPC / HTTP
|
│ gRPC / HTTP
|
||||||
▼
|
▼
|
||||||
device-control-service (manual commands from the UI)
|
device-control-service (ручные команды из UI)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Stack
|
## Стек
|
||||||
|
|
||||||
| Concern | Technology |
|
| Назначение | Технология |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Device firmware / emulator | Go (`services/esp32-emulator`) |
|
| Прошивка устройства / эмулятор | Go (`services/esp32-emulator`) |
|
||||||
| Device ↔ server protocol | MQTT (Mosquitto) |
|
| Протокол устройство ↔ сервер | MQTT (Mosquitto) |
|
||||||
| Telemetry / commands / rules backend | Go |
|
| Backend телеметрии / команд / правил | Go |
|
||||||
| Business logic / API / dashboard | PHP (Laravel) |
|
| Бизнес-логика / API / дашборд | PHP (Laravel) |
|
||||||
| Users, zones, devices, rules | PostgreSQL |
|
| Пользователи, зоны, устройства, правила | PostgreSQL |
|
||||||
| Telemetry (time-series) | ClickHouse |
|
| Телеметрия (time-series) | ClickHouse |
|
||||||
| Real-time device state | Redis (Device Shadow pattern) |
|
| Состояние устройств в реальном времени | Redis (паттерн Device Shadow) |
|
||||||
| Inter-service async events | RabbitMQ |
|
| Асинхронные события между сервисами | RabbitMQ |
|
||||||
| Inter-service sync calls | gRPC / Protobuf |
|
| Синхронные вызовы между сервисами | gRPC / Protobuf |
|
||||||
| Metrics (stage 2.5) | Prometheus + Grafana |
|
| Метрики (этап 2.5) | Prometheus + Grafana |
|
||||||
| Containerization | Docker Compose |
|
| Контейнеризация | Docker Compose |
|
||||||
| Web frontend (stage 3) | Vue.js + Inertia.js |
|
| Веб-фронтенд (этап 3) | Vue.js + Inertia.js |
|
||||||
| Mobile app (stage 3) | Flutter |
|
| Мобильное приложение (этап 3) | Flutter |
|
||||||
| Notifications | Telegram Bot (MVP) / Firebase Cloud Messaging (later) |
|
| Уведомления | Telegram-бот (MVP) / Firebase Cloud Messaging (позже) |
|
||||||
|
|
||||||
## Layout
|
## Структура репозитория
|
||||||
|
|
||||||
```
|
```
|
||||||
services/
|
services/
|
||||||
ingest-service/ Go — MQTT → ClickHouse + RabbitMQ
|
ingest-service/ Go — MQTT → ClickHouse + RabbitMQ
|
||||||
device-control-service/ Go — gRPC + MQTT + Redis device shadow
|
device-control-service/ Go — gRPC + MQTT + Redis device shadow
|
||||||
rule-engine-service/ Go — RabbitMQ consumer + rule evaluation
|
rule-engine-service/ Go — консьюмер RabbitMQ + оценка правил
|
||||||
health-check-service/ Go — offline detection ticker
|
health-check-service/ Go — тикер обнаружения офлайн-устройств
|
||||||
esp32-emulator/ Go — fake device for local dev
|
esp32-emulator/ Go — фейковое устройство для локальной разработки
|
||||||
laravel-app/ PHP/Laravel — API, auth, dashboard (stage 2)
|
laravel-app/ PHP/Laravel — API, авторизация, дашборд (этап 2)
|
||||||
proto/ Shared gRPC contracts (device_control.proto)
|
proto/ Общие gRPC-контракты (device_control.proto)
|
||||||
migrations/
|
migrations/
|
||||||
postgres/ users, zones, devices, device_types, automation_rules
|
postgres/ users, zones, devices, device_types, automation_rules
|
||||||
clickhouse/ telemetry table
|
clickhouse/ таблица telemetry
|
||||||
configs/mosquitto/ MQTT broker config
|
configs/mosquitto/ Конфиг MQTT-брокера
|
||||||
monitoring/ Prometheus + Grafana config (stage 2.5)
|
monitoring/ Конфиги Prometheus + Grafana (этап 2.5)
|
||||||
```
|
```
|
||||||
|
|
||||||
Each `services/*/README.md` describes that service's responsibility and
|
Каждый `services/*/README.md` описывает зону ответственности сервиса и явно
|
||||||
explicit non-responsibilities before any code exists there.
|
то, что в неё не входит, — ещё до того, как там появится код.
|
||||||
|
|
||||||
## Build stages
|
## Этапы разработки
|
||||||
|
|
||||||
1. **Go services** — ingest, rule-engine, device-control, ESP32 emulator.
|
1. **Go-сервисы** — ingest, rule-engine, device-control, эмулятор ESP32.
|
||||||
2. **Laravel** — auth (Sanctum, owner/viewer), CRUD for zones/devices/rules,
|
2. **Laravel** — авторизация (Sanctum, owner/viewer), CRUD для зон/устройств/
|
||||||
Blade dashboard, manual device control. MVP is considered done here.
|
правил, Blade-дашборд, ручное управление устройствами. MVP считается
|
||||||
3. *(optional, high value)* **Observability** — Prometheus metrics from the
|
готовым на этом этапе.
|
||||||
Go services, a Grafana dashboard or two.
|
3. *(опционально, но сильно повышает ценность)* **Observability** — метрики
|
||||||
4. **Extensions** (no rush) — Telegram notifications, Vue.js/Inertia
|
Prometheus с Go-сервисов, один-два дашборда в Grafana.
|
||||||
frontend, Flutter app, real ESP32 hardware.
|
4. **Расширения** (не спеша) — Telegram-уведомления, фронтенд на
|
||||||
|
Vue.js/Inertia, приложение на Flutter, подключение реального ESP32.
|
||||||
|
|
||||||
## Running locally
|
## Запуск локально
|
||||||
|
|
||||||
Infrastructure only for now (app services are added to `docker-compose.yml`
|
Пока только инфраструктура (сервисы добавляются в `docker-compose.yml` по
|
||||||
as they're implemented):
|
мере реализации):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
This brings up Mosquitto (`1883`), PostgreSQL (`5432`), ClickHouse (`8123`/`9000`),
|
Поднимутся Mosquitto (`1883`), PostgreSQL (`5432`), ClickHouse (`8123`/`9000`),
|
||||||
Redis (`6379`), and RabbitMQ (`5672`, management UI on `15672`).
|
Redis (`6379`) и RabbitMQ (`5672`, веб-интерфейс управления на `15672`).
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
# device-control-service (Go)
|
# device-control-service (Go)
|
||||||
|
|
||||||
Status: not implemented yet.
|
Статус: пока не реализован.
|
||||||
|
|
||||||
Responsibility:
|
Зона ответственности:
|
||||||
- Expose a gRPC API (see `proto/device_control.proto`) for issuing commands
|
- Предоставляет gRPC API (см. `proto/device_control.proto`) для отправки
|
||||||
to devices — called by Laravel (manual control) and rule-engine-service
|
команд устройствам — вызывается из Laravel (ручное управление) и из
|
||||||
(rule-triggered actions).
|
rule-engine-service (действия по срабатыванию правил).
|
||||||
- Publish the command to the device's MQTT topic (`devices/{device_id}/commands`)
|
- Публикует команду в MQTT-топик устройства (`devices/{device_id}/commands`)
|
||||||
and listen for acknowledgements (`devices/{device_id}/ack`).
|
и слушает подтверждения (`devices/{device_id}/ack`).
|
||||||
- Maintain the Device Shadow pattern in Redis: `desired_state` (what the user
|
- Поддерживает паттерн Device Shadow в Redis: `desired_state` (чего хочет
|
||||||
wants) vs `reported_state` (what the device confirmed), plus `status` and
|
пользователь) против `reported_state` (что подтвердило устройство), плюс
|
||||||
`last_seen`.
|
`status` и `last_seen`.
|
||||||
- Owns the health-check loop (or delegates to health-check-service): a ticker
|
- Владеет циклом health-check (либо делегирует его health-check-service):
|
||||||
goroutine that flips a device to `offline` when `last_seen` exceeds a
|
горутина-тикер переводит устройство в `offline`, когда `last_seen`
|
||||||
timeout, and emits an event for notification-service.
|
превышает таймаут, и генерирует событие для notification-service.
|
||||||
|
|
||||||
Not this service's job: deciding *when* to send a command based on sensor
|
Не входит в зону ответственности: решение о том, *когда* отправлять команду
|
||||||
thresholds — that's rule-engine-service's logic. This service only executes
|
на основе показаний датчиков — это логика rule-engine-service. Этот сервис
|
||||||
and tracks state for whatever command it's given.
|
только исполняет команды и отслеживает состояние для той команды, что ему дали.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# esp32-emulator (Go)
|
# esp32-emulator (Go)
|
||||||
|
|
||||||
Status: not implemented yet.
|
Статус: пока не реализован.
|
||||||
|
|
||||||
Responsibility:
|
Зона ответственности:
|
||||||
- Stand in for real ESP32 hardware during MVP development.
|
- Заменяет реальное железо ESP32 на время разработки MVP.
|
||||||
- Publish plausible telemetry (temperature/humidity/etc.) to
|
- Публикует правдоподобную телеметрию (температура/влажность/и т.д.) в
|
||||||
`devices/{device_id}/telemetry` over MQTT on an interval.
|
`devices/{device_id}/telemetry` по MQTT с заданным интервалом.
|
||||||
- Subscribe to `devices/{device_id}/commands` and reply on
|
- Подписывается на `devices/{device_id}/commands` и отвечает в
|
||||||
`devices/{device_id}/ack`, so device-control-service has something real
|
`devices/{device_id}/ack`, чтобы device-control-service было с кем
|
||||||
to talk to.
|
по-настоящему взаимодействовать.
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# health-check-service (Go)
|
# health-check-service (Go)
|
||||||
|
|
||||||
Status: not implemented yet. May start as a goroutine inside
|
Статус: пока не реализован. Может начаться как горутина внутри
|
||||||
device-control-service and get split out into its own container later —
|
device-control-service и позже выделиться в отдельный контейнер — поэтому
|
||||||
kept as a separate directory from day one so the split is a non-event.
|
с первого дня держим отдельную директорию, чтобы это разделение прошло
|
||||||
|
безболезненно.
|
||||||
|
|
||||||
Responsibility:
|
Зона ответственности:
|
||||||
- Periodically (ticker) check `last_seen` for every device in Redis.
|
- Периодически (по тикеру) проверяет `last_seen` каждого устройства в Redis.
|
||||||
- When a device exceeds the offline timeout, flip its `status` to `offline`
|
- Когда устройство превышает таймаут офлайна, переводит его `status` в
|
||||||
and publish an event for notification-service.
|
`offline` и публикует событие для notification-service.
|
||||||
- Good place to demonstrate goroutines + graceful shutdown in Go.
|
- Хороший повод продемонстрировать горутины + graceful shutdown в Go.
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
# ingest-service (Go)
|
# ingest-service (Go)
|
||||||
|
|
||||||
Status: implemented (MVP).
|
Статус: реализован (MVP).
|
||||||
|
|
||||||
Responsibility:
|
Зона ответственности:
|
||||||
- Subscribe to MQTT topics `devices/{device_id}/telemetry`.
|
- Подписка на MQTT-топики `devices/{device_id}/telemetry`.
|
||||||
- Validate/parse payload (device_id, sensor_type, value, timestamp).
|
- Валидация/парсинг payload (device_id, sensor_type, value, timestamp).
|
||||||
- Batch-write raw readings into ClickHouse `telemetry` table.
|
- Батч-запись сырых показаний в таблицу ClickHouse `telemetry`.
|
||||||
- Publish a "new reading" event to RabbitMQ for rule-engine-service.
|
- Публикация события «новое показание» в RabbitMQ для rule-engine-service.
|
||||||
|
|
||||||
Not this service's job: interpreting what a sensor reading *means* (thresholds,
|
Не входит в зону ответственности: интерпретация того, что *означает*
|
||||||
actions) — that belongs to rule-engine-service. This service only ingests and
|
показание датчика (пороги, действия) — это задача rule-engine-service. Этот
|
||||||
stores.
|
сервис только принимает и сохраняет данные.
|
||||||
|
|
||||||
Note on `zone_id`: the telemetry payload carries `zone_id` directly (the
|
Про `zone_id`: payload телеметрии несёт `zone_id` напрямую (публикующее
|
||||||
publishing device/emulator is configured with its own zone), rather than
|
устройство/эмулятор само знает свою зону), а не ingest-service ищет его в
|
||||||
ingest-service looking it up from PostgreSQL. This keeps ingest-service's
|
PostgreSQL. Это удерживает зависимости ingest-service в рамках
|
||||||
dependencies limited to MQTT/ClickHouse/RabbitMQ, matching the architecture
|
MQTT/ClickHouse/RabbitMQ, как и на диаграмме архитектуры в корневом README.
|
||||||
diagram in the root README. A real fleet where devices don't know their own
|
Для реального парка устройств, которые не будут знать свою зону,
|
||||||
zone would need a device-registry lookup (cached, like rule-engine caches
|
понадобится lookup через реестр устройств (с кэшированием, как rule-engine
|
||||||
rules) — that's a natural follow-up, not implemented yet.
|
кэширует правила) — это естественное развитие, пока не реализовано.
|
||||||
|
|
||||||
## Running
|
## Запуск
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd services/ingest-service
|
cd services/ingest-service
|
||||||
@@ -28,7 +28,8 @@ go test ./...
|
|||||||
go build ./cmd/ingest-service
|
go build ./cmd/ingest-service
|
||||||
```
|
```
|
||||||
|
|
||||||
Configuration is env-var driven (see the ingest-service section of the
|
Конфигурация читается из переменных окружения (см. секцию ingest-service в
|
||||||
repo-root `.env.example`). Via `docker compose up ingest-service` it talks to
|
корневом `.env.example`). Через `docker compose up ingest-service` сервис
|
||||||
the `mosquitto`/`clickhouse`/`rabbitmq` containers directly; connect from a
|
обращается напрямую к контейнерам `mosquitto`/`clickhouse`/`rabbitmq`; для
|
||||||
local `go run` by exporting `MQTT_HOST=localhost`, `CLICKHOUSE_HOST=localhost`, etc.
|
локального запуска через `go run` выставь `MQTT_HOST=localhost`,
|
||||||
|
`CLICKHOUSE_HOST=localhost` и т.д.
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# rule-engine-service (Go)
|
# rule-engine-service (Go)
|
||||||
|
|
||||||
Status: not implemented yet.
|
Статус: пока не реализован.
|
||||||
|
|
||||||
Responsibility:
|
Зона ответственности:
|
||||||
- Consume "new reading" events from RabbitMQ (async — delivery reliability
|
- Потребляет события «новое показание» из RabbitMQ (асинхронно — здесь
|
||||||
matters more than latency here).
|
надёжность доставки важнее задержки).
|
||||||
- Load active `automation_rules` for the relevant zone/device from PostgreSQL,
|
- Загружает активные `automation_rules` для нужной зоны/устройства из
|
||||||
caching in memory/Redis to avoid a DB round-trip per event.
|
PostgreSQL, кэшируя в памяти/Redis, чтобы не ходить в БД на каждое событие.
|
||||||
- Evaluate rule conditions generically: `sensor_type X operator value` against
|
- Оценивает условия правил обобщённо: `sensor_type X operator value` против
|
||||||
the reading — no hardcoded sensor/device names, everything comes from the
|
показания — никаких зашитых названий датчиков/устройств, всё берётся из
|
||||||
`automation_rules` row.
|
строки `automation_rules`.
|
||||||
- On match: call device-control-service over gRPC (needs a fast pass/fail
|
- При срабатывании: вызывает device-control-service по gRPC (нужен быстрый
|
||||||
result) and publish an event to notification-service over RabbitMQ (not
|
результат успех/неудача) и публикует событие в notification-service через
|
||||||
latency-critical).
|
RabbitMQ (не критично к задержке).
|
||||||
|
|
||||||
Not this service's job: talking to MQTT or Redis directly — device state
|
Не входит в зону ответственности: прямое взаимодействие с MQTT или Redis —
|
||||||
changes always go through device-control-service's gRPC API.
|
изменения состояния устройства всегда идут через gRPC API device-control-service.
|
||||||
|
|||||||
Reference in New Issue
Block a user