опыт реализации
Паровой электрокамин
Интеграция в систему умного дома Wirenboard.
В процессе работы над проектами умных домов иногда возникают задачи, которые требуют нестандартного подхода. Одной из таких задач стала интеграция умного 2-метрового электрокамина производства компании Schönes Feuer - 3D Fireline 2000 в систему автоматизации Wirenboard. Расскажем о том, как мы с этим справились.
Начало работы и предварительная подготовка

Проект был передан нам на этапе реализации. К этому моменту уже была выполнена черновая отделка помещения и проложены кабельные линии. Перед нами стояла задача провести инвентаризацию выполненных работ, выявить ошибки и исправить их, а также собрать щит умного дома и подключить все запланированные устройства.
Одной из сложностей стало ограниченное пространство для монтажа оборудования. Большую часть места занимали блоки питания светодиодных лент, что потребовало тщательного планирования размещения остального оборудования. В качестве защитных устройств и автоматов были выбраны компактные решения от Siemens, так как найти оборудование надлежащего качества и размеров в России оказалось затруднительно.
Подключение электрокамина

Среди устройств, которые необходимо было интегрировать в систему умного дома, особое место занимал электрокамин. Производитель этого устройства ранее пообещал возможность управления через API с использованием HTTP-запросов. После установки камина на место и его подключения к электросети началась работа по его интеграции.
Первые шаги

Камин оснащён платой управления, которая создаёт собственную точку доступа Wi-Fi. Однако попытки подключиться к камину напрямую через веб-интерфейс не увенчались успехом. Для настройки подключения к локальной сети потребовалось установить приложение на смартфон. Через него удалось настроить подключение камина к Wi-Fi и получить доступ к его IP-адресу в локальной сети.
Изучение API

Из документации производителя были взяты примеры HTTP-команд, которые можно отправлять камину для управления его состоянием. Например, это могли быть команды включения/выключения, изменения режимов работы или регулировки яркости. Камин успешно выполнял эти команды, но использование его только через приложение или HTTP-запросы не решало главной задачи — интеграции в систему умного дома.
Интеграция с Wirenboard

Wirenboard не имеет встроенных инструментов для управления устройствами через HTTP-запросы, такими как наш камин. Также отсутствует поддержка протоколов RS-485 или других стандартных интерфейсов. Поэтому для решения задачи потребовалось написать скрипт на Python, который бы обеспечивал взаимодействие контроллера Wirenboard с платой управления камина.

Даже несмотря на то, что сам камин расположен сравнительно недалеко от WiFi роутера в Постирочной, возникла характерная для "беспровода" проблема стабильности связи. Модуль беспроводной связи камина находится в металлическом корпусе и при подаче на него питания не всегда подключается в локальную сеть, а не найдя ее, переходит в режим точки доступа.
С помощью техподдержки удалось это подтвердить, но в любом случае получается, что проводное соединение не давало бы такого поведения!
Поиск решения

После обращения в техподдержку Wirenboard мы получили информацию о том, что подобные задачи решаются с помощью пользовательских скриптов на Python. Однако у нашей команды не было достаточного опыта программирования на этом языке. Мы решили обратиться за помощью к нейросетям.
Использование нейросетей

Нейросеть помогла нам создать скрипт, который выполнял следующие функции:
1. Отправка HTTP-запросов к камину для управления его состоянием.
2. Получение текущих параметров работы камина (например, статус включения, выбранный режим).
3. Интеграция с интерфейсом Wirenboard для управления камином через сценарии умного дома.
Последовательность действий

Для успешного взаимодействия с нейросетью мы предоставили ей следующую информацию:
- Описание задачи и её целей.
- Примеры HTTP-запросов из документации производителя камина.
- Требования к интеграции с Wirenboard.
Результат превзошёл ожидания: практически с первого раза мы получили рабочий скрипт, который успешно выполнил все поставленные задачи.

Оставалось только выполнить косметические действия для того чтобы удобно было использовать в дальнейших инсталляциях данный скрипт, вынести в графический файл настроечные параметры и дать возможность отображения органов управления камином в SprutHub и через него в Алису.
ЗАПРОС:
Имеется сетевой устройство - камин.
По адресу 192.168.1.17
Оно может отправлять свое состояние на контроллер в той же локальной сети
Программа устанавливается на контроллер, отправляет запросы и принимает ответы посредством http, а затем полученные данные отправляет на локальный MQTT брокер.
Для подключения к брокеру и устройству имеется конфигурационный файл /mnt/data/python/fireplace_config.json
Вот его содержимое:
{
"mqtt": {
"broker": "localhost",
"name": "fireplace",
"port": 1883,
"username": "admin",
"password": "GQNywTnjr"
},
"device": {
"base_url": "192.168.1.17"
}
}
Напиши программу, которая будет подключаться к MQTT брокеру, каждые 10 секунд запрашивать у камина данные командой {BASE_URL}/jsonSetings и публиковать их в топик лога /devices/fireplace/controls/log на брокере
Для этого топика нужно создать подтопик /devices/fireplace/controls/log/meta с содержимым { "order": 1, "title": {"en": "Log", "ru": "Лог"}, "type": "text", "readonly": True }

Чтобы содержимое этого топика отображалось в интерфейсе контроллера, нужно создать еще один meta топик /devices/fireplace/meta с содержимым { "driver": "wb-rules", "title": {"en": "Fireplace", "ru": "Камин"} }
Также желательно выводить в лог приходящие сообщения
DeepSeek:
Для решения задачи напишем программу на Python, которая будет подключаться к MQTT-брокеру, запрашивать данные у камина и публиковать их в соответствующие топики. Также добавим логирование для отладки и мониторинга.
Программа на Python
Описание программы

1.    Загрузка конфигурации:
    Конфигурация загружается из файла fireplace_config.json.
    Параметры MQTT и устройства извлекаются из конфигурации.
2.    MQTT-клиент:
    Используется библиотека paho-mqtt для подключения к MQTT-брокеру.
    Обработчики on_connect и on_message обрабатывают подключение и входящие сообщения.
3.    Meta-топики:
    При подключении к брокеру публикуются meta-топики для корректного отображения в интерфейсе.
4.    Запрос данных у камина:
    Каждые 10 секунд программа запрашивает данные у камина по адресу {BASE_URL}/jsonSettings.
    Данные публикуются в топик /devices/fireplace/controls/log.
5.    Логирование:
    Программа логирует ключевые события (подключение к брокеру, публикация данных, ошибки).
ЗависимостиДля работы программы необходимо установить следующие библиотеки:
pip install paho-mqtt requests
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging

# Настройка логирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"

