Привет как мне вот этот план сделать давай сначала поясни план составь а код пока не пиши я сам буду код запрашивать отдельно по каждому пункту. 
1. Разделение программы на 2 модуля (туя и зигби). 
	Веб сервер один, модули туя и зигби могут друг без друга работать. запросы должны быть универсальными, вне зависимости от типа устройства (зигби или туя)
2. Внедрение защиты инсталяции на 2 модуля.
	Система определяет некий параметры компа и генерирует ключ по ним, и ответный ключ тоже генерирует. Например, что если через некоторое время поменяли железо на компе то программа должна ругаться но работать 5 дней
3. Сделать чтобы несколько слс работали в одной сети.
	Конфиг несколько слс добавлять. несколько инстансов zigbee2mqtt или сделать так чтобы в одном zigbee2mqtt работали несколько слс штук которые обмениваются с зигби2мкутт данными. . вот конфиг zigbee2mqtt homeassistant: false
permit_join: true
mqtt:
  base_topic: zigbee2mqtt
  server: mqtt://localhost
serial:
  port: 'tcp://192.168.1.108:8881'
advanced:
  homeassistant_legacy_entity_attributes: false
  legacy_api: false
  legacy_availability_payload: false
device_options:
  legacy: false
devices:
  '0x00124b002935e63a':
    friendly_name: '0x00124b002935e63a'
  '0xa4c1382186fa9186':
    friendly_name: '0xa4c1382186fa9186'
вот можно ли как-то с нескольких устройств
4. Сделать конфигурации 
	общий конфигурация
	конфиг туя 
	конфиг зигби
5. Получение нескольких статусов по id
6. Получение массива устройств и их команд
7. Нужна установка или удаление подписки на изменение некого параметра, при изменении чтобы дергал указанный в подписке сервис
8. При запуске не ждать обновления устройства, а брать сохраненные данные
вот кода рабочие from flask import Flask, request, jsonify
from flasgger import Swagger, swag_from
from tuya_connector import TuyaOpenAPI
import logging
import configparser
import time
from zigbee import *

app = Flask(__name__)
swagger = Swagger(app)  # Инициализация Flasgger

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

# Чтение конфигурации из config.ini
config = configparser.ConfigParser()
config.read('config.ini')

# Получение значений из конфигурации
TUYA_API_KEY = config['tuya']['api_key']
TUYA_API_SECRET = config['tuya']['api_secret']
TUYA_API_ENDPOINT = config['tuya']['api_endpoint']

openapi = TuyaOpenAPI(TUYA_API_ENDPOINT, TUYA_API_KEY, TUYA_API_SECRET)
try:
    openapi.connect()
    logging.info("Successfully connected to Tuya API")
except Exception as e:
    logging.error(f"Failed to connect to Tuya API: {e}")
    raise

def handle_response(response):
    """Handle API response and map Tuya error codes to HTTP status codes."""
    error_code_map = {
        1106: 403,  # Permission denied
        2008: 422,  # Command or value not supported
        1104: 400   # Invalid request
    }
    http_code = error_code_map.get(response.get('code'), 200)
    if http_code != 200:
        logging.warning(f"Error from Tuya API: {response}")
    return jsonify(response), http_code

