plc-cloud - облачный сервер для PLC/ESP32-контроллеров с веб-интерфейсом, браузерным WebSocket API и Telegram-ботом.
🌐 Один сервис для веба, устройств и Telegram-управления с единым runtime state.
Проект решает несколько задач:
- аутентификация пользователей веб-интерфейса;
- хранение объектов, устройств, пользователей и Telegram-настроек в SQLite;
- поддержание онлайн-сессий устройств;
- проксирование протокольных
get/cmdкоманд между браузером, Telegram и PLC; - фильтрация объектов, устройств, контроллеров и команд через ACL, приходящий от PLC;
- отправка Telegram-уведомлений об онлайне, офлайне и событиях устройств.
| Контур | Адрес | Назначение |
|---|---|---|
| Web UI / REST | http://HOST:WEB_PORT |
фронтенд, REST API |
| Browser WS | ws://HOST:WEB_PORT/ws/web |
браузерные команды и подписки |
| Device WS | ws://HOST:PORT/ws/device |
подключение PLC/ESP32 |
- Web UI и REST API:
http://HOST:WEB_PORT - Browser WS:
ws://HOST:WEB_PORT/ws/web - Device WS:
ws://HOST:PORT/ws/device
Порты по умолчанию:
WEB_PORT=80PORT=3001
Приложение поднимает два отдельных listener:
- web listener для статики, REST и
/ws/web - device listener для
/ws/device
⚙️ Express ⚡ ws 🧩 awilix 🤖 grammY 💾 SQLite
ExpresswsawilixgrammYsequelize+sqlite3- in-memory runtime state в
DeviceRegistry
flowchart LR
subgraph Browser["Браузер"]
UI["SPA интерфейс"]
BWS["WS /ws/web"]
end
subgraph Telegram["Telegram"]
TG["Бот grammY<br/>long polling"]
end
subgraph Cloud["plc-cloud"]
HTTP["Express + REST + static"]
WSS["WebWsServer"]
DWS["DeviceWsServer"]
ACL["AccessControl"]
REG["DeviceRegistry"]
SES["SessionStore"]
UDB["UsersDb"]
DDB["DevicesDb"]
TDB["TelegramConfigDb"]
BOT["TelegramBotService"]
end
subgraph PLC["PLC / ESP32"]
DEV["Контроллер"]
end
UI --> HTTP
UI --> BWS
BWS --> WSS
TG --> BOT
BOT --> DWS
HTTP --> ACL
WSS --> ACL
ACL --> REG
HTTP --> UDB
HTTP --> DDB
HTTP --> TDB
DEV --> DWS
DWS --> REG
DWS --> DDB
BOT --> REG
sequenceDiagram
participant PLC as PLC/ESP32
participant WS as DeviceWsServer
participant DB as DevicesDb
participant REG as DeviceRegistry
participant WEB as WebWsServer
participant TG as TelegramBotService
PLC->>WS: hello + auth.api_key
WS->>DB: getByApiKey(api_key)
DB-->>WS: устройство найдено
WS->>REG: attach(session)
WS-->>PLC: hello_ack(session_id)
WS-->>WEB: device_online
WS-->>TG: notifyDeviceOnline()
PLC->>WS: result / ack / event
WS->>REG: merge runtime state
WS->>DB: updateLastSeen()
WS-->>WEB: device_update
WS-->>TG: notifyDeviceUpdate()
sequenceDiagram
participant UI as Браузер
participant WEB as WebWsServer
participant ACL as AccessControl
participant DWS as DeviceWsServer
participant PLC as PLC/ESP32
UI->>WEB: send_get / send_cmd
WEB->>ACL: проверка доступа
ACL-->>WEB: ok / deny
WEB->>DWS: sendGet() / sendCmd()
DWS->>PLC: get / cmd
PLC-->>DWS: result / ack
DWS-->>WEB: device_update
sequenceDiagram
participant User as Пользователь
participant TG as TelegramBotService
participant UDB as UsersDb
participant ACL as AccessControl
participant DWS as DeviceWsServer
participant PLC as PLC/ESP32
User->>TG: нажатие inline-кнопки
TG->>UDB: findByTelegramIdentity()
UDB-->>TG: user + plc_username
TG->>ACL: sanitizeSummary / canSendControllerCommand
ACL-->>TG: ok / deny
TG->>DWS: sendCmd() / sendGet()
DWS->>PLC: cmd / get
PLC-->>DWS: ack / result
TG-->>User: новое сообщение с меню
Сборка приложения построена через awilix.
Точка входа:
Composition root:
Основные сервисы контейнера:
SessionStoreDeviceRegistryUsersDbDevicesDbTelegramConfigDbTelegramBotServiceDatastoreFactoryHttpFactoryWsFactoryAppServer
src/
app/
AppContainer.js
AppServer.js
factories/
auth/
AccessControl.js
bot/
TelegramBotService.js
menu/
db/
http/
state/
utils/
ws/
public/
index.html
styles.css
js/
data/
proto.json
Основная БД проекта:
data/plc-cloud.sqlite
В ней лежат:
- объекты и устройства
- пользователи
- Telegram-конфиг
При первом запуске код умеет импортировать старые JSON-файлы:
data/users.jsondata/devices.jsondata/telegram.json
После импорта основным источником истины становится SQLite.
Пользователь хранит не только логин и пароль, но и Telegram/ACL-поля:
usernamepassword_hashplc_usernametelegram_usernamechat_idtelegram_notify_onlinetelegram_notify_offlinetelegram_notify_eventsnotification_prefs_jsonallowed_objects_json
Основные:
HOSTWEB_PORTPORTPLC_CLOUD_SEED_SAMPLE=1TELEGRAM_BOT_TOKEN
Legacy Telegram env-переменные, которые ещё поддерживаются как defaults для storage:
TELEGRAM_BOT_PUBLIC_BASE_URLTELEGRAM_BOT_WEBHOOK_PATHTELEGRAM_BOT_SECRET_TOKEN
npm installnpm startРежим разработки:
npm run devСборка образа:
docker build -t plc-cloud .Запуск контейнера:
docker run -d \
--name plc-cloud \
-p 80:80 \
-p 3001:3001 \
-v "$(pwd)/data:/app/data" \
-e HOST=0.0.0.0 \
-e WEB_PORT=80 \
-e PORT=3001 \
plc-cloudЗапуск через compose:
docker compose up -d --buildТекущий маппинг compose:
${WEB_HOST_PORT:-80}:80${DEVICE_HOST_PORT:-3001}:3001
Основные маршруты:
POST /api/loginPOST /api/logoutGET /api/objectsGET /api/devices?object=<name>GET /api/device/:idGET /api/admin/devicesGET /api/admin/usersPOST /api/admin/usersPUT /api/admin/users/:usernameDELETE /api/admin/users/:usernameGET /api/admin/telegram/settingsPUT /api/admin/telegram/settings- CRUD объектов
- CRUD устройств
Endpoint:
/ws/web
Сообщения:
list_devicessubscribe_deviceunsubscribe_devicesend_getsend_cmd
Broadcasts:
device_onlinedevice_offlinedevice_updatecommand_sentcommand_error
Все snapshot'ы, которые уходят в браузер, проходят ACL-санацию.
Endpoint:
/ws/device
Поддерживаемый цикл:
hellohello_ackresultackeventping/pong
После hello все сообщения должны нести валидный session_id.
🤖 long polling 🧭 inline-меню 🔐 ACL-фильтрация 🔔 уведомления
Текущий режим:
long polling- webhook не нужен
Текущая интерактивная структура:
- объекты
- устройства и stack slave-узлы
- контроллеры
- розетки
- свет
- метео
- термостаты
- баки
- септик
- полив
- быстрые действия
Поведение меню:
- long polling
- inline-кнопки
- бот отправляет новый экран сообщением, а не редактирует текущее сообщение
- в настройках облака показываются
Last Chat ID, последний username и время последней активности
Уведомления уходят только тем пользователям, у которых:
- заполнен
chat_id - заполнен
plc_username - ACL разрешает доступ к устройству
SPA включает:
- выбор объектов
- список устройств
- статус устройства
- обзор контроллеров
- розетки
- свет
- метео
- термостаты
- баки
- септик
- полив
- сеть
- настройки с плитками:
- объекты
- устройства
- пользователи
- телеграм
ACL приходит от PLC и интерпретируется в:
Используется в:
- REST API
- browser websocket
- Telegram bot
Если ACL отсутствует, код работает в legacy permissive-режиме.
ℹ️ Проект использует SQLite для persistent-данных и живой runtime state в памяти.
- браузерные сессии хранятся только в памяти
- online runtime state хранятся только в памяти
- long polling предполагает один активный экземпляр бота на токен
- Telegram storage всё ещё содержит legacy webhook-поля
- локальный
nodeна этой машине может падать из-за отсутствующегоicu4c; для проверок безопаснее использовать Dockernode:20-alpine
Чтобы добавить новый контроллер:
- расширить proto.json
- при необходимости согласовать контракт с прошивкой PLC
- обновить src/auth/AccessControl.js
- добавить рендеринг во public/js/ui.js
- добавить действия во public/js/main.js
- при необходимости добавить Telegram-меню в src/bot/menu