def load_config():
    """Загружает конфигурацию из JSON-файла."""
    try:
        with open(CONFIG_FILE, "r") as file:
            return json.load(file)
    except Exception as e:
        logger.error(f"Ошибка загрузки конфигурации: {e}")
        raise

# Загрузка конфигурации
config = load_config()

# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]

# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"

# Топики MQTT
LOG_TOPIC = "/devices/fireplace/controls/log"
LOG_META_TOPIC = "/devices/fireplace/controls/log/meta"
DEVICE_META_TOPIC = "/devices/fireplace/meta"

# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)

def on_connect(client, userdata, flags, rc):
    """Обработчик подключения к MQTT-брокеру."""
    if rc == 0:
        logger.info("Успешное подключение к MQTT-брокеру")
        # Публикация meta-топиков
        publish_meta_topics()
    else:
        logger.error(f"Ошибка подключения к MQTT-брокеру: {rc}")

def publish_meta_topics():
    """Публикует meta-топики для корректного отображения в интерфейсе."""
    log_meta = {
        "order": 1,
        "title": {"en": "Log", "ru": "Лог"},
        "type": "text",
        "readonly": True
    }
    device_meta = {
        "driver": "wb-rules",
        "title": {"en": "Fireplace", "ru": "Камин"}
    }
    mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=True)
    mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=True)
    logger.info("Meta-топики опубликованы")

def fetch_device_data():
    """Запрашивает данные у камина."""
    try:
        response = requests.get(f"{DEVICE_BASE_URL}/jsonSettings")
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса данных у камина: {e}")
        return None

def on_message(client, userdata, msg):
    """Обработчик входящих MQTT-сообщений."""
    logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")

def main():
    """Основной цикл программы."""
    # Подключение к MQTT-брокеру
    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message
    mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
    mqtt_client.loop_start()

    # Основной цикл
    while True:
        # Запрос данных у камина
        data = fetch_device_data()
        if data:
            # Публикация данных в топик лога
            mqtt_client.publish(LOG_TOPIC, json.dumps(data))
            logger.info(f"Данные опубликованы в топик {LOG_TOPIC}")
        # Ожидание 10 секунд
        time.sleep(10)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        logger.info("Программа завершена")
    except Exception as e:
        logger.error(f"Ошибка в работе программы: {e}")
    finally:
        mqtt_client.loop_stop()
        mqtt_client.disconnect()
ЗАПРОС:
Теперь необходимо создать еще один топик /devices/fireplace/controls/power
Для него meta: { "order": 1, "title": {"en": "Power", "ru": "Включение"}, "type": "switch", "readonly": false, }
DeepSeek:
Добавим новый топик /devices/fireplace/controls/power и его meta-описание. Также добавим обработку входящих сообщений для этого топика, чтобы программа могла реагировать на команды включения/выключения.
Что добавлено

1.    Новый топик /devices/fireplace/controls/power:
o    Добавлен топик для управления включением/выключением камина.
o    Для него создан meta-топик /devices/fireplace/controls/power/meta сописанием:
2.    Обработка входящих сообщений:
o    Программа подписывается на топик POWER_TOPIC и обрабатывает команды on, off, 1, 0.
o    Логирует полученные команды.
3.    Логика управления:
o    В текущей версии программа только логирует команды. Если нужно отправить команду на устройство, это можно добавить в раздел обработки сообщений.
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging
from datetime import datetime
 
# Настройка логирования
logging.basicConfig(
   level=logging.INFO,
   format="%(asctime)s - %(levelname)s - %(message)s",
   datefmt="%H:%M:%S" # Формат времени: часы:минуты:секунды
)
logger = logging.getLogger(__name__)
 
# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"
 
def load_config():
"""Загружает конфигурацию из JSON-файла."""
   try:
       with open(CONFIG_FILE, "r") as file:
           return json.load(file)
   except Exception as e:
       logger.error(f"Ошибка загрузки конфигурации: {e}")
       raise
 
# Загрузка конфигурации
config = load_config()
 
# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]
 
# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"
 
# Топики MQTT
LOG_TOPIC = "/devices/fireplace/controls/log"
LOG_META_TOPIC = "/devices/fireplace/controls/log/meta"
DEVICE_META_TOPIC = "/devices/fireplace/meta"
POWER_TOPIC = "/devices/fireplace/controls/power"
POWER_META_TOPIC = "/devices/fireplace/controls/power/meta"
 
# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
 
def on_connect(client, userdata, flags, rc):
   """Обработчик подключения к MQTT-брокеру."""
   if rc == 0:
logger.info("Успешное подключение к MQTT-брокеру")
       # Публикация meta-топиков
       publish_meta_topics()
       # Подписка на топик power для управления
       mqtt_client.subscribe(POWER_TOPIC)
   else:
       logger.error(f"Ошибка подключенияк MQTT-брокеру: {rc}")
 
def publish_meta_topics():
"""Публикует meta-топики для корректного отображения в интерфейсе."""
   # Meta для лога
   log_meta = {
       "order": 1,
       "title": {"en": "Log", "ru": "Лог"},
       "type": "text",
       "readonly": True
   }
   mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=True)
 
   # Meta для управления power
   power_meta = {
       "order": 1,
       "title": {"en": "Power", "ru": "Включение"},
       "type": "switch",
       "readonly": False
   }
   mqtt_client.publish(POWER_META_TOPIC, json.dumps(power_meta), retain=True)
 
   # Meta для устройства
   device_meta = {
       "driver": "wb-rules",
       "title": {"en": "Fireplace", "ru": "Камин"}
   }
   mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=True)
 
   logger.info("Meta-топики опубликованы")
 
def fetch_device_data():
"""Запрашивает данные у камина."""
   try:
       response = requests.get(f"{DEVICE_BASE_URL}/jsonSetings") # Исправленона jsonSetings
       response.raise_for_status()
       return response.json()
   except requests.exceptions.RequestException as e:
       logger.error(f"Ошибка запроса данных у камина: {e}")
       return None
 
def on_message(client, userdata, msg):
   """Обработчик входящих MQTT-сообщений."""
   logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")
 
   # Обработкакоманд для топика power
   if msg.topic == POWER_TOPIC:
       try:
           payload = msg.payload.decode()
           if payload.lower() in ["on", "off", "1", "0"]:
               # Логируем команду
               logger.info(f"Команда для power: {payload}")
               # Здесь можно добавить логику для управления устройством
               # Например, отправить команду на устройство
           else:
logger.warning(f"Некорректная команда для power: {payload}")
       except Exception as e:
           logger.error(f"Ошибка обработкикоманды для power: {e}")
 
