- PVSM.RU - https://www.pvsm.ru -
Примечание: IP-адреса, device ID, product ID, MQTT-логины, ключи и имена хостов в статье заменены на условные. Методика сохранена, но при повторении нужно подставлять свои значения.
У меня есть настенный кондиционер Royal Clima Pandora RC-PDC22HN. В родном приложении SmartLife у него доступно довольно много функций, но в Home Assistant через стандартную Tuya-интеграцию виден только базовый набор: включить, выключить, изменить температуру и режим. Для обычного сценария этого достаточно, но в реальной автоматизации быстро захотелось большего: управлять дисплеем на внутреннем блоке, отключать звуковое подтверждение, видеть сырые параметры устройства и не зависеть от облачного профиля Tuya.
Перепрошивать Wi-Fi-модуль в кондиционере я не стал. Вместо этого пошёл более консервативным путём: прочитал локальные Tuya DPS через TinyTuya, аккуратно определил назначение нужных параметров и собрал небольшой MQTT-мост в Docker. Home Assistant в этой схеме видит кондиционер как обычное MQTT-устройство через MQTT Discovery.
Статья не про взлом криптографии Tuya и не про написание клиента протокола с нуля. Эту часть уже берёт на себя TinyTuya. Здесь речь о практическом reverse engineering на уровне смыслов: какой datapoint за что отвечает именно в этом кондиционере, какие значения безопасно читать, какие можно менять и как не потерять соседние флаги при записи.
После настройки в Home Assistant появились сущности:
climate.royal_clima_pandora
switch.royal_clima_power
switch.royal_clima_display
switch.royal_clima_buzzer
sensor.royal_clima_humidity
sensor.royal_clima_dp123
sensor.royal_clima_raw_dps
Проверенные функции работают локально: питание, режимы cool, heat, dry, fan_only, auto, установка температуры, скорость вентилятора, включение и выключение дисплея, включение и выключение звукового сигнала. Отдельно публикуются сырые DPS, чтобы позже можно было исследовать дополнительные функции без переписывания всей интеграции.
Архитектура получилась такой:
Royal Clima / Tuya Wi-Fi module
│
│ local Tuya protocol 3.5
▼
Python bridge в Docker
│
│ MQTT
▼
Mosquitto
│
│ MQTT Discovery
▼
Home Assistant
Схема не требует custom integration для Home Assistant. Мост работает отдельным контейнером рядом с Home Assistant и Mosquitto, читает состояние кондиционера через локальную сеть и публикует команды/состояния в MQTT.
Для воспроизводимости дальше используются условные значения:
|
Компонент |
Значение |
|---|---|
|
Модель кондиционера |
Royal Clima Pandora RC-PDC22HN |
|
Хост Home Assistant |
|
|
IP хоста Home Assistant |
|
|
IP кондиционера |
|
|
Локальный протокол Tuya |
|
|
Device ID |
|
|
Product ID |
|
|
MQTT-брокер |
Mosquitto в Docker |
|
MQTT-пользователь |
|
|
Каталог проекта |
|
Из инструментов понадобились TinyTuya, Mosquitto, Python-клиент paho-mqtt, Docker Compose и встроенный механизм Home Assistant MQTT Discovery.
Tuya-устройства описывают свои функции через DPS — datapoints. Это не человекочитаемые имена вроде display или buzzer, а числовые идентификаторы: 1, 2, 3, 123 и так далее. Значение DP может быть boolean, integer, enum, строкой или служебной JSON-подобной строкой.
Home Assistant через облачную Tuya-интеграцию обычно опирается на описание устройства, полученное из Tuya Cloud. Такое описание может быть неполным. В моём случае в облачной карте были видны только базовые параметры:
1 switch
2 temp_set
3 temp_current
4 mode
18 humidity_current
19 temp_unit_convert
Локальный опрос показал, что устройство отдаёт больше данных:
{
"dps": {
"1": false,
"2": 210,
"3": 27,
"4": "cold",
"5": "low",
"18": 0,
"19": "c",
"20": 0,
"23": 81,
"24": 700,
"101": 0,
"102": "off",
"103": false,
"105": "off",
"110": 567,
"120": "off",
"123": "0008",
"125": "great",
"130": 26,
"131": false,
"132": false,
"134": "{"t":1784217961,"s":false,"clr":false}"
}
}
Именно этот разрыв между облачным описанием и локальным состоянием стал причиной делать свой мост.
Для локального управления Tuya-устройством нужны три вещи: IP-адрес устройства, device ID и local_key. IP и device ID можно увидеть через локальное сканирование TinyTuya, а ключ обычно получают через Tuya IoT project и TinyTuya wizard.
Пример результата локального сканирования:
TinyTuya device scan
Address = 192.168.10.50
Device ID = bf1234567890abcdef1234
Product ID = kloexamplepandora
Version = 3.5
После wizard появляется файл devices.json. В реальном файле будет настоящий local_key, поэтому его не стоит хранить в публичном репозитории и тем более публиковать в статье. В проекте я положил его отдельно от кода:
/opt/royal-clima/data/devices.json
Права на файл лучше сразу закрыть:
sudo chown root:root /opt/royal-clima/data/devices.json
sudo chmod 600 /opt/royal-clima/data/devices.json
Минимальный вид записи в devices.json:
[
{
"name": "Air Conditioner",
"id": "bf1234567890abcdef1234",
"key": "0123456789abcdef",
"mac": "AA:BB:CC:DD:EE:FF",
"category": "kt",
"product_name": "Air conditioner",
"product_id": "kloexamplepandora"
}
]
Перед Docker и MQTT важно проверить самый простой сценарий: устройство вообще читается локально. Я использовал отдельный маленький скрипт read_status.py.
import json
import tinytuya
DEVICE_ID = "bf1234567890abcdef1234"
AC_IP = "192.168.10.50"
AC_VERSION = 3.5
DEVICES_JSON = "devices.json"
with open(DEVICES_JSON, "r", encoding="utf-8") as file:
devices = json.load(file)
info = next(item for item in devices if item["id"] == DEVICE_ID)
local_key = info["key"]
device = tinytuya.Device(DEVICE_ID, AC_IP, local_key)
device.set_version(AC_VERSION)
device.set_socketTimeout(10)
status = device.status()
print(json.dumps(status, indent=2, ensure_ascii=False))
Запускал его в отдельном виртуальном окружении:
python3 -m venv .venv
source .venv/bin/activate
pip install tinytuya==1.20.0
python read_status.py
Если в ответе есть объект dps, можно двигаться дальше. Если появляется ошибка расшифровки, в первую очередь стоит проверить local_key и версию протокола. Если скрипт уходит в timeout, надо проверять сетевую доступность устройства, firewall и порт 6668.
ping 192.168.10.50
nc -vz 192.168.10.50 6668
На этом этапе легко испортить устройство лишними экспериментами, поэтому я придерживался простого правила: сначала только чтение, затем изменение одной функции за раз, потом повторное чтение и физическая проверка результата.
Для наблюдения использовался watcher. Он сравнивает предыдущее состояние с новым и печатает только изменившиеся DP. Служебный DP 134 исключён, потому что он менялся постоянно и только мешал анализу.
import json
import time
import tinytuya
DEVICE_ID = "bf1234567890abcdef1234"
AC_IP = "192.168.10.50"
AC_VERSION = 3.5
DEVICES_JSON = "devices.json"
IGNORE_DPS = {"134"}
with open(DEVICES_JSON, "r", encoding="utf-8") as file:
devices = json.load(file)
local_key = next(item for item in devices if item["id"] == DEVICE_ID)["key"]
device = tinytuya.Device(DEVICE_ID, AC_IP, local_key)
device.set_version(AC_VERSION)
device.set_socketTimeout(10)
previous = {}
while True:
dps = device.status().get("dps", {})
for dp, value in dps.items():
if dp in IGNORE_DPS:
continue
if previous.get(dp) != value:
print(f"DP {dp}: {previous.get(dp)!r} -> {value!r}")
previous = dps
time.sleep(1)
Дальше я открывал SmartLife или брал пульт и менял только одну функцию за раз: Display, Buzzer, режим работы, температуру, скорость вентилятора. Такой подход медленнее, чем хаотично нажимать всё подряд, зато он даёт чистые диффы.
После нескольких циклов наблюдения получилась такая карта:
|
DP |
Пример значения |
Назначение |
Статус |
|---|---|---|---|
|
|
|
питание |
подтверждено |
|
|
|
заданная температура, |
подтверждено |
|
|
|
текущая температура, °C |
подтверждено |
|
|
|
режим работы |
подтверждено |
|
|
|
скорость вентилятора |
подтверждено |
|
|
|
влажность |
читается, польза сомнительная |
|
|
|
единицы температуры |
понятно, но обычно не трогаю |
|
|
|
текущая температура в °F |
дубль DP3 |
|
|
|
заданная температура в °F × 10 |
дубль DP2 |
|
|
|
флаговая маска Display/Buzzer |
подтверждено |
|
|
JSON timestamp |
heartbeat |
игнорируется |
Самым интересным оказался DP123. Он не был отдельным переключателем. В нём одновременно лежали несколько флагов.
При переключении дисплея и звукового подтверждения watcher показывал такие изменения:
DP 123: '0000' -> '0008'
DP 123: '0008' -> '0000'
DP 123: '0000' -> '0010'
DP 123: '0010' -> '0000'
После проверки на самом кондиционере карта стала понятной:
|
DP123 |
Display |
Buzzer |
|---|---|---|
|
|
OFF |
OFF |
|
|
ON |
OFF |
|
|
OFF |
ON |
|
|
ON |
ON |
То есть значения 0008 и 0010 — не самостоятельные состояния, а биты одной маски:
DISPLAY_MASK = 0x0008
BUZZER_MASK = 0x0010
Из-за этого нельзя просто записать в DP123 новое значение вслепую. Если включить дисплей записью 0008, можно случайно выключить уже включённый Buzzer. Правильная операция выглядит иначе: прочитать текущее значение, изменить один бит и записать результат обратно.
def update_flag(raw_value, mask, enabled):
current = int(raw_value, 16)
if enabled:
updated = current | mask
else:
updated = current & ~mask
return f"{updated:04X}"
Например, если текущее значение 0010, а нужно включить Display, результатом будет 0018, а не 0008.
После проверки DPS я перенёс логику в отдельный Docker-сервис. Структура проекта минимальная:
/opt/royal-clima/
├── app/
│ ├── Dockerfile
│ └── royal_clima_bridge.py
├── data/
│ └── devices.json
├── .env
└── docker-compose.yml
Файл .env содержит параметры устройства и MQTT. В нём нет ничего сложного, но есть пароль MQTT, поэтому права на него тоже лучше закрыть.
sudo chown root:root /opt/royal-clima/.env
sudo chmod 600 /opt/royal-clima/.env
Пример .env:
TZ=Europe/Moscow
AC_DEVICE_ID=bf1234567890abcdef1234
AC_IP=192.168.10.50
AC_VERSION=3.5
DEVICES_JSON=/data/devices.json
MQTT_HOST=127.0.0.1
MQTT_PORT=1883
MQTT_USER=ac_bridge
MQTT_PASSWORD=CHANGE_ME_STRONG_PASSWORD
MQTT_BASE_TOPIC=royal_clima/pandora
HA_DISCOVERY_PREFIX=homeassistant
POLL_INTERVAL=10
DISPLAY_MASK=0x0008
BUZZER_MASK=0x0010
docker-compose.yml:
version: "3.8"
services:
royal_clima_bridge:
build:
context: ./app
container_name: royal_clima_bridge
restart: unless-stopped
network_mode: host
env_file:
- .env
volumes:
- ./data:/data:ro
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir tinytuya==1.20.0 paho-mqtt==2.1.0
COPY royal_clima_bridge.py /app/royal_clima_bridge.py
CMD ["python", "/app/royal_clima_bridge.py"]
Mosquitto у меня уже работал рядом с Home Assistant. Для моста я сделал отдельного MQTT-пользователя, чтобы не использовать общий административный пароль.
Фрагмент конфигурации Mosquitto:
listener 1883
allow_anonymous false
password_file /mosquitto/config/mqttuser
Создание пользователя:
sudo docker exec -it mosquitto
mosquitto_passwd /mosquitto/config/mqttuser ac_bridge
sudo docker restart mosquitto
Здесь намеренно нет ключа -c. Он нужен только при создании нового password-файла. Если файл уже существует, -c может перезаписать базу пользователей.
Проверка публикации:
sudo docker exec mosquitto mosquitto_pub
-h 127.0.0.1
-u ac_bridge
-P 'CHANGE_ME_STRONG_PASSWORD'
-t test/royal_clima
-m 'mqtt_auth_test'
Полный файл моста довольно длинный, поэтому в статье лучше оставить только важные части. Ниже — логика, которая определяет поведение интеграции.
Сначала задаются основные DP и соответствие режимов Tuya режимам Home Assistant:
DP_POWER = 1
DP_TARGET_TEMP = 2
DP_CURRENT_TEMP = 3
DP_MODE = 4
DP_FAN = 5
DP_HUMIDITY = 18
DP_FLAGS = 123
TUYA_TO_HA_MODE = {
"cold": "cool",
"hot": "heat",
"wet": "dry",
"wind": "fan_only",
"auto": "auto",
}
HA_TO_TUYA_MODE = {
"cool": "cold",
"heat": "hot",
"dry": "wet",
"fan_only": "wind",
"auto": "auto",
}
Отдельно описываются скорости вентилятора. На моём устройстве средняя скорость приходила как middle, а в Home Assistant удобнее использовать medium.
TUYA_TO_HA_FAN = {
"auto": "auto",
"low": "low",
"middle": "medium",
"high": "high",
}
HA_TO_TUYA_FAN = {
"auto": "auto",
"low": "low",
"medium": "middle",
"high": "high",
}
Работа с DP123 вынесена в отдельную функцию. Это уменьшает риск случайно затереть соседний бит.
def set_flag(flag_name, enabled):
dps = read_dps()
old_raw = str(dps.get(str(DP_FLAGS), "0000"))
old_value = int(old_raw, 16)
mask = FLAGS[flag_name]
if enabled:
new_value = old_value | mask
else:
new_value = old_value & ~mask
new_raw = f"{new_value:04X}"
if new_raw != old_raw:
set_dp(DP_FLAGS, new_raw)
publish_current_state()
Обработчик MQTT-команд разделяет обычные команды и флаговые команды:
def on_message(client, userdata, message):
topic = message.topic
payload = message.payload.decode().strip()
if topic.endswith("/power/set"):
set_dp(DP_POWER, payload.upper() == "ON")
elif topic.endswith("/temperature/set"):
set_dp(DP_TARGET_TEMP, int(float(payload) * 10))
elif topic.endswith("/mode/set"):
if payload == "off":
set_dp(DP_POWER, False)
else:
set_dp(DP_POWER, True)
set_dp(DP_MODE, HA_TO_TUYA_MODE[payload])
elif topic.endswith("/display/set"):
set_flag("display", payload.upper() == "ON")
elif topic.endswith("/buzzer/set"):
set_flag("buzzer", payload.upper() == "ON")
Чтобы не добавлять сущности вручную в configuration.yaml, мост при старте публикует retained discovery-конфигурацию. Например, climate-сущность описывается так:
climate_payload = {
"name": "Royal Clima Pandora",
"unique_id": "royal_clima_pandora_climate",
"object_id": "royal_clima_pandora",
"modes": ["off", "cool", "heat", "dry", "fan_only", "auto"],
"mode_state_topic": "royal_clima/pandora/mode/state",
"mode_command_topic": "royal_clima/pandora/mode/set",
"temperature_state_topic": "royal_clima/pandora/temperature/state",
"temperature_command_topic": "royal_clima/pandora/temperature/set",
"current_temperature_topic": "royal_clima/pandora/current_temperature/state",
"fan_mode_state_topic": "royal_clima/pandora/fan/state",
"fan_mode_command_topic": "royal_clima/pandora/fan/set",
"fan_modes": ["auto", "low", "medium", "high"],
"min_temp": 16,
"max_temp": 31,
"temp_step": 0.5,
"precision": 0.1,
"temperature_unit": "C",
"availability_topic": "royal_clima/pandora/availability",
"device": DEVICE_INFO,
}
Публикуется это в discovery topic:
homeassistant/climate/royal_clima_pandora/config
Для Display, Buzzer и Power используются обычные MQTT switches. В результате Home Assistant сам создаёт устройство и сущности после перезапуска моста или брокера.
Сборка и запуск:
sudo bash -lc 'cd /opt/royal-clima && docker compose build'
sudo bash -lc 'cd /opt/royal-clima && docker compose up -d'
Проверка логов:
sudo docker logs --tail=100 -f royal_clima_bridge
Нормальный старт выглядит примерно так:
2026-07-16 20:00:55 [INFO] Connecting to MQTT 127.0.0.1:1883
2026-07-16 20:00:55 [INFO] Connected to MQTT: rc=0
2026-07-16 20:00:55 [INFO] MQTT Discovery published
2026-07-16 20:00:55 [INFO] Published state: power=False mode=off target=21.0 current=27.0 fan=low DP123=0008
Если в логах есть предупреждение Paho о deprecated Callback API, это не обязательно блокирует работу. Позже код можно обновить под новый callback API, но для проверки самой схемы это не критично.
Сначала я проверял не интерфейс Home Assistant, а сами MQTT-топики. Так проще понять, где именно ошибка: в кондиционере, мосте, брокере или discovery.
sudo bash -lc '
set -a
. /opt/royal-clima/.env
set +a
docker exec mosquitto mosquitto_sub
-h 127.0.0.1
-u "$MQTT_USER"
-P "$MQTT_PASSWORD"
-t "royal_clima/pandora/#"
-v
-C 20
'
Пример ожидаемого вывода:
royal_clima/pandora/availability online
royal_clima/pandora/power/state OFF
royal_clima/pandora/mode/state off
royal_clima/pandora/temperature/state 21.0
royal_clima/pandora/current_temperature/state 27.0
royal_clima/pandora/fan/state low
royal_clima/pandora/display/state ON
royal_clima/pandora/buzzer/state OFF
royal_clima/pandora/raw/123/state 0008
Отдельная команда для проверки дисплея:
sudo bash -lc '
set -a
. /opt/royal-clima/.env
set +a
docker exec mosquitto mosquitto_pub
-h 127.0.0.1
-u "$MQTT_USER"
-P "$MQTT_PASSWORD"
-t "royal_clima/pandora/display/set"
-m "ON"
'
В логе моста должно быть видно изменение DP123:
MQTT command: royal_clima/pandora/display/set = ON
Set flag display=True: DP123 0000 -> 0008
Set DP 123 = '0008'
Device response: ... '123': '0008'
После публикации MQTT Discovery в Home Assistant появилось устройство Royal Clima Pandora. В карточке climate отображается текущая температура и заданная температура, а рядом доступны отдельные переключатели Power, Display и Buzzer.
Проверку я делал последовательно. Сначала включение и выключение питания, затем установка температуры, потом режимы, скорость вентилятора и только после этого DP123-функции. Такой порядок полезен потому, что при ошибке сразу понятно, какой слой сломался.
Порядок проверки:
Power ON/OFF
Temperature 21 → 22 °C
Mode cool → dry → fan_only
Fan low → medium → high
Display ON/OFF
Buzzer ON/OFF
Для температуры ожидаемая запись такая: если в интерфейсе выбрано 22 °C, в DP2 уходит 220, потому что у параметра scale = 1. Для режима нужен перевод значений: cool в Home Assistant соответствует cold у Tuya, dry соответствует wet, а fan_only соответствует wind.
После того как базовая схема заработала, остаётся соблазн быстро превратить все неизвестные DP в переключатели. Я бы так не делал. В кондиционере могут быть не только пользовательские функции, но и служебные параметры.
Безопасная стратегия такая: все неизвестные DP сначала публикуются как diagnostic sensors, затем наблюдаются при переключении одной функции, после чего подтверждаются физическим поведением. Только после этого их можно превращать в switch, select или number.
Кандидаты для дальнейшего исследования:
|
DP |
Пример |
Возможный смысл |
|---|---|---|
|
|
|
дополнительный режим |
|
|
|
boolean-функция |
|
|
|
дополнительный режим |
|
|
|
дополнительный режим |
|
|
|
диагностический статус |
|
|
|
внутренний параметр или температура |
|
|
|
boolean-функция |
|
|
|
boolean-функция |
Для исследования удобно подписаться на сырые DPS:
sudo bash -lc '
set -a
. /opt/royal-clima/.env
set +a
docker exec mosquitto mosquitto_sub
-h 127.0.0.1
-u "$MQTT_USER"
-P "$MQTT_PASSWORD"
-t "royal_clima/pandora/raw/dps/attributes"
-v
'
И дальше вести таблицу наблюдений:
|
Функция |
Было |
Стало |
Вывод |
|---|---|---|---|
|
Sleep |
|
|
проверить |
|
Turbo |
|
|
проверить |
|
Eco |
|
|
проверить |
|
Swing vertical |
|
|
проверить |
|
Self Clean |
|
|
проверить |
|
Health/Ion |
|
|
проверить |
В этой задаче ИИ не угадывал назначение DP и не заменял эксперимент. Он был полезен как инженерный ассистент: помогал формулировать гипотезы, собирать маленькие проверочные скрипты, сравнивать логи и не раздувать мост раньше времени.
Рабочий процесс был таким: сначала фиксируется фактический вывод d.status(), затем выбирается одна гипотеза, затем пишется короткий проверочный скрипт. После этого меняется только одна функция кондиционера, сравнивается дифф и проверяется физический результат. Если в логе состояние изменилось, но кондиционер никак не отреагировал, гипотеза не считается подтверждённой.
Такой подход особенно помог с DP123. Если смотреть только на отдельные значения 0008 и 0010, можно решить, что это разные режимы. Но при сравнении нескольких переходов стало видно, что это именно битовая маска.
Несколько клиентов TinyTuya одновременно. На время экспериментов лучше остановить лишние watcher-скрипты. Одновременный опрос устройства несколькими клиентами может давать нестабильные ответы.
Смена local_key. Если удалить устройство из SmartLife и привязать заново, ключ может измениться. После этого старый devices.json перестанет работать.
DP134 мешает анализу. Этот DP менялся постоянно и выглядел как heartbeat с timestamp. Для диффов его лучше исключать.
Неизвестные DP нельзя трогать вслепую. Запись в неподтверждённый DP может включить не пользовательскую функцию, а служебный режим.
MQTT retain важен. Discovery config и последние состояния лучше публиковать retained, чтобы Home Assistant корректно переживал перезапуск брокера или моста.
Блокировку облака лучше отложить. После нескольких дней стабильной локальной работы можно закрыть кондиционеру доступ в WAN через firewall, оставив доступ внутри LAN. Но делать это первым шагом не стоит: сначала нужно убедиться, что локальная схема действительно стабильна.
Главный результат этой работы — не сам Python-скрипт, а методика. Если стандартная Tuya-интеграция Home Assistant показывает только базовые функции, это не значит, что остальные функции недоступны локально. Часто устройство отдаёт больше DPS, чем описывает облачный профиль, но эти параметры нужно аккуратно найти и подтвердить.
Для Royal Clima Pandora хватило нескольких шагов: получить local_key, прочитать локальные DPS, отделить служебный heartbeat от пользовательских параметров, определить битовую маску DP123, собрать тонкий MQTT-мост и опубликовать сущности через MQTT Discovery.
В результате штатный Wi-Fi-модуль остался на месте, SmartLife можно оставить как резервный канал, а Home Assistant получил локальное управление нужными функциями кондиционера.
Home Assistant MQTT integration: https://www.home-assistant.io/integrations/mqtt/ [1]
Home Assistant MQTT climate: https://www.home-assistant.io/integrations/climate.mqtt/ [2]
TinyTuya: https://github.com/jasonacox/tinytuya [3]
Eclipse Mosquitto authentication methods: https://mosquitto.org/documentation/authentication-methods/ [4]
Mosquitto configuration manual: https://mosquitto.org/man/mosquitto-conf-5.html [5]
Eclipse Paho MQTT Python client: https://eclipse.dev/paho/files/paho.mqtt.python/html/ [6]
Автор: wildriver
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/mqtt/455512
Ссылки в тексте:
[1] https://www.home-assistant.io/integrations/mqtt/: https://www.home-assistant.io/integrations/mqtt/
[2] https://www.home-assistant.io/integrations/climate.mqtt/: https://www.home-assistant.io/integrations/climate.mqtt/
[3] https://github.com/jasonacox/tinytuya: https://github.com/jasonacox/tinytuya
[4] https://mosquitto.org/documentation/authentication-methods/: https://mosquitto.org/documentation/authentication-methods/
[5] https://mosquitto.org/man/mosquitto-conf-5.html: https://mosquitto.org/man/mosquitto-conf-5.html
[6] https://eclipse.dev/paho/files/paho.mqtt.python/html/: https://eclipse.dev/paho/files/paho.mqtt.python/html/
[7] Источник: https://habr.com/ru/articles/1062934/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1062934
Нажмите здесь для печати.