def get_device_status_tuya(device_id):
    try:
        response = openapi.get(f'/v1.0/devices/{device_id}/status')
        logging.info(f"Device status for {device_id} retrieved successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to get device status for {device_id}: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}

def get_device_specifications_tuya(device_id):
    try:
        response = openapi.get(f'/v1.0/iot-03/devices/{device_id}/specification')
        logging.info(f"Device specifications for {device_id} retrieved successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to get device specifications for {device_id}: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}

def send_device_command_tuya(device_id, command):
    try:
        response = openapi.post(f'/v1.0/iot-03/devices/{device_id}/commands', command)
        logging.info(f"Command sent to device {device_id} successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to send command to device {device_id}: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}

def get_device_categories_tuya():
    try:
        response = openapi.get('/v1.0/iot-03/device-categories')
        logging.info("Device categories retrieved successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to get device categories: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}

def get_multiple_device_status_tuya(device_ids):
    try:
        response = openapi.get(f'/v1.0/iot-03/devices/status?device_ids={device_ids}')
        logging.info(f"Status for multiple devices ({device_ids}) retrieved successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to get status for multiple devices: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}

def get_devices_list_tuya(last_id=None, page_size=20):
    base_url = '/v2.0/cloud/thing/device'
    params = []
    if last_id is not None:
        params.append(f'last_id={last_id}')
    params.append(f'page_size={page_size}')
    query_string = '&'.join(params)
    url = f'{base_url}?{query_string}'
    
    try:
        response = openapi.get(url)
        logging.info("Device list retrieved successfully")
        return response
    except Exception as e:
        logging.error(f"Failed to get device list: {e}")
        return {"success": False, "code": 500, "msg": "Internal server error"}


@app.route('/tuya/device/status', methods=['GET'])
@swag_from({
    'parameters': [
        {
            'name': 'device_id',
            'in': 'query',
            'type': 'string',
            'required': True,
            'description': 'ID устройства, для которого нужно получить статус'
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': [
                        {'code': 'switch_1', 'value': True},
                        {'code': 'bright_value', 'value': 500},
                    ]
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'code': 1104,
                    'msg': 'type is incorrect',
                    'success': False
                }
            }
        },
        403: {
            'description': 'Доступ запрещен',
            'examples': {
                'application/json': {
                    'code': 1106,
                    'msg': 'permission deny',
                    'success': False
                }
            }
        },
        422: {
            'description': 'Команда или значение не поддерживается',
            'examples': {
                'application/json': {
                    'code': 2008,
                    'msg': 'command or value not support',
                    'success': False
                }
            }
        }
    }
})
def handle_get_device_status():
    device_id = request.args.get('device_id')
    if not device_id:
        logging.error("Device ID not provided in request")
        return jsonify({"success": False, "code": 400, "msg": "Device ID is required"}), 400
    
    response = get_device_status_tuya(device_id)
    return handle_response(response)



@app.route('/tuya/device/specifications', methods=['GET'])
@swag_from({
    'parameters': [
        {
            'name': 'device_id',
            'in': 'query',
            'type': 'string',
            'required': True,
            'description': 'ID устройства, для которого нужно получить спецификации'
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': {
                        'category': 'dj',
                        'functions': [
                            {
                                'code': 'temp_sampling',
                                'name': 'Sampling Time',
                                'type': 'Integer',
                                'desc': 'Time interval for sampling data',
                                'values': {
                                    'unit': 'min',
                                    'min': 10,
                                    'max': 480,
                                    'scale': 0,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'temp_unit_convert',
                                'name': 'Temperature Unit',
                                'type': 'Enum',
                                'desc': 'Convert temperature units',
                                'values': {
                                    'range': ['Celsius', 'Fahrenheit']
                                }
                            },
                            {
                                'code': 'maxtemp_set',
                                'name': 'Max Temperature',
                                'type': 'Integer',
                                'desc': 'Maximum temperature setting',
                                'values': {
                                    'unit': '°C',
                                    'min': -200,
                                    'max': 600,
                                    'scale': 1,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'minitemp_set',
                                'name': 'Min Temperature',
                                'type': 'Integer',
                                'desc': 'Minimum temperature setting',
                                'values': {
                                    'unit': '°C',
                                    'min': -200,
                                    'max': 600,
                                    'scale': 1,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'maxhum_set',
                                'name': 'Max Humidity',
                                'type': 'Integer',
                                'desc': 'Maximum humidity setting',
                                'values': {
                                    'unit': '%',
                                    'min': 0,
                                    'max': 100,
                                    'scale': 0,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'switch',
                                'name': 'Mute',
                                'type': 'Boolean',
                                'desc': 'Mute the device',
                                'values': {}
                            }
                        ],
                        'status': [
                            {
                                'code': 'temp_current',
                                'name': 'Current Temperature',
                                'type': 'Integer',
                                'desc': 'Current temperature value',
                                'values': {
                                    'unit': '°C',
                                    'min': -200,
                                    'max': 600,
                                    'scale': 1,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'humidity_value',
                                'name': 'Current Humidity',
                                'type': 'Integer',
                                'desc': 'Current humidity value',
                                'values': {
                                    'unit': '%',
                                    'min': 0,
                                    'max': 100,
                                    'scale': 1,
                                    'step': 1
                                }
                            },
                            {
                                'code': 'temp_unit_convert',
                                'name': 'Temperature Unit',
                                'type': 'Enum',
                                'desc': 'Current temperature unit',
                                'values': {
                                    'range': ['Celsius', 'Fahrenheit']
                                }
                            },
                            {
                                'code': 'switch',
                                'name': 'Mute Status',
                                'type': 'Boolean',
                                'desc': 'Mute status of the device',
                                'values': {}
                            }
                        ]
                    }
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'code': 1104,
                    'msg': 'type is incorrect',
                    'success': False
                }
            }
        },
        403: {
            'description': 'Доступ запрещен',
            'examples': {
                'application/json': {
                    'code': 1106,
                    'msg': 'permission deny',
                    'success': False
                }
            }
        },
        422: {
            'description': 'Команда или значение не поддерживается',
            'examples': {
                'application/json': {
                    'code': 2008,
                    'msg': 'command or value not support',
                    'success': False
                }
            }
        }
    }
})
def handle_get_device_specifications():
    device_id = request.args.get('device_id')
    if not device_id:
        logging.error("Device ID not provided in request")
        return jsonify({"success": False, "code": 400, "msg": "Device ID is required"}), 400

    response = get_device_specifications_tuya(device_id)
    return handle_response(response)

@app.route('/tuya/device/command', methods=['POST'])
@swag_from({
    'parameters': [
        {
            'name': 'body',
            'in': 'body',
            'required': True,
            'schema': {
                'type': 'object',
                'properties': {
                    'device_id': {'type': 'string'},
                    'command': {
                        'type': 'object',
                        'example': {
                            'commands': [
                                {'code': 'maxtemp_set', 'value': 400}
                            ]
                        }
                    }
                },
                'example': {
                    "device_id": "bff2b32c8174d07832xndl",
                    "command": {
                        "commands": [
                            {
                                "code": "maxtemp_set",
                                "value": 400
                            }
                        ]
                    }
                }
            }
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    "result": True,
                    "success": True,
                    "t": 1723624595485,
                    "tid": "50e1abaf5a1811efa755be0969012f23"
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    "code": 1104,
                    "msg": "type is incorrect",
                    "success": False,
                    "t": 1723625245449,
                    "tid": "d44ae7765a1911ef90101ed351686a6f"
                }
            }
        },
        422: {
            'description': 'Команда или значение не поддерживаются',
            'examples': {
                'application/json': {
                    "code": 2008,
                    "msg": "command or value not support",
                    "success": False,
                    "t": 1723625267328,
                    "tid": "e150e96e5a1911efb841325685d3ae3c"
                }
            }
        }
    }
})
def handle_send_device_command():
    data = request.json
    device_id = data.get('device_id')
    command = data.get('command')
    if not device_id or not command:
        logging.error("Device ID or command not provided in request")
        return jsonify({"success": False, "code": 400, "msg": "Device ID and command are required"}), 400

    response = send_device_command_tuya(device_id, command)
    return handle_response(response)
   

@app.route('/tuya/device/categories', methods=['GET'])
@swag_from({
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': [
                        {'code': 'cz', 'name': 'Socket'},
                        {'code': 'kg', 'name': 'Switch'},
                    ]
                }
            }
        }
    }
})
def handle_get_device_categories():
    logging.info(f"Received request to get categories of devices")
    response = get_device_categories_tuya()
    return handle_response(response)


