103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
import asyncio
|
||
import json
|
||
import socket
|
||
import logging
|
||
import subprocess
|
||
import ipaddress
|
||
from typing import List, Dict
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DiscoveryService:
|
||
def __init__(self, port: int = 38899):
|
||
self.port = port
|
||
self.discover_msg = {"method": "getPilot", "params": {}}
|
||
|
||
def _get_local_subnet(self) -> str:
|
||
try:
|
||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||
s.connect(("8.8.8.8", 80))
|
||
local_ip = s.getsockname()[0]
|
||
|
||
network = ipaddress.IPv4Network(f"{local_ip}/24", strict=False)
|
||
return str(network)
|
||
except Exception as e:
|
||
logger.error(f"Linux Discovery Error: Не удалось определить подсеть: {e}")
|
||
return "192.168.1.0/24"
|
||
|
||
async def scan_network(self, timeout: float = 1.5) -> List[Dict]:
|
||
subnet = self._get_local_subnet()
|
||
network = ipaddress.IPv4Network(subnet)
|
||
found_devices = []
|
||
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
sock.setblocking(False)
|
||
|
||
loop = asyncio.get_running_loop()
|
||
message = json.dumps(self.discover_msg).encode()
|
||
|
||
logger.info(f"🚀 Начинаю сканирование сети {subnet}...")
|
||
|
||
# Рассылаем запросы
|
||
for ip in network.hosts():
|
||
try:
|
||
sock.sendto(message, (str(ip), self.port))
|
||
except Exception:
|
||
continue
|
||
|
||
start_time = loop.time()
|
||
while (loop.time() - start_time) < timeout:
|
||
try:
|
||
data, addr = await asyncio.wait_for(
|
||
loop.run_in_executor(None, sock.recvfrom, 1024), timeout=0.1
|
||
)
|
||
|
||
resp = json.loads(data.decode())
|
||
if "result" in resp:
|
||
res = resp["result"]
|
||
mac = res.get("mac")
|
||
ip_from_socket = addr[0]
|
||
|
||
if mac:
|
||
device_info = {
|
||
"mac": mac,
|
||
"ip": ip_from_socket,
|
||
"state": {
|
||
"on": res.get("state"),
|
||
"dimming": res.get("dimming"),
|
||
"temp": res.get("temp"),
|
||
},
|
||
}
|
||
found_devices.append(device_info)
|
||
# Красивый лог с IP и MAC
|
||
logger.info(
|
||
f" [+] Найдена лампа: {ip_from_socket} | MAC: {mac}"
|
||
)
|
||
|
||
except (asyncio.TimeoutError, json.JSONDecodeError):
|
||
continue
|
||
except Exception as e:
|
||
# На Linux Errno 11 (EAGAIN) тут может выскочить, если буфер пуст
|
||
await asyncio.sleep(0.01)
|
||
continue
|
||
|
||
sock.close()
|
||
# Фильтруем дубликаты (если лампа ответила несколько раз)
|
||
unique_devices = list({d["mac"]: d for d in found_devices}.values())
|
||
return unique_devices
|
||
|
||
async def start_background_discovery(self, state_manager, interval=600):
|
||
"""Запускает бесконечный цикл сканирования."""
|
||
while True:
|
||
try:
|
||
found_devices = await self.scan_network()
|
||
for dev_data in found_devices:
|
||
state_manager.update_device(dev_data)
|
||
logger.info(
|
||
f"📡 Discovery: онлайн {len(state_manager.devices)} устройств"
|
||
)
|
||
except Exception as e:
|
||
print(f"❌ Discovery error: {e}")
|
||
await asyncio.sleep(interval)
|