def main():
"""Основной цикл программы."""
   # Подключениек MQTT-брокеру
   mqtt_client.on_connect = on_connect
   mqtt_client.on_message = on_message
   mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
   mqtt_client.loop_start()
 
   # Основной цикл
   while True:
       # Запрос данных у камина
       data = fetch_device_data()
       if data:
           # Публикация данных в топик лога
           mqtt_client.publish(LOG_TOPIC, json.dumps(data))
           # Логирование полученных данных
logger.info(f"Получены данные: {json.dumps(data, indent=2)}")
       # Ожидание 10 секунд
time.sleep(10)
 
if __name__ == "__main__":
   try:
       main()
   except KeyboardInterrupt:
       logger.info("Программа завершена")
   except Exception as e:
       logger.error(f"Ошибка в работе программы: {e}")
   finally:
       mqtt_client.loop_stop()
       mqtt_client.disconnect()
ЗАПРОС:
Добавь еще топики /devices/{DEVICE}/controls/fire_mode/on /devices/{DEVICE}/controls/audio_mode/on И meta к ним: { "order": 3, "title": {"en": "Fire Mode", "ru": "Режим огня"}, "type": "range", "min": 1, "max": 4 }, { "order": 4, "title": {"en": "Audio Mode", "ru": "Режим звука"}, "type": "range", "min": 0, "max": 2 } При поступлении данных в эти топики необходимо отправлять камину команды вида "fire_mode_1": f"{DEVICE_BASE_URL}/SAVE?select_rez=1", "fire_mode_2": f"{DEVICE_BASE_URL}/SAVE?select_rez=2", "fire_mode_3": f"{DEVICE_BASE_URL}/SAVE?select_rez=3", "fire_mode_4": f"{DEVICE_BASE_URL}/SAVE?select_rez=4", "audio_mode_0": f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej=0", "audio_mode_1": f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej=1", "audio_mode_2": f" DEVICE_BASE_URL}/SAVE?AUDIO_rej=2",
А если из приходящих от камина параметров приходит "AUDIO_rej": 0, или другое значение, то преобразовываем его в Int и записываем в топик /devices/{DEVICE}/controls/audio_mode (без /on) Аналогично для параметра "select_rez": 3, и топика /devices/{DEVICE}/controls/fire_mode (без /on)
DeepSeek:
Добавил новые топики для управления режимами огня (fire_mode) и звука (audio_mode), а также их meta-описания.
1.    Создает топики:
    /devices/{DEVICE}/controls/fire_mode/on
    /devices/{DEVICE}/controls/audio_mode/on
    /devices/{DEVICE}/controls/fire_mode
    /devices/{DEVICE}/controls/audio_mode
2.    Публикует meta-описания для новых топиков.
3.    Обрабатывает входящие команды для fire_mode/on и audio_mode/on, отправляя соответствующие HTTP-запросы на устройство.
4.    Парсит данные от камина и обновляет топики fire_mode и audio_mode значениями параметров select_rez и AUDIO_rej.
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%H:%M:%S"  # Формат времени: часы:минуты:секунды
)
logger = logging.getLogger(__name__)

# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"

def load_config():
    """Загружает конфигурацию из JSON-файла."""
    try:
        with open(CONFIG_FILE, "r") as file:
            return json.load(file)
    except Exception as e:
        logger.error(f"Ошибка загрузки конфигурации: {e}")
        raise

# Загрузка конфигурации
config = load_config()

# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]

# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"
DEVICE = "fireplace"  # Имя устройства

# Топики MQTT
LOG_TOPIC = f"/devices/{DEVICE}/controls/log"
LOG_META_TOPIC = f"/devices/{DEVICE}/controls/log/meta"
DEVICE_META_TOPIC = f"/devices/{DEVICE}/meta"
POWER_TOPIC = f"/devices/{DEVICE}/controls/power"
POWER_META_TOPIC = f"/devices/{DEVICE}/controls/power/meta"
POWER_ON_TOPIC = f"/devices/{DEVICE}/controls/power/on"
FIRE_MODE_TOPIC = f"/devices/{DEVICE}/controls/fire_mode"
FIRE_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/on"
FIRE_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/meta"
AUDIO_MODE_TOPIC = f"/devices/{DEVICE}/controls/audio_mode"
AUDIO_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/on"
AUDIO_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/meta"

# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)

def on_connect(client, userdata, flags, rc):
    """Обработчик подключения к MQTT-брокеру."""
    if rc == 0:
        logger.info("Успешное подключение к MQTT-брокеру")
        # Публикация meta-топиков
        publish_meta_topics()
        # Подписка на топики управления
        mqtt_client.subscribe(POWER_ON_TOPIC)
        mqtt_client.subscribe(FIRE_MODE_ON_TOPIC)
        mqtt_client.subscribe(AUDIO_MODE_ON_TOPIC)
    else:
        logger.error(f"Ошибка подключения к MQTT-брокеру: {rc}")

def publish_meta_topics():
    """Публикует meta-топики для корректного отображения в интерфейсе."""
    # Meta для лога
    log_meta = {
        "order": 2,
        "title": {"en": "Log", "ru": "Лог"},
        "type": "text",
        "readonly": True
    }
    mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=False)

    # Meta для управления power
    power_meta = {
        "order": 1,
        "title": {"en": "Power", "ru": "Включение"},
        "type": "switch",
        "readonly": False
    }
    mqtt_client.publish(POWER_META_TOPIC, json.dumps(power_meta), retain=False)

    # Meta для fire_mode
    fire_mode_meta = {
        "order": 3,
        "title": {"en": "Fire Mode", "ru": "Режим огня"},
        "type": "range",
        "min": 1,
        "max": 4
    }
    mqtt_client.publish(FIRE_MODE_META_TOPIC, json.dumps(fire_mode_meta), retain=False)

    # Meta для audio_mode
    audio_mode_meta = {
        "order": 4,
        "title": {"en": "Audio Mode", "ru": "Режим звука"},
        "type": "range",
        "min": 0,
        "max": 2
    }
    mqtt_client.publish(AUDIO_MODE_META_TOPIC, json.dumps(audio_mode_meta), retain=False)

    # Meta для устройства
    device_meta = {
        "driver": "wb-rules",
        "title": {"en": "Fireplace", "ru": "Камин"}
    }
    mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=False)

    logger.info("Meta-топики опубликованы")

def fetch_device_data():
    """Запрашивает данные у камина."""
    try:
        response = requests.get(f"{DEVICE_BASE_URL}/jsonSetings")  # Исправлено на jsonSetings
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса данных у камина: {e}")
        return None