@app.route('/tuya/devices/status', methods=['GET'])
@swag_from({
    'parameters': [
        {
            'name': 'device_ids',
            'in': 'query',
            'type': 'string',
            'required': True,
            'description': 'IDs устройств, разделенные запятыми. Ограничение до 20 устройств.'
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': [
                        {
                            'id': 'xxxxxx',
                            'status': [
                                {'code': 'switch_led', 'value': True},
                                {'code': 'work_mode', 'value': 'scene_2'}
                            ]
                        }
                    ]
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'code': 1104,
                    'msg': 'type is incorrect',
                    'success': False
                }
            }
        },
        403: {
            'description': 'Доступ запрещен',
            'examples': {
                'application/json': {
                    'code': 1106,
                    'msg': 'permission deny',
                    'success': False
                }
            }
        },
        422: {
            'description': 'Команда или значение не поддерживается',
            'examples': {
                'application/json': {
                    'code': 2008,
                    'msg': 'command or value not support',
                    'success': False
                }
            }
        }
    }
})
def handle_get_multiple_device_status():
    device_ids = request.args.get('device_ids')
    if not device_ids:
        logging.error("Device IDs not provided in request")
        return jsonify({"success": False, "code": 400, "msg": "Device IDs are required"}), 400

    response = get_multiple_device_status_tuya(device_ids)
    return handle_response(response)



