Add zeroconf discovery
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add zeroconf/mDNS discovery
|
||||||
- Support openWakeWord
|
- Support openWakeWord
|
||||||
- Support multiple wake words
|
- Support multiple wake words
|
||||||
- Save active wake words to preferences JSON file
|
- Save active wake words to preferences JSON file
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .mpv_player import MpvMediaPlayer
|
|||||||
from .openwakeword import OpenWakeWord, OpenWakeWordFeatures
|
from .openwakeword import OpenWakeWord, OpenWakeWordFeatures
|
||||||
from .satellite import VoiceSatelliteProtocol
|
from .satellite import VoiceSatelliteProtocol
|
||||||
from .util import get_mac, is_arm
|
from .util import get_mac, is_arm
|
||||||
|
from .zeroconf import HomeAssistantZeroconf
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_MODULE_DIR = Path(__file__).parent
|
_MODULE_DIR = Path(__file__).parent
|
||||||
@@ -204,6 +205,10 @@ async def main() -> None:
|
|||||||
lambda: VoiceSatelliteProtocol(state), host=args.host, port=args.port
|
lambda: VoiceSatelliteProtocol(state), host=args.host, port=args.port
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Auto discovery (zeroconf, mDNS)
|
||||||
|
discovery = HomeAssistantZeroconf(port=args.port, name=args.name)
|
||||||
|
await discovery.register_server()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("Opening audio input device: %s", args.audio_input_device)
|
_LOGGER.debug("Opening audio input device: %s", args.audio_input_device)
|
||||||
with sd.RawInputStream(
|
with sd.RawInputStream(
|
||||||
@@ -254,7 +259,7 @@ def process_audio(state: ServerState):
|
|||||||
if (not wake_words) or (state.wake_words_changed and state.wake_words):
|
if (not wake_words) or (state.wake_words_changed and state.wake_words):
|
||||||
# Update list of wake word models to process
|
# Update list of wake word models to process
|
||||||
state.wake_words_changed = False
|
state.wake_words_changed = False
|
||||||
wake_words = list(state.wake_words.values())
|
wake_words = [ww for ww in state.wake_words.values() if ww.is_active]
|
||||||
|
|
||||||
has_oww = False
|
has_oww = False
|
||||||
for wake_word in wake_words:
|
for wake_word in wake_words:
|
||||||
@@ -286,13 +291,10 @@ def process_audio(state: ServerState):
|
|||||||
oww_inputs.extend(oww_features.process_streaming(audio_chunk))
|
oww_inputs.extend(oww_features.process_streaming(audio_chunk))
|
||||||
|
|
||||||
for wake_word in wake_words:
|
for wake_word in wake_words:
|
||||||
if not wake_word.is_active:
|
|
||||||
continue
|
|
||||||
|
|
||||||
activated = False
|
activated = False
|
||||||
if isinstance(wake_word, MicroWakeWord):
|
if isinstance(wake_word, MicroWakeWord):
|
||||||
for oww_input in oww_inputs:
|
for micro_input in micro_inputs:
|
||||||
if wake_word.process_streaming(oww_input):
|
if wake_word.process_streaming(micro_input):
|
||||||
activated = True
|
activated = True
|
||||||
elif isinstance(wake_word, OpenWakeWord):
|
elif isinstance(wake_word, OpenWakeWord):
|
||||||
for oww_input in oww_inputs:
|
for oww_input in oww_inputs:
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ class VoiceSatelliteProtocol(APIServer):
|
|||||||
)
|
)
|
||||||
for ww in self.state.available_wake_words.values()
|
for ww in self.state.available_wake_words.values()
|
||||||
],
|
],
|
||||||
active_wake_words=list(self.state.wake_words.keys()),
|
active_wake_words=[
|
||||||
|
ww.id for ww in self.state.wake_words.values() if ww.is_active
|
||||||
|
],
|
||||||
max_active_wake_words=2,
|
max_active_wake_words=2,
|
||||||
)
|
)
|
||||||
_LOGGER.info("Connected to Home Assistant")
|
_LOGGER.info("Connected to Home Assistant")
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Runs mDNS zeroconf service for Home Assistant discovery."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import uuid
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.fatal("pip install zeroconf")
|
||||||
|
raise
|
||||||
|
|
||||||
|
MDNS_TARGET_IP = "224.0.0.251"
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantZeroconf:
|
||||||
|
def __init__(
|
||||||
|
self, port: int, name: Optional[str] = None, host: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
self.port = port
|
||||||
|
self.name = name or _get_mac_address()
|
||||||
|
|
||||||
|
if not host:
|
||||||
|
test_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
test_sock.setblocking(False)
|
||||||
|
test_sock.connect((MDNS_TARGET_IP, 1))
|
||||||
|
host = test_sock.getsockname()[0]
|
||||||
|
_LOGGER.debug("Detected IP: %s", host)
|
||||||
|
|
||||||
|
assert host
|
||||||
|
self.host = host
|
||||||
|
self._aiozc = AsyncZeroconf()
|
||||||
|
|
||||||
|
async def register_server(self) -> None:
|
||||||
|
|
||||||
|
service_info = AsyncServiceInfo(
|
||||||
|
"_esphomelib._tcp.local.",
|
||||||
|
f"{self.name}._esphomelib._tcp.local.",
|
||||||
|
addresses=[socket.inet_aton(self.host)],
|
||||||
|
port=self.port,
|
||||||
|
properties={
|
||||||
|
"version": "2025.9.0",
|
||||||
|
"mac": _get_mac_address(),
|
||||||
|
"board": "host",
|
||||||
|
"platform": "HOST",
|
||||||
|
"network": "ethernet", # or "wifi"
|
||||||
|
},
|
||||||
|
server=f"{self.name}.local.",
|
||||||
|
)
|
||||||
|
await self._aiozc.async_register_service(service_info)
|
||||||
|
_LOGGER.debug("Zeroconf discovery enabled: %s", service_info)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_mac_address() -> str:
|
||||||
|
"""Return MAC address formatted as hex with no colons."""
|
||||||
|
return "".join(
|
||||||
|
# pylint: disable=consider-using-f-string
|
||||||
|
["{:02x}".format((uuid.getnode() >> ele) & 0xFF) for ele in range(0, 8 * 6, 8)][
|
||||||
|
::-1
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -30,6 +30,7 @@ dependencies = [
|
|||||||
"numpy>=2,<3",
|
"numpy>=2,<3",
|
||||||
"pymicro-features==1.0.0",
|
"pymicro-features==1.0.0",
|
||||||
"python-mpv>=1,<2",
|
"python-mpv>=1,<2",
|
||||||
|
"zeroconf",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user