def send_command(url):
    """Отправляет команду на устройство."""
    try:
        response = requests.post(url)
        response.raise_for_status()
        logger.info(f"Команда отправлена: {url}")
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка отправки команды: {e}")

def on_message(client, userdata, msg):
    """Обработчик входящих MQTT-сообщений."""
    logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")

    # Обработка команд для power/on
    if msg.topic == POWER_ON_TOPIC:
        try:
            payload = msg.payload.decode().strip().lower()
            if payload in ["on", "off", "1", "0"]:
                power_value = 1 if payload in ["on", "1"] else 0
                send_command(f"{DEVICE_BASE_URL}/analog?POWER={power_value}")
                mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
        except Exception as e:
            logger.error(f"Ошибка обработки команды для power/on: {e}")

    # Обработка команд для fire_mode/on
    elif msg.topic == FIRE_MODE_ON_TOPIC:
        try:
            fire_mode = int(msg.payload.decode().strip())
            if 1 <= fire_mode <= 4:
                send_command(f"{DEVICE_BASE_URL}/SAVE?select_rez={fire_mode}")
                mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для fire_mode: {fire_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для fire_mode/on: {e}")

    # Обработка команд для audio_mode/on
    elif msg.topic == AUDIO_MODE_ON_TOPIC:
        try:
            audio_mode = int(msg.payload.decode().strip())
            if 0 <= audio_mode <= 2:
                send_command(f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej={audio_mode}")
                mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для audio_mode: {audio_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для audio_mode/on: {e}")

def main():
    """Основной цикл программы."""
    # Подключение к MQTT-брокеру
    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message
    mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
    mqtt_client.loop_start()

    # Основной цикл
    while True:
        # Запрос данных у камина
        data = fetch_device_data()
        if data:
            # Публикация данных в топик лога
            mqtt_client.publish(LOG_TOPIC, json.dumps(data), retain=False)
            logger.info(f"Получены данные: {json.dumps(data, indent=2)}")

            # Обновление топика power
            if "POWER" in data:
                try:
                    power_value = int(data["POWER"])
                    mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования POWER: {e}")

            # Обновление топика fire_mode
            if "select_rez" in data:
                try:
                    fire_mode = int(data["select_rez"])
                    mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования select_rez: {e}")

            # Обновление топика audio_mode
            if "AUDIO_rej" in data:
                try:
                    audio_mode = int(data["AUDIO_rej"])
                    mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования AUDIO_rej: {e}")

        # Ожидание 10 секунд
        time.sleep(10)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        logger.info("Программа завершена")
    except Exception as e:
        logger.error(f"Ошибка в работе программы: {e}")
    finally:
        mqtt_client.loop_stop()
        mqtt_client.disconnect()
ЗАПРОС:
Теперь необходимо добавить обработку корректного завершения программы и обработку недоступности MQTT брокера
DeepSeek:
Добавил обработку корректного завершения программы и обработку недоступности MQTT-брокера.
1.    Корректно завершает работу при получении сигнала SIGINT (Ctrl+C) или SIGTERM.
2.    Обрабатывает недоступность MQTT-брокера, пытаясь переподключиться с интервалом в 5 секунд.
3.    Логирует все ошибки и попытки переподключения.
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging
import signal
import sys
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%H:%M:%S"  # Формат времени: часы:минуты:секунды
)
logger = logging.getLogger(__name__)

# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"

def load_config():
    """Загружает конфигурацию из JSON-файла."""
    try:
        with open(CONFIG_FILE, "r") as file:
            return json.load(file)
    except Exception as e:
        logger.error(f"Ошибка загрузки конфигурации: {e}")
        raise

# Загрузка конфигурации
config = load_config()

# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]

# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"
DEVICE = "fireplace"  # Имя устройства

# Топики MQTT
LOG_TOPIC = f"/devices/{DEVICE}/controls/log"
LOG_META_TOPIC = f"/devices/{DEVICE}/controls/log/meta"
DEVICE_META_TOPIC = f"/devices/{DEVICE}/meta"
POWER_TOPIC = f"/devices/{DEVICE}/controls/power"
POWER_META_TOPIC = f"/devices/{DEVICE}/controls/power/meta"
POWER_ON_TOPIC = f"/devices/{DEVICE}/controls/power/on"
FIRE_MODE_TOPIC = f"/devices/{DEVICE}/controls/fire_mode"
FIRE_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/on"
FIRE_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/meta"
AUDIO_MODE_TOPIC = f"/devices/{DEVICE}/controls/audio_mode"
AUDIO_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/on"
AUDIO_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/meta"

# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)

# Флаг для корректного завершения программы
running = True

def signal_handler(sig, frame):
    """Обработчик сигналов для корректного завершения программы."""
    global running
    logger.info("Получен сигнал завершения. Завершение работы...")
    running = False
    sys.exit(0)

# Регистрация обработчика сигналов
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def on_connect(client, userdata, flags, rc):
    """Обработчик подключения к MQTT-брокеру."""
    if rc == 0:
        logger.info("Успешное подключение к MQTT-брокеру")
        # Публикация meta-топиков
        publish_meta_topics()
        # Подписка на топики управления
        mqtt_client.subscribe(POWER_ON_TOPIC)
        mqtt_client.subscribe(FIRE_MODE_ON_TOPIC)
        mqtt_client.subscribe(AUDIO_MODE_ON_TOPIC)
    else:
        logger.error(f"Ошибка подключения к MQTT-брокеру: {rc}")

def on_disconnect(client, userdata, rc):
    """Обработчик отключения от MQTT-брокера."""
    if rc != 0:
        logger.warning("Отключение от MQTT-брокера. Попытка переподключения...")
        while running:
            try:
                mqtt_client.reconnect()
                logger.info("Переподключение к MQTT-брокеру успешно")
                break
            except Exception as e:
                logger.error(f"Ошибка переподключения: {e}. Повторная попытка через 5 секунд...")
                time.sleep(5)

def publish_meta_topics():
    """Публикует meta-топики для корректного отображения в интерфейсе."""
    # Meta для лога
    log_meta = {
        "order": 2,
        "title": {"en": "Log", "ru": "Лог"},
        "type": "text",
        "readonly": True
    }
    mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=False)

    # Meta для управления power
    power_meta = {
        "order": 1,
        "title": {"en": "Power", "ru": "Включение"},
        "type": "switch",
        "readonly": False
    }
    mqtt_client.publish(POWER_META_TOPIC, json.dumps(power_meta), retain=False)

    # Meta для fire_mode
    fire_mode_meta = {
        "order": 3,
        "title": {"en": "Fire Mode", "ru": "Режим огня"},
        "type": "range",
        "min": 1,
        "max": 4
    }
    mqtt_client.publish(FIRE_MODE_META_TOPIC, json.dumps(fire_mode_meta), retain=False)

    # Meta для audio_mode
    audio_mode_meta = {
        "order": 4,
        "title": {"en": "Audio Mode", "ru": "Режим звука"},
        "type": "range",
        "min": 0,
        "max": 2
    }
    mqtt_client.publish(AUDIO_MODE_META_TOPIC, json.dumps(audio_mode_meta), retain=False)

    # Meta для устройства
    device_meta = {
        "driver": "wb-rules",
        "title": {"en": "Fireplace", "ru": "Камин"}
    }
    mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=False)

    logger.info("Meta-топики опубликованы")