@app.route('/tuya/devices/list', methods=['GET'])
@swag_from({
    'parameters': [
        {
            'name': 'last_id',
            'in': 'query',
            'type': 'string',
            'required': False,
            'description': 'ID последнего устройства, возвращенного на предыдущей странице. Этот параметр не передается для первой страницы.'
        },
        {
            'name': 'page_size',
            'in': 'query',
            'type': 'integer',
            'required': True,
            'description': 'Количество элементов, возвращаемых на каждой странице. Максимальное значение: 20.'
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    "result": [
                        {
                            "id": "vdevo165abcd98***",
                            "category": "cz",
                            "icon": "smart/icon/baydffaa6539efg/a6cdd065abcde3bfb689152eda***.png",
                            "ip": "124.90.34.***",
                            "lat": "50.3029",
                            "lon": "20.0641",
                            "name": "Robot Vacuum-vdevo",
                            "isOnline": True,
                            "activeTime": 1659422574,
                            "createTime": 1659422574,
                            "updateTime": 1659422574,
                            "customName": "Robot Vacuum in Living Room",
                            "uuid": "vdevo165abcd98***",
                            "productId": "h9soglkuugftb***",
                            "productName": "Robot Vacuum A001",
                            "sub": False,
                            "timeZone": "+8.0",
                            "localKey": "06f4adf1e5fc4***"
                        }
                    ],
                    "success": True,
                    "t": 1677752774750,
                    "tid": "a8bcb54fb8e411eda71e169efc83a172"
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'code': 1104,
                    'msg': 'type is incorrect',
                    'success': False
                }
            }
        },
        403: {
            'description': 'Доступ запрещен',
            'examples': {
                'application/json': {
                    'code': 1106,
                    'msg': 'permission deny',
                    'success': False
                }
            }
        },
        422: {
            'description': 'Команда или значение не поддерживается',
            'examples': {
                'application/json': {
                    'code': 2008,
                    'msg': 'command or value not support',
                    'success': False
                }
            }
        }
    }
})
def handle_get_devices_list():
    last_id = request.args.get('last_id')
    page_size = int(request.args.get('page_size', 20))

    if page_size > 20:
        logging.error("Page size exceeds the maximum limit of 20")
        return jsonify({"success": False, "code": 400, "msg": "Page size exceeds the maximum limit of 20"}), 400
    
    response = get_devices_list_tuya(last_id, page_size)
    return handle_response(response)

