aioesphomeapi/aioesphomeapi/host_resolver.py

98 lines
2.8 KiB
Python

import socket
import time
from typing import Optional
import zeroconf
class HostResolver(zeroconf.RecordUpdateListener):
def __init__(self, name: str):
self.name = name
self.address: Optional[bytes] = None
def update_record(
self, zc: zeroconf.Zeroconf, now: float, record: zeroconf.DNSRecord
) -> None:
if record is None:
return
if record.type == zeroconf._TYPE_A:
assert isinstance(record, zeroconf.DNSAddress)
if record.name == self.name:
self.address = record.address
def request(self, zc: zeroconf.Zeroconf, timeout: float) -> bool:
now = time.time()
delay = 0.2
next_ = now + delay
last = now + timeout
try:
zc.add_listener(
self,
zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY, zeroconf._CLASS_IN),
)
while self.address is None:
if last <= now:
# Timeout
return False
if next_ <= now:
out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY)
out.add_question(
zeroconf.DNSQuestion(
self.name, zeroconf._TYPE_A, zeroconf._CLASS_IN
)
)
out.add_answer_at_time(
zc.cache.get_by_details(
self.name, zeroconf._TYPE_A, zeroconf._CLASS_IN
),
now,
)
zc.send(out)
next_ = now + delay
delay *= 2
zc.wait(min(next_, last) - now)
now = time.time()
finally:
zc.remove_listener(self)
return True
def resolve_host(
host: str,
timeout: float = 3.0,
zeroconf_instance: Optional[zeroconf.Zeroconf] = None,
) -> str:
from aioesphomeapi.core import APIConnectionError
try:
zc = zeroconf_instance or zeroconf.Zeroconf()
except Exception:
raise APIConnectionError(
"Cannot start mDNS sockets, is this a docker container without "
"host network mode?"
)
try:
info = HostResolver(host + ".")
assert info.address is not None
address = None
if info.request(zc, timeout):
address = socket.inet_ntoa(info.address)
except Exception as err:
raise APIConnectionError(
"Error resolving mDNS hostname: {}".format(err)
) from err
finally:
if not zeroconf_instance:
zc.close()
if address is None:
raise APIConnectionError(
"Error resolving address with mDNS: Did not respond. "
"Maybe the device is offline."
)
return address