def fetch_device_data():
    """Запрашивает данные у камина."""
    try:
        response = requests.get(f"{DEVICE_BASE_URL}/jsonSetings")  # Исправлено на jsonSetings
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса данных у камина: {e}")
        return None

def send_command(url):
    """Отправляет команду на устройство."""
    try:
        response = requests.post(url)
        response.raise_for_status()
        logger.info(f"Команда отправлена: {url}")
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка отправки команды: {e}")

def on_message(client, userdata, msg):
    """Обработчик входящих MQTT-сообщений."""
    logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")

    # Обработка команд для power/on
    if msg.topic == POWER_ON_TOPIC:
        try:
            payload = msg.payload.decode().strip().lower()
            if payload in ["on", "off", "1", "0"]:
                power_value = 1 if payload in ["on", "1"] else 0
                send_command(f"{DEVICE_BASE_URL}/analog?POWER={power_value}")
                mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
        except Exception as e:
            logger.error(f"Ошибка обработки команды для power/on: {e}")

    # Обработка команд для fire_mode/on
    elif msg.topic == FIRE_MODE_ON_TOPIC:
        try:
            fire_mode = int(msg.payload.decode().strip())
            if 1 <= fire_mode <= 4:
                send_command(f"{DEVICE_BASE_URL}/SAVE?select_rez={fire_mode}")
                mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для fire_mode: {fire_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для fire_mode/on: {e}")

    # Обработка команд для audio_mode/on
    elif msg.topic == AUDIO_MODE_ON_TOPIC:
        try:
            audio_mode = int(msg.payload.decode().strip())
            if 0 <= audio_mode <= 2:
                send_command(f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej={audio_mode}")
                mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для audio_mode: {audio_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для audio_mode/on: {e}")

def main():
    """Основной цикл программы."""
    global running

    # Настройка обработчиков MQTT
    mqtt_client.on_connect = on_connect
    mqtt_client.on_disconnect = on_disconnect
    mqtt_client.on_message = on_message

    # Подключение к MQTT-брокеру
    while running:
        try:
            mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
            break
        except Exception as e:
            logger.error(f"Ошибка подключения к MQTT-брокеру: {e}. Повторная попытка через 5 секунд...")
            time.sleep(5)

    mqtt_client.loop_start()

    # Основной цикл
    while running:
        # Запрос данных у камина
        data = fetch_device_data()
        if data:
            # Публикация данных в топик лога
            mqtt_client.publish(LOG_TOPIC, json.dumps(data), retain=False)
            logger.info(f"Получены данные: {json.dumps(data, indent=2)}")

            # Обновление топика power
            if "POWER" in data:
                try:
                    power_value = int(data["POWER"])
                    mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования POWER: {e}")

            # Обновление топика fire_mode
            if "select_rez" in data:
                try:
                    fire_mode = int(data["select_rez"])
                    mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования select_rez: {e}")

            # Обновление топика audio_mode
            if "AUDIO_rej" in data:
                try:
                    audio_mode = int(data["AUDIO_rej"])
                    mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования AUDIO_rej: {e}")

        # Ожидание 10 секунд
        time.sleep(10)

    # Корректное завершение работы
    logger.info("Завершение работы программы...")
    mqtt_client.loop_stop()
    mqtt_client.disconnect()

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logger.error(f"Ошибка в работе программы: {e}")
    finally:
        logger.info("Программа завершена")
ЗАПРОС:
В случае если от камина перестают приходит ответы на запросы /jsonSetings, то в топики
/devices/{DEVICE}/controls/power/error
/devices/{DEVICE}/controls/fire_mode/error /devices/{DEVICE}/controls/audio_mode/error
нужно записать значение r как только снова начинают приходить сообщения, то туда записать пустое значение ""
DeepSeek:
Добавил обработку ошибок при запросе данных от камина.
1.    Если запрос к /jsonSetings завершается ошибкой или данные не приходят, в топики:
    /devices/{DEVICE}/controls/power/error
    /devices/{DEVICE}/controls/fire_mode/error
    /devices/{DEVICE}/controls/audio_mode/error
записываетсязначение "r".
2.    Как только данные снова начинают приходить, в эти топики записывается пустое значение "".
3.    Логируются все ошибки и изменения состояния.
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging
import signal
import sys
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%H:%M:%S"  # Формат времени: часы:минуты:секунды
)
logger = logging.getLogger(__name__)

# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"

def load_config():
    """Загружает конфигурацию из JSON-файла."""
    try:
        with open(CONFIG_FILE, "r") as file:
            return json.load(file)
    except Exception as e:
        logger.error(f"Ошибка загрузки конфигурации: {e}")
        raise

# Загрузка конфигурации
config = load_config()

# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]

# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"
DEVICE = "fireplace"  # Имя устройства

# Топики MQTT
LOG_TOPIC = f"/devices/{DEVICE}/controls/log"
LOG_META_TOPIC = f"/devices/{DEVICE}/controls/log/meta"
DEVICE_META_TOPIC = f"/devices/{DEVICE}/meta"
POWER_TOPIC = f"/devices/{DEVICE}/controls/power"
POWER_META_TOPIC = f"/devices/{DEVICE}/controls/power/meta"
POWER_ON_TOPIC = f"/devices/{DEVICE}/controls/power/on"
POWER_ERROR_TOPIC = f"/devices/{DEVICE}/controls/power/error"
FIRE_MODE_TOPIC = f"/devices/{DEVICE}/controls/fire_mode"
FIRE_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/on"
FIRE_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/meta"
FIRE_MODE_ERROR_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/error"
AUDIO_MODE_TOPIC = f"/devices/{DEVICE}/controls/audio_mode"
AUDIO_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/on"
AUDIO_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/meta"
AUDIO_MODE_ERROR_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/error"

# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)

# Флаг для корректного завершения программы
running = True

def signal_handler(sig, frame):
    """Обработчик сигналов для корректного завершения программы."""
    global running
    logger.info("Получен сигнал завершения. Завершение работы...")
    running = False
    sys.exit(0)

# Регистрация обработчика сигналов
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def on_connect(client, userdata, flags, rc):
    """Обработчик подключения к MQTT-брокеру."""
    if rc == 0:
        logger.info("Успешное подключение к MQTT-брокеру")
        # Публикация meta-топиков
        publish_meta_topics()
        # Подписка на топики управления
        mqtt_client.subscribe(POWER_ON_TOPIC)
        mqtt_client.subscribe(FIRE_MODE_ON_TOPIC)
        mqtt_client.subscribe(AUDIO_MODE_ON_TOPIC)
    else:
        logger.error(f"Ошибка подключения к MQTT-брокеру: {rc}")

def on_disconnect(client, userdata, rc):
    """Обработчик отключения от MQTT-брокера."""
    if rc != 0:
        logger.warning("Отключение от MQTT-брокера. Попытка переподключения...")
        while running:
            try:
                mqtt_client.reconnect()
                logger.info("Переподключение к MQTT-брокеру успешно")
                break
            except Exception as e:
                logger.error(f"Ошибка переподключения: {e}. Повторная попытка через 5 секунд...")
                time.sleep(5)

def publish_meta_topics():
    """Публикует meta-топики для корректного отображения в интерфейсе."""
    # Meta для лога
    log_meta = {
        "order": 2,
        "title": {"en": "Log", "ru": "Лог"},
        "type": "text",
        "readonly": True
    }
    mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=False)

    # Meta для управления power
    power_meta = {
        "order": 1,
        "title": {"en": "Power", "ru": "Включение"},
        "type": "switch",
        "readonly": False
    }
    mqtt_client.publish(POWER_META_TOPIC, json.dumps(power_meta), retain=False)

    # Meta для fire_mode
    fire_mode_meta = {
        "order": 3,
        "title": {"en": "Fire Mode", "ru": "Режим огня"},
        "type": "range",
        "min": 1,
        "max": 4
    }
    mqtt_client.publish(FIRE_MODE_META_TOPIC, json.dumps(fire_mode_meta), retain=False)

    # Meta для audio_mode
    audio_mode_meta = {
        "order": 4,
        "title": {"en": "Audio Mode", "ru": "Режим звука"},
        "type": "range",
        "min": 0,
        "max": 2
    }
    mqtt_client.publish(AUDIO_MODE_META_TOPIC, json.dumps(audio_mode_meta), retain=False)

    # Meta для устройства
    device_meta = {
        "driver": "wb-rules",
        "title": {"en": "Fireplace", "ru": "Камин"}
    }
    mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=False)

    logger.info("Meta-топики опубликованы")