@app.route('/zigbee/device/command', methods=['POST'])
@swag_from({
    'parameters': [
        {
            'name': 'device_id',
            'in': 'json',
            'type': 'string',
            'required': True,
            'description': 'ID устройства Zigbee, которому отправляется команда'
        },
        {
            'name': 'command',
            'in': 'json',
            'type': 'object',
            'required': True,
            'description': 'Команда для Zigbee устройства, например {"state": "ON"}'
        }
    ],
    'responses': {
        200: {
            'description': 'Команда успешно отправлена',
            'examples': {
                'application/json': {
                    'success': True,
                    'msg': 'Command sent successfully'
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 400,
                    'msg': 'Invalid request format'
                }
            }
        },
        404: {
            'description': 'Устройство не найдено',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 404,
                    'msg': 'Device not found'
                }
            }
        },
        500: {
            'description': 'Внутренняя ошибка сервера',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 500,
                    'msg': 'Internal server error'
                }
            }
        }
    }
})
def send_zigbee_device_command():    
    data = request.json
    device_id = data.get('device_id')
    command = data.get('command')

    if not device_id or not command:
        return jsonify({"success": False, "code": 400, "msg": "Invalid request format"}), 400

    if device_id not in zigbee_controller.devices_data:
        return jsonify({"success": False, "code": 404, "msg": "Device not found"}), 404

    try:
        # Публикуем команду в MQTT-топик для конкретного устройства
        mqtt_topic = f'zigbee2mqtt/{device_id}/set'
        zigbee_controller.mqtt_client.publish(mqtt_topic, json.dumps(command))
        return jsonify({"success": True, "msg": "Command sent successfully"}), 200
    except Exception as e:
        logging.error(f"Failed to send command to Zigbee device {device_id}: {e}")
        return jsonify({"success": False, "code": 500, "msg": "Internal server error"}), 500


@app.route('/zigbee/devices', methods=['GET'])
@swag_from({
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': [
                        {'id': 'device_1', 'name': 'Zigbee Device 1'},
                        {'id': 'device_2', 'name': 'Zigbee Device 2'},
                    ]
                }
            }
        },
        500: {
            'description': 'Внутренняя ошибка сервера',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 500,
                    'msg': 'Internal server error'
                }
            }
        }
    }
})
def handle_get_zigbee_devices():
    try:
        devices = zigbee_controller.get_zigbee_devices()
        return jsonify({"success": True, "result": devices}), 200
    except Exception as e:
        logging.error(f"Failed to retrieve Zigbee devices: {e}")
        return jsonify({"success": False, "code": 500, "msg": "Internal server error"}), 500


@app.route('/zigbee/device/status', methods=['GET'])
@swag_from({
    'parameters': [
        {
            'name': 'device_id',
            'in': 'query',
            'type': 'string',
            'required': True,
            'description': 'ID устройства Zigbee, для которого нужно получить статус'
        }
    ],
    'responses': {
        200: {
            'description': 'Успешный ответ',
            'examples': {
                'application/json': {
                    'success': True,
                    'result': {
                        'id': 'device_1', 
                        'status': {
                            "battery": 100,
                            "humidity": 50.01,
                            "linkquality": 248,
                            "temperature": 26.17,
                            "voltage": 3000
                        }
                    }
                }
            }
        },
        400: {
            'description': 'Некорректный запрос',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 400,
                    'msg': 'Device ID is required'
                }
            }
        },
        404: {
            'description': 'Устройство не найдено',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 404,
                    'msg': 'Device not found'
                }
            }
        },
        500: {
            'description': 'Внутренняя ошибка сервера',
            'examples': {
                'application/json': {
                    'success': False,
                    'code': 500,
                    'msg': 'Internal server error'
                }
            }
        }
    }
})
def handle_get_zigbee_device_status():
    device_id = request.args.get('device_id')
    if not device_id:
        logging.error("Device ID not provided in request")
        return jsonify({"success": False, "code": 400, "msg": "Device ID is required"}), 400

    try:
        status = zigbee_controller.get_zigbee_device_status(device_id)
        if status['status'] == 'Device not found':
            return jsonify({"success": False, "code": 404, "msg": "Device not found"}), 404
        elif status['status'] == 'Status not yet updated':
            return jsonify({"success": True, "result": status, "msg": "Status not yet updated"}), 200
        return jsonify({"success": True, "result": status}), 200
    except Exception as e:
        logging.error(f"Failed to retrieve status for Zigbee device {device_id}: {e}")
        return jsonify({"success": False, "code": 500, "msg": "Internal server error"}), 500



# Инициализация контроллера Zigbee
zigbee_controller = ZigbeeController()

