Fix loglevel & discovery-subnet
This commit is contained in:
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import os
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
@@ -14,21 +14,33 @@ class DiscoveryService:
|
|||||||
self.port = port
|
self.port = port
|
||||||
self.discover_msg = {"method": "getPilot", "params": {}}
|
self.discover_msg = {"method": "getPilot", "params": {}}
|
||||||
|
|
||||||
def _get_local_subnet(self) -> str:
|
def _get_target_subnets(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
Определяет список подсетей для сканирования.
|
||||||
|
Приоритет:
|
||||||
|
1. Переменная окружения SCAN_NETWORK (можно через запятую: "192.168.0.0/24,192.168.1.0/24")
|
||||||
|
2. Автоопределение по дефолтному шлюзу
|
||||||
|
"""
|
||||||
|
env_network = os.getenv("SCAN_NETWORK")
|
||||||
|
if env_network:
|
||||||
|
return [s.strip() for s in env_network.split(",")]
|
||||||
|
|
||||||
|
# Автоопределение (твой старый метод)
|
||||||
try:
|
try:
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
# Коннект не создает трафика, но заставляет ОС выбрать нужный интерфейс
|
||||||
s.connect(("8.8.8.8", 80))
|
s.connect(("8.8.8.8", 80))
|
||||||
local_ip = s.getsockname()[0]
|
local_ip = s.getsockname()[0]
|
||||||
|
|
||||||
network = ipaddress.IPv4Network(f"{local_ip}/24", strict=False)
|
network = ipaddress.IPv4Network(f"{local_ip}/24", strict=False)
|
||||||
return str(network)
|
return [str(network)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Linux Discovery Error: Не удалось определить подсеть: {e}")
|
logger.error(
|
||||||
return "192.168.1.0/24"
|
f"Discovery Error: Не удалось определить подсеть автоматически: {e}"
|
||||||
|
)
|
||||||
|
return ["192.168.1.0/24"]
|
||||||
|
|
||||||
async def scan_network(self, timeout: float = 1.5) -> List[Dict]:
|
async def scan_network(self, timeout: float = 2.0) -> List[Dict]:
|
||||||
subnet = self._get_local_subnet()
|
subnets = self._get_target_subnets()
|
||||||
network = ipaddress.IPv4Network(subnet)
|
|
||||||
found_devices = []
|
found_devices = []
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
@@ -37,55 +49,56 @@ class DiscoveryService:
|
|||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
message = json.dumps(self.discover_msg).encode()
|
message = json.dumps(self.discover_msg).encode()
|
||||||
|
|
||||||
logger.info(f"🚀 Начинаю сканирование сети {subnet}...")
|
logger.debug(f"🚀 Начинаю сканирование сетей: {', '.join(subnets)}...")
|
||||||
|
|
||||||
# Рассылаем запросы
|
# Рассылаем запросы по всем целевым сетям
|
||||||
|
for subnet in subnets:
|
||||||
|
try:
|
||||||
|
network = ipaddress.IPv4Network(subnet)
|
||||||
for ip in network.hosts():
|
for ip in network.hosts():
|
||||||
try:
|
try:
|
||||||
sock.sendto(message, (str(ip), self.port))
|
sock.sendto(message, (str(ip), self.port))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"❌ Неверный формат подсети {subnet}: {e}")
|
||||||
|
|
||||||
|
# Собираем ответы
|
||||||
start_time = loop.time()
|
start_time = loop.time()
|
||||||
while (loop.time() - start_time) < timeout:
|
while (loop.time() - start_time) < timeout:
|
||||||
try:
|
try:
|
||||||
|
# Используем небольшой таймаут на чтение, чтобы успевать выходить из цикла
|
||||||
data, addr = await asyncio.wait_for(
|
data, addr = await asyncio.wait_for(
|
||||||
loop.run_in_executor(None, sock.recvfrom, 1024), timeout=0.1
|
loop.run_in_executor(None, sock.recvfrom, 1024), timeout=0.2
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = json.loads(data.decode())
|
resp = json.loads(data.decode())
|
||||||
if "result" in resp:
|
if "result" in resp:
|
||||||
res = resp["result"]
|
res = resp["result"]
|
||||||
mac = res.get("mac")
|
mac = res.get("mac")
|
||||||
ip_from_socket = addr[0]
|
|
||||||
|
|
||||||
if mac:
|
if mac:
|
||||||
device_info = {
|
found_devices.append(
|
||||||
|
{
|
||||||
"mac": mac,
|
"mac": mac,
|
||||||
"ip": ip_from_socket,
|
"ip": addr[0],
|
||||||
"state": {
|
"state": {
|
||||||
"on": res.get("state"),
|
"on": res.get("state"),
|
||||||
"dimming": res.get("dimming"),
|
"dimming": res.get("dimming"),
|
||||||
"temp": res.get("temp"),
|
"temp": res.get("temp"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
found_devices.append(device_info)
|
|
||||||
# Красивый лог с IP и MAC
|
|
||||||
logger.info(
|
|
||||||
f" [+] Найдена лампа: {ip_from_socket} | MAC: {mac}"
|
|
||||||
)
|
)
|
||||||
|
logger.info(f" [+] Найдена лампа: {addr[0]} | MAC: {mac}")
|
||||||
|
|
||||||
except (asyncio.TimeoutError, json.JSONDecodeError):
|
except (asyncio.TimeoutError, json.JSONDecodeError):
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# На Linux Errno 11 (EAGAIN) тут может выскочить, если буфер пуст
|
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sock.close()
|
sock.close()
|
||||||
# Фильтруем дубликаты (если лампа ответила несколько раз)
|
# Фильтруем дубликаты
|
||||||
unique_devices = list({d["mac"]: d for d in found_devices}.values())
|
return list({d["mac"]: d for d in found_devices}.values())
|
||||||
return unique_devices
|
|
||||||
|
|
||||||
async def start_background_discovery(self, state_manager, interval=600):
|
async def start_background_discovery(self, state_manager, interval=600):
|
||||||
"""Запускает бесконечный цикл сканирования."""
|
"""Запускает бесконечный цикл сканирования."""
|
||||||
@@ -98,5 +111,5 @@ class DiscoveryService:
|
|||||||
f"📡 Discovery: онлайн {len(state_manager.devices)} устройств"
|
f"📡 Discovery: онлайн {len(state_manager.devices)} устройств"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Discovery error: {e}")
|
logger.error(f"❌ Discovery background error: {e}")
|
||||||
await asyncio.sleep(interval)
|
await asyncio.sleep(interval)
|
||||||
|
|||||||
6
main.py
6
main.py
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
@@ -11,10 +12,9 @@ from sqlalchemy import select
|
|||||||
from app.models.device import GroupModel
|
from app.models.device import GroupModel
|
||||||
from app.api.routes import devices, control, schedules
|
from app.api.routes import devices, control, schedules
|
||||||
|
|
||||||
|
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(level=LOG_LEVEL, format="%(asctime)s | %(levelname)s | %(message)s")
|
||||||
level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s"
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user