def fetch_device_data():
    """Запрашивает данные у камина."""
    try:
        response = requests.get(f"{DEVICE_BASE_URL}/jsonSetings", timeout=5)  # Таймаут 5 секунд
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса данных у камина: {e}")
        return None

def send_command(url):
    """Отправляет команду на устройство."""
    try:
        response = requests.post(url, timeout=5)  # Таймаут 5 секунд
        response.raise_for_status()
        logger.info(f"Команда отправлена: {url}")
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка отправки команды: {e}")

def on_message(client, userdata, msg):
    """Обработчик входящих MQTT-сообщений."""
    logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")

    # Обработка команд для power/on
    if msg.topic == POWER_ON_TOPIC:
        try:
            payload = msg.payload.decode().strip().lower()
            if payload in ["on", "off", "1", "0"]:
                power_value = 1 if payload in ["on", "1"] else 0
                send_command(f"{DEVICE_BASE_URL}/analog?POWER={power_value}")
                mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
        except Exception as e:
            logger.error(f"Ошибка обработки команды для power/on: {e}")

    # Обработка команд для fire_mode/on
    elif msg.topic == FIRE_MODE_ON_TOPIC:
        try:
            fire_mode = int(msg.payload.decode().strip())
            if 1 <= fire_mode <= 4:
                send_command(f"{DEVICE_BASE_URL}/SAVE?select_rez={fire_mode}")
                mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для fire_mode: {fire_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для fire_mode/on: {e}")

    # Обработка команд для audio_mode/on
    elif msg.topic == AUDIO_MODE_ON_TOPIC:
        try:
            audio_mode = int(msg.payload.decode().strip())
            if 0 <= audio_mode <= 2:
                send_command(f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej={audio_mode}")
                mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для audio_mode: {audio_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для audio_mode/on: {e}")

def main():
    """Основной цикл программы."""
    global running

    # Настройка обработчиков MQTT
    mqtt_client.on_connect = on_connect
    mqtt_client.on_disconnect = on_disconnect
    mqtt_client.on_message = on_message

    # Подключение к MQTT-брокеру
    while running:
        try:
            mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
            break
        except Exception as e:
            logger.error(f"Ошибка подключения к MQTT-брокеру: {e}. Повторная попытка через 5 секунд...")
            time.sleep(5)

    mqtt_client.loop_start()

    # Флаг для отслеживания состояния ошибки
    error_state = False

    # Основной цикл
    while running:
        # Запрос данных у камина
        data = fetch_device_data()
        if data:
            # Если данные пришли, сбрасываем ошибку
            if error_state:
                mqtt_client.publish(POWER_ERROR_TOPIC, "", retain=False)
                mqtt_client.publish(FIRE_MODE_ERROR_TOPIC, "", retain=False)
                mqtt_client.publish(AUDIO_MODE_ERROR_TOPIC, "", retain=False)
                error_state = False

            # Публикация данных в топик лога
            mqtt_client.publish(LOG_TOPIC, json.dumps(data), retain=False)
            logger.info(f"Получены данные: {json.dumps(data, indent=2)}")

            # Обновление топика power
            if "POWER" in data:
                try:
                    power_value = int(data["POWER"])
                    mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования POWER: {e}")

            # Обновление топика fire_mode
            if "select_rez" in data:
                try:
                    fire_mode = int(data["select_rez"])
                    mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования select_rez: {e}")

            # Обновление топика audio_mode
            if "AUDIO_rej" in data:
                try:
                    audio_mode = int(data["AUDIO_rej"])
                    mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования AUDIO_rej: {e}")
        else:
            # Если данные не пришли, публикуем ошибку
            if not error_state:
                mqtt_client.publish(POWER_ERROR_TOPIC, "r", retain=False)
                mqtt_client.publish(FIRE_MODE_ERROR_TOPIC, "r", retain=False)
                mqtt_client.publish(AUDIO_MODE_ERROR_TOPIC, "r", retain=False)
                error_state = True
                logger.warning("Данные от камина не получены. Ошибка записана в топики.")

        # Ожидание 10 секунд
        time.sleep(10)

    # Корректное завершение работы
    logger.info("Завершение работы программы...")
    mqtt_client.loop_stop()
    mqtt_client.disconnect()

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logger.error(f"Ошибка в работе программы: {e}")
    finally:
        logger.info("Программа завершена")
ЗАПРОС:
И при считывании конфигурации необходимо либо включать вывод в лог либо не выводить и тогда в топик лога записать значение "Debugging OFF"
DeepSeek:
Добавил поддержку параметра debug в конфигурации
1.    Считывает параметр debug из конфигурации.
2.    Если debug равен true, включает вывод отладочных сообщений в лог.
3.    Если debug равен false, записывает в топик лога значение "Debugging OFF" и отключает вывод отладочных сообщений.
import json
import time
import requests
import paho.mqtt.client as mqtt
import logging
import signal
import sys
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%H:%M:%S"  # Формат времени: часы:минуты:секунды
)
logger = logging.getLogger(__name__)

# Загрузка конфигурации из файла
CONFIG_FILE = "/mnt/data/python/fireplace_config.json"

def load_config():
    """Загружает конфигурацию из JSON-файла."""
    try:
        with open(CONFIG_FILE, "r") as file:
            return json.load(file)
    except Exception as e:
        logger.error(f"Ошибка загрузки конфигурации: {e}")
        raise

# Загрузка конфигурации
config = load_config()

# Параметры MQTT
MQTT_BROKER = config["mqtt"]["broker"]
MQTT_PORT = config["mqtt"]["port"]
MQTT_USERNAME = config["mqtt"]["username"]
MQTT_PASSWORD = config["mqtt"]["password"]
MQTT_CLIENT_NAME = config["mqtt"]["name"]

# Параметры устройства
DEVICE_BASE_URL = f"http://{config['device']['base_url']}"
DEVICE = "fireplace"  # Имя устройства
DEBUG_MODE = config["device"].get("debug", False)  # Режим отладки

# Топики MQTT
LOG_TOPIC = f"/devices/{DEVICE}/controls/log"
LOG_META_TOPIC = f"/devices/{DEVICE}/controls/log/meta"
DEVICE_META_TOPIC = f"/devices/{DEVICE}/meta"
POWER_TOPIC = f"/devices/{DEVICE}/controls/power"
POWER_META_TOPIC = f"/devices/{DEVICE}/controls/power/meta"
POWER_ON_TOPIC = f"/devices/{DEVICE}/controls/power/on"
POWER_ERROR_TOPIC = f"/devices/{DEVICE}/controls/power/error"
FIRE_MODE_TOPIC = f"/devices/{DEVICE}/controls/fire_mode"
FIRE_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/on"
FIRE_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/meta"
FIRE_MODE_ERROR_TOPIC = f"/devices/{DEVICE}/controls/fire_mode/error"
AUDIO_MODE_TOPIC = f"/devices/{DEVICE}/controls/audio_mode"
AUDIO_MODE_ON_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/on"
AUDIO_MODE_META_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/meta"
AUDIO_MODE_ERROR_TOPIC = f"/devices/{DEVICE}/controls/audio_mode/error"

# Создание MQTT-клиента
mqtt_client = mqtt.Client(MQTT_CLIENT_NAME)
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)

# Флаг для корректного завершения программы
running = True

def signal_handler(sig, frame):
    """Обработчик сигналов для корректного завершения программы."""
    global running
    logger.info("Получен сигнал завершения. Завершение работы...")
    running = False
    sys.exit(0)

# Регистрация обработчика сигналов
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def on_connect(client, userdata, flags, rc):
    """Обработчик подключения к MQTT-брокеру."""
    if rc == 0:
        logger.info("Успешное подключение к MQTT-брокеру")
        # Публикация meta-топиков
        publish_meta_topics()
        # Подписка на топики управления
        mqtt_client.subscribe(POWER_ON_TOPIC)
        mqtt_client.subscribe(FIRE_MODE_ON_TOPIC)
        mqtt_client.subscribe(AUDIO_MODE_ON_TOPIC)
    else:
        logger.error(f"Ошибка подключения к MQTT-брокеру: {rc}")

def on_disconnect(client, userdata, rc):
    """Обработчик отключения от MQTT-брокера."""
    if rc != 0:
        logger.warning("Отключение от MQTT-брокера. Попытка переподключения...")
        while running:
            try:
                mqtt_client.reconnect()
                logger.info("Переподключение к MQTT-брокеру успешно")
                break
            except Exception as e:
                logger.error(f"Ошибка переподключения: {e}. Повторная попытка через 5 секунд...")
                time.sleep(5)

def publish_meta_topics():
    """Публикует meta-топики для корректного отображения в интерфейсе."""
    # Meta для лога
    log_meta = {
        "order": 4,
        "title": {"en": "Log", "ru": "Лог"},
        "type": "text",
        "readonly": True
    }
    mqtt_client.publish(LOG_META_TOPIC, json.dumps(log_meta), retain=False)

    # Meta для управления power
    power_meta = {
        "order": 1,
        "title": {"en": "Power", "ru": "Включение"},
        "type": "switch",
        "readonly": False
    }
    mqtt_client.publish(POWER_META_TOPIC, json.dumps(power_meta), retain=False)

    # Meta для fire_mode
    fire_mode_meta = {
        "order": 2,
        "title": {"en": "Fire Mode", "ru": "Режим огня"},
        "type": "range",
        "min": 1,
        "max": 4
    }
    mqtt_client.publish(FIRE_MODE_META_TOPIC, json.dumps(fire_mode_meta), retain=False)

    # Meta для audio_mode
    audio_mode_meta = {
        "order": 3,
        "title": {"en": "Audio Mode", "ru": "Режим звука"},
        "type": "range",
        "min": 0,
        "max": 2
    }
    mqtt_client.publish(AUDIO_MODE_META_TOPIC, json.dumps(audio_mode_meta), retain=False)

    # Meta для устройства
    device_meta = {
        "driver": "wb-rules",
        "title": {"en": "Fireplace", "ru": "Камин"}
    }
    mqtt_client.publish(DEVICE_META_TOPIC, json.dumps(device_meta), retain=False)

    logger.info("Meta-топики опубликованы")

def fetch_device_data():
    """Запрашивает данные у камина."""
    try:
        response = requests.get(f"{DEVICE_BASE_URL}/jsonSetings", timeout=5)  # Таймаут 5 секунд
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса данных у камина: {e}")
        return None

def send_command(url):
    """Отправляет команду на устройство."""
    try:
        response = requests.post(url, timeout=5)  # Таймаут 5 секунд
        response.raise_for_status()
        logger.info(f"Команда отправлена: {url}")
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка отправки команды: {e}")

def on_message(client, userdata, msg):
    """Обработчик входящих MQTT-сообщений."""
    if DEBUG_MODE:
        logger.info(f"Получено сообщение: {msg.topic} - {msg.payload.decode()}")

    # Обработка команд для power/on
    if msg.topic == POWER_ON_TOPIC:
        try:
            payload = msg.payload.decode().strip().lower()
            if payload in ["on", "off", "1", "0"]:
                power_value = 1 if payload in ["on", "1"] else 0
                send_command(f"{DEVICE_BASE_URL}/analog?POWER={power_value}")
                mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
        except Exception as e:
            logger.error(f"Ошибка обработки команды для power/on: {e}")

    # Обработка команд для fire_mode/on
    elif msg.topic == FIRE_MODE_ON_TOPIC:
        try:
            fire_mode = int(msg.payload.decode().strip())
            if 1 <= fire_mode <= 4:
                send_command(f"{DEVICE_BASE_URL}/SAVE?select_rez={fire_mode}")
                mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для fire_mode: {fire_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для fire_mode/on: {e}")

    # Обработка команд для audio_mode/on
    elif msg.topic == AUDIO_MODE_ON_TOPIC:
        try:
            audio_mode = int(msg.payload.decode().strip())
            if 0 <= audio_mode <= 2:
                send_command(f"{DEVICE_BASE_URL}/SAVE?AUDIO_rej={audio_mode}")
                mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
            else:
                logger.warning(f"Некорректное значение для audio_mode: {audio_mode}")
        except (ValueError, TypeError) as e:
            logger.error(f"Ошибка обработки команды для audio_mode/on: {e}")

def main():
    """Основной цикл программы."""
    global running

    # Настройка уровня логирования
    if DEBUG_MODE:
        logger.setLevel(logging.DEBUG)
        logger.info("Режим отладки включен")
    else:
        logger.setLevel(logging.INFO)
        mqtt_client.publish(LOG_TOPIC, "Debugging OFF", retain=False)
        logger.info("Режим отладки выключен")

    # Настройка обработчиков MQTT
    mqtt_client.on_connect = on_connect
    mqtt_client.on_disconnect = on_disconnect
    mqtt_client.on_message = on_message

    # Подключение к MQTT-брокеру
    while running:
        try:
            mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
            break
        except Exception as e:
            logger.error(f"Ошибка подключения к MQTT-брокеру: {e}. Повторная попытка через 5 секунд...")
            time.sleep(5)

    mqtt_client.loop_start()

    # Флаг для отслеживания состояния ошибки
    error_state = False

    # Основной цикл
    while running:
        # Запрос данных у камина
        data = fetch_device_data()
        if data:
            # Если данные пришли, сбрасываем ошибку
            if error_state:
                mqtt_client.publish(POWER_ERROR_TOPIC, "", retain=False)
                mqtt_client.publish(FIRE_MODE_ERROR_TOPIC, "", retain=False)
                mqtt_client.publish(AUDIO_MODE_ERROR_TOPIC, "", retain=False)
                error_state = False

            # Публикация данных в топик лога
            mqtt_client.publish(LOG_TOPIC, json.dumps(data), retain=False)
            if DEBUG_MODE:
                logger.info(f"Получены данные: {json.dumps(data, indent=2)}")

            # Обновление топика power
            if "POWER" in data:
                try:
                    power_value = int(data["POWER"])
                    mqtt_client.publish(POWER_TOPIC, str(power_value), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования POWER: {e}")

            # Обновление топика fire_mode
            if "select_rez" in data:
                try:
                    fire_mode = int(data["select_rez"])
                    mqtt_client.publish(FIRE_MODE_TOPIC, str(fire_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования select_rez: {e}")

            # Обновление топика audio_mode
            if "AUDIO_rej" in data:
                try:
                    audio_mode = int(data["AUDIO_rej"])
                    mqtt_client.publish(AUDIO_MODE_TOPIC, str(audio_mode), retain=False)
                except (ValueError, TypeError) as e:
                    logger.error(f"Ошибка преобразования AUDIO_rej: {e}")
        else:
            # Если данные не пришли, публикуем ошибку
            if not error_state:
                mqtt_client.publish(POWER_ERROR_TOPIC, "r", retain=False)
                mqtt_client.publish(FIRE_MODE_ERROR_TOPIC, "r", retain=False)
                mqtt_client.publish(AUDIO_MODE_ERROR_TOPIC, "r", retain=False)
                error_state = True
                logger.warning("Данные от камина не получены. Ошибка записана в топики.")

        # Ожидание 10 секунд
        time.sleep(10)

    # Корректное завершение работы
    logger.info("Завершение работы программы...")
    mqtt_client.loop_stop()
    mqtt_client.disconnect()

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logger.error(f"Ошибка в работе программы: {e}")
    finally:
        logger.info("Программа завершена")
В процессе отладки были некоторые отступления от нормальной работы, но задавая нейросети выявленные симптомы она без труда выявляет ошибки в коде и добавляет код, позволяющий выявить причины и затем подправить работу всей системы.
Итоги

В результате нашей работы электрокамин был успешно интегрирован в систему умного дома Wirenboard. Теперь управление камином осуществляется через тот же интерфейс, что и остальными устройствами. Мы можем:
- Включать и выключать камин.
- Менять режимы работы.
- Добавлять камин в сценарии умного дома, например, включать его вместе с освещением или по расписанию.

Этот опыт показал, что даже при отсутствии готовых решений для интеграции нестандартных устройств современные технологии, такие как нейросети, могут значительно упростить задачу и помочь достичь желаемого результата.

Мы гордимся тем, что смогли реализовать эту задачу и продемонстрировать её успешную работу заказчику.
Все шаги по реализации данного проекта были взяты из реального общения с нейросетью https://chat.deepseek.com/
Отдельные фрагменты, не имеющие существенного значения, опущены для сохранения читабельности статьи, но представленные наглядно иллюстрируют возможности Искусственного интеллекта в написании работающих программ.
Полный актуальный код в репозитории Github
Мы приглашаем вас к использованию этого потенциала.
Тем кого заинтересовала наша работа, мы предлагаем сотрудничество и всегда готовы откликнуться на интересные предложения.
ЕСЛИ У ВАС ЕСТЬ ВОПРОСЫ, НАПИШИТЕ
Мы ответим в ближайшее время