def start_zigbee():
    """Функция для запуска Zigbee2MQTT."""
    zigbee_controller.start_zigbee2mqtt()
    logging.info("Zigbee2MQTT запущен.")

def main():
    """Функция для запуска Flask-приложения и Zigbee2MQTT."""
    # Запуск Zigbee2MQTT в отдельном потоке
    zigbee_thread = threading.Thread(target=start_zigbee, daemon=True)
    zigbee_thread.start()

    # Запуск Flask-сервера
    try:
        app.run(debug=True, use_reloader=False)  # use_reloader=False, чтобы избежать двойного запуска
    except Exception as e:
        logging.error(f"Failed to start Flask application: {e}")

if __name__ == '__main__':
    main()
import paho.mqtt.client as mqtt
from util import *


# Конфигурация
MQTT_BROKER = 'localhost'
MQTT_PORT = 1883
MQTT_TOPIC = 'zigbee2mqtt/#'
ZIGBEE2MQTT_DIR = 'zigbee2mqtt'

class Device:
    """Класс для хранения данных об устройстве."""
    def __init__(self, name, address, data_json):
        self.name = name
        self.address = address
        self.data_json = data_json
        self.status_json = ""

    def update_status(self, new_status_json):
        """Обновляет статус устройства."""
        try:
            new_status = json.loads(new_status_json)
            
            # Если статус еще не инициализирован (пустая строка), устанавливаем новый статус
            if not self.status_json:
                current_status = {}
            else:
                current_status = json.loads(self.status_json)
            
            # Объединяем текущий статус и новый статус, обновляя значения
            current_status.update(new_status)
            self.status_json = json.dumps(current_status)
        except json.JSONDecodeError:
            log(f"Ошибка декодирования JSON при обновлении статуса устройства {self.name}")

    def __repr__(self):
        return f"Device(name={self.name}, address={self.address}, status_json={self.status_json})"

