mirror of
https://github.com/esphome/esphome.git
synced 2025-09-03 20:02:22 +01:00
Dashboard node import and render in browser (#2374)
This commit is contained in:
@@ -2,6 +2,8 @@ import socket
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Optional
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
from zeroconf import (
|
||||
DNSAddress,
|
||||
@@ -10,11 +12,14 @@ from zeroconf import (
|
||||
DNSQuestion,
|
||||
RecordUpdateListener,
|
||||
Zeroconf,
|
||||
ServiceBrowser,
|
||||
)
|
||||
from zeroconf._services import ServiceStateChange
|
||||
|
||||
_CLASS_IN = 1
|
||||
_FLAGS_QR_QUERY = 0x0000 # query
|
||||
_TYPE_A = 1
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostResolver(RecordUpdateListener):
|
||||
@@ -57,7 +62,7 @@ class HostResolver(RecordUpdateListener):
|
||||
return True
|
||||
|
||||
|
||||
class DashboardStatus(RecordUpdateListener, threading.Thread):
|
||||
class DashboardStatus(threading.Thread):
|
||||
PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds
|
||||
OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds
|
||||
|
||||
@@ -70,9 +75,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread):
|
||||
self.query_event = threading.Event()
|
||||
self.on_update = on_update
|
||||
|
||||
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
|
||||
pass
|
||||
|
||||
def request_query(self, hosts: Dict[str, str]) -> None:
|
||||
self.query_hosts = set(hosts.values())
|
||||
self.key_to_host = hosts
|
||||
@@ -93,7 +95,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread):
|
||||
)
|
||||
|
||||
def run(self) -> None:
|
||||
self.zc.add_listener(self, None)
|
||||
while not self.stop_event.is_set():
|
||||
self.on_update(
|
||||
{key: self.host_status(host) for key, host in self.key_to_host.items()}
|
||||
@@ -110,7 +111,75 @@ class DashboardStatus(RecordUpdateListener, threading.Thread):
|
||||
self.zc.send(out)
|
||||
self.query_event.wait()
|
||||
self.query_event.clear()
|
||||
self.zc.remove_listener(self)
|
||||
|
||||
|
||||
ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local."
|
||||
TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url"
|
||||
TXT_RECORD_PROJECT_NAME = b"project_name"
|
||||
TXT_RECORD_PROJECT_VERSION = b"project_version"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiscoveredImport:
|
||||
device_name: str
|
||||
package_import_url: str
|
||||
project_name: str
|
||||
project_version: str
|
||||
|
||||
|
||||
class DashboardImportDiscovery:
|
||||
def __init__(self, zc: Zeroconf) -> None:
|
||||
self.zc = zc
|
||||
self.service_browser = ServiceBrowser(
|
||||
self.zc, ESPHOME_SERVICE_TYPE, [self._on_update]
|
||||
)
|
||||
self.import_state = {}
|
||||
|
||||
def _on_update(
|
||||
self,
|
||||
zeroconf: Zeroconf,
|
||||
service_type: str,
|
||||
name: str,
|
||||
state_change: ServiceStateChange,
|
||||
) -> None:
|
||||
_LOGGER.debug(
|
||||
"service_update: type=%s name=%s state_change=%s",
|
||||
service_type,
|
||||
name,
|
||||
state_change,
|
||||
)
|
||||
if service_type != ESPHOME_SERVICE_TYPE:
|
||||
return
|
||||
if state_change == ServiceStateChange.Removed:
|
||||
self.import_state.pop(name, None)
|
||||
|
||||
info = zeroconf.get_service_info(service_type, name)
|
||||
_LOGGER.debug("-> resolved info: %s", info)
|
||||
if info is None:
|
||||
return
|
||||
node_name = name[: -len(ESPHOME_SERVICE_TYPE) - 1]
|
||||
required_keys = [
|
||||
TXT_RECORD_PACKAGE_IMPORT_URL,
|
||||
TXT_RECORD_PROJECT_NAME,
|
||||
TXT_RECORD_PROJECT_VERSION,
|
||||
]
|
||||
if any(key not in info.properties for key in required_keys):
|
||||
# Not a dashboard import device
|
||||
return
|
||||
|
||||
import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode()
|
||||
project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode()
|
||||
project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode()
|
||||
|
||||
self.import_state[name] = DiscoveredImport(
|
||||
device_name=node_name,
|
||||
package_import_url=import_url,
|
||||
project_name=project_name,
|
||||
project_version=project_version,
|
||||
)
|
||||
|
||||
def cancel(self) -> None:
|
||||
self.service_browser.cancel()
|
||||
|
||||
|
||||
class EsphomeZeroconf(Zeroconf):
|
||||
|
Reference in New Issue
Block a user