class ZigbeeController:
    """Класс для управления Zigbee2MQTT и MQTT-клиентом."""
    
    def __init__(self):
        self.mqtt_client = mqtt.Client()
        self.mqtt_client.on_connect = self.on_connect
        self.mqtt_client.on_message = self.on_message        
        self.devices_data = {}

    def start_zigbee2mqtt(self):
        """Запуск Zigbee2MQTT."""
        log("Запуск Zigbee2MQTT...")
        current_dir = os.getcwd()
        zigbee2mqtt_dir = os.path.join(current_dir, ZIGBEE2MQTT_DIR)

        if not os.path.exists(zigbee2mqtt_dir):
            log(f"Ошибка: Директория {zigbee2mqtt_dir} не найдена.")
            return

        process = subprocess.Popen(
            ['node', 'index.js'],
            cwd=zigbee2mqtt_dir,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            bufsize=1
        )

        threading.Thread(target=self.read_process_output, args=(process,), daemon=True).start()

    def connect_mqtt(self):
        """Подключение к MQTT-брокеру."""
        log("Подключение к MQTT...")
        try:
            self.mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
            self.mqtt_client.loop_start()
        except Exception as e:
            log(f"Ошибка при подключении к MQTT: {str(e)}")

    def on_connect(self, client, userdata, flags, rc):
        """Обработчик события подключения к MQTT."""
        if rc == 0:
            log("Подключение к MQTT прошло успешно.")
            client.subscribe(MQTT_TOPIC)
        else:
            log(f"Ошибка подключения к MQTT: {rc}")

    def on_message(self, client, userdata, message):
        """Обработчик сообщений от MQTT-брокера."""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        payload = message.payload.decode()
        log_message = f'[{timestamp}] Topic: {message.topic}, Payload: {payload}'

        # Список топиков, которые нужно исключить из логов
        excluded_topics = [
            "zigbee2mqtt/bridge/definitions",
            "zigbee2mqtt/bridge/devices",
            "zigbee2mqtt/bridge/info"
        ]

        if message.topic not in excluded_topics:
            log(log_message)

        # Обработка сообщений в зависимости от топика
        if message.topic == "zigbee2mqtt/bridge/devices":
            self.handle_devices_message(payload)
        elif message.topic == "zigbee2mqtt/bridge/logging":
            self.handle_logging_message(payload)

    def handle_devices_message(self, payload):
        """Обработка сообщений с топика 'zigbee2mqtt/bridge/devices'."""
        try:
            devices = json.loads(payload)
            for device_data in devices:
                ieee_address = device_data.get('ieee_address')            
                friendly_name = self.get_friendly_name(device_data)
                data_json = json.dumps(device_data)

                # Создаем объект Device и сохраняем в словарь
                device = Device(name=friendly_name, address=ieee_address, data_json=data_json)
                self.devices_data[ieee_address] = device

                log(f"Устройство добавлено: {device}")
        except json.JSONDecodeError:
            log(f"Ошибка декодирования JSON: {payload}")

    def get_friendly_name(self, device):
        """Получает удобное имя устройства из информации о нем."""
        definition = device.get('definition', {})
        if definition:
            return definition.get('description', 'No description')
        return device.get('friendly_name', 'Unknown Device')

    def handle_logging_message(self, payload):
        """Обработка сообщений с топика 'zigbee2mqtt/bridge/logging'."""
        try:
            data = json.loads(payload)
            if "message" in data:
                message = data["message"]
                if "MQTT publish" in message:
                    parts = message.split("'")
                    if len(parts) >= 4:
                        topic = parts[1]
                        status_json_str = parts[3]

                        # Парсинг JSON из строки с данными устройства
                        status_json = json.loads(status_json_str)

                        # Проверка, является ли топик связным с устройством
                        if topic.startswith("zigbee2mqtt/") and not topic.endswith("/bridge/logging"):
                            ieee_address = topic.split('/')[-1]

                            # Обновляем статус устройства, если оно уже зарегистрировано
                            if ieee_address in self.devices_data:
                                device = self.devices_data[ieee_address]
                                device.update_status(json.dumps(status_json))
                                log(f"Статус устройства обновлен: {device}")
                            else:
                                log(f"Устройство с адресом {ieee_address} не найдено.")
        except json.JSONDecodeError:
            log(f"Ошибка декодирования JSON: {payload}")

    def read_process_output(self, process):
        """Чтение вывода процесса Zigbee2MQTT."""
        for line in process.stdout:
            log(line.strip())
            if "MQTT started!" in line:
                self.connect_mqtt()

    
    def get_zigbee_devices(self):
        """Возвращает список устройств Zigbee."""
        devices = []
        for ieee_address, device in self.devices_data.items():
            devices.append({'id': ieee_address, 'name': device.name})
        return devices

    def get_zigbee_device_status(self, device_id):
        """Возвращает статус устройства Zigbee по его ID."""
        device = self.devices_data.get(device_id)
        
        if device:
            # Проверяем, есть ли в status_json данные и не пустые ли они
            if device.status_json:
                try:
                    status = json.loads(device.status_json)
                    if not status:
                        return {'id': device_id, 'status': 'Status not yet updated'}
                    return {'id': device_id, 'status': status}
                except json.JSONDecodeError:
                    # Если JSON не удается декодировать, возвращаем сообщение об ошибке
                    return {'id': device_id, 'status': 'Invalid JSON format'}
            else:
                # Если нет данных в status_json
                return {'id': device_id, 'status': 'Status not yet updated'}
        
        return {'id': device_id, 'status': 'Device not found'}



# # Инициализация и запуск контроллера Zigbee
# zigbee_controller = ZigbeeController()
# zigbee_controller.start_zigbee2mqtt()

# completion_event = threading.Event()
# completion_event.wait()from datetime import datetime
import json
import subprocess
import threading
import os
import asyncio

def log(message, session="server", file_path="log.txt"):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    log_message = f"{timestamp} - {session} - {message}"
    print(log_message)
    with open(file_path, 'a', encoding='utf-8') as log_file:
        log_file.write(log_message + "\n")

    
def read_process_output(process, on_start_callback):
    for line in iter(process.stdout.readline, ''):
        log(line.strip(), "Node.js")
        if "Zigbee2MQTT started!" in line:
            log("Zigbee2MQTT запущен.")
            on_start_callback()