hotfix for mac-detection and various variable names (#139)
* Change Client-Name: The client name inside LVA is not capable of having spaces in the name. Fixed the example. * Add first version of Docker-Compose for developers * Add variable for network-interface and auto detection by default * Added autodetection for the MAC-Address based on the detected or specified network-interface. * Switched from listening on all IP-Addresses to the IP of the default interface, usually u set the correct network-interface and it will automatically detect the right IP-Address. * Added missing variables, fixed variables names * Fixed not working variables for example sound-files. * Moved the auto-name creation from shell to python. The name is no longer mandatory, it will be generated if no name is given.
This commit is contained in:
1 parent
f72e5c8d30
commit
4052daae01
11 files changed
+255
-112
No files matched your search
+11
-3
@@ -14,8 +14,7 @@ LVA_USER_ID="1000"
|
||||
LVA_USER_GROUP="1000"
|
||||
|
||||
### Name for the client (optional):
|
||||
# by default it uses the HOSTNAME variable from the piCompose environment which includes the MAC from the network card
|
||||
# CLIENT_NAME="My Voice Assistant Speaker"
|
||||
# CLIENT_NAME="speaker_livvingroom"
|
||||
|
||||
### PulseAudio socket path on the host:
|
||||
# PulseAudio Server: /run/user/1000/pulse
|
||||
@@ -26,6 +25,12 @@ LVA_XDG_RUNTIME_DIR="/run/user/${LVA_USER_ID}"
|
||||
### Path to the preferences file (optional):
|
||||
# PREFERENCES_FILE="/app/configuration/preferences.json"
|
||||
|
||||
### Network interface (optional):
|
||||
# NETWORK_INTERFACE="eth0"
|
||||
|
||||
### IP-Address for the api (optional):
|
||||
# HOST="0.0.0.0"
|
||||
|
||||
### Port for the api (optional):
|
||||
# PORT="6053"
|
||||
|
||||
@@ -39,7 +44,10 @@ LVA_XDG_RUNTIME_DIR="/run/user/${LVA_USER_ID}"
|
||||
# ENABLE_THINKING_SOUND="1"
|
||||
|
||||
# Wake model (optional):
|
||||
# WAKE-MODEL="okay_nabu"
|
||||
# WAKE_MODEL="okay_nabu"
|
||||
|
||||
# Stop model (optional):
|
||||
# STOP_MODEL="stop"
|
||||
|
||||
# Refactory seconds (optional):
|
||||
# REFACTORY_SECONDS="2"
|
||||
|
||||
@@ -39,6 +39,7 @@ apt-get install --yes --no-install-recommends \
|
||||
libasound2-plugins \
|
||||
ca-certificates \
|
||||
iproute2 \
|
||||
vim \
|
||||
procps && \
|
||||
apt-get clean
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ usage: __main__.py [-h] [--name NAME] [--audio-input-device AUDIO_INPUT_DEVICE]
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `--name` | Name of the voice assistant device (required) | - |
|
||||
| `--name` | Name of the voice assistant device (required) | Autogenerated (`lva-MAC-ADDRESS`) |
|
||||
| `--audio-input-device` | Soundcard name for input device | System default microphone |
|
||||
| `--audio-input-block-size` | Audio input block size in samples | 1024 |
|
||||
| `--audio-output-device` | mpv name for output device | System default speaker |
|
||||
@@ -62,7 +62,8 @@ usage: __main__.py [-h] [--name NAME] [--audio-input-device AUDIO_INPUT_DEVICE]
|
||||
| `--mute-sound` | Sound played when muting the assistant | `sounds/mute_switch_on.flac` |
|
||||
| `--unmute-sound` | Sound played when unmuting the assistant | `sounds/mute_switch_off.flac` |
|
||||
| `--preferences-file` | Path to preferences JSON file | `preferences.json` |
|
||||
| `--host` | Address for ESPHome server | `0.0.0.0` |
|
||||
| `--host` | IP-Address for ESPHome server, use 0.0.0.0 for all | Autodetected |
|
||||
| `--network-interface` | Network interface for ESPHome server | Autodetected |
|
||||
| `--port` | Port for ESPHome server | 6053 |
|
||||
| `--enable-thinking-sound` | Enable thinking sound on startup | False |
|
||||
| `--debug` | Print DEBUG messages to console | False |
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
services:
|
||||
# Fix permissions for wakeword_data and configuration volume
|
||||
fix-permissions:
|
||||
image: "ghcr.io/ohf-voice/linux-voice-assistant:latest"
|
||||
entrypoint: []
|
||||
command: "chown -R ${LVA_USER_ID}:${LVA_USER_GROUP} /app/local /app/configuration"
|
||||
env_file: .env
|
||||
group_add:
|
||||
- audio
|
||||
volumes:
|
||||
# Wakeword data and configuration
|
||||
- wakeword_data:/app/local
|
||||
- configuration:/app/configuration
|
||||
restart: "no"
|
||||
|
||||
# Linux Voice Assistant
|
||||
linux-voice-assistant:
|
||||
container_name: linux-voice-assistant
|
||||
image: "ghcr.io/ohf-voice/linux-voice-assistant:latest"
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
user: "${LVA_USER_ID}:${LVA_USER_GROUP}"
|
||||
environment:
|
||||
# Pulseaudio
|
||||
- XDG_RUNTIME_DIR=${LVA_XDG_RUNTIME_DIR}
|
||||
- PULSE_SERVER=${LVA_PULSE_SERVER}
|
||||
env_file: .env
|
||||
group_add:
|
||||
- audio
|
||||
# Priority for audio process
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
volumes:
|
||||
# Wakeword data and configuration
|
||||
- wakeword_data:/app/local
|
||||
- configuration:/app/configuration
|
||||
# Time and zone
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
# Pulseaudio
|
||||
# /run/user/1000:/run/user/1000
|
||||
- ${LVA_XDG_RUNTIME_DIR}:${LVA_XDG_RUNTIME_DIR}
|
||||
# Mount source code for development
|
||||
- ./linux_voice_assistant:/app/linux_voice_assistant
|
||||
depends_on:
|
||||
- fix-permissions
|
||||
# Health check for process
|
||||
healthcheck:
|
||||
test: ["CMD", "pgrep", "-f", "linux_voice_assistant"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
#command:
|
||||
# - --additional-parameters-here
|
||||
# - --but-use-env-if-possible
|
||||
|
||||
|
||||
volumes:
|
||||
wakeword_data:
|
||||
configuration:
|
||||
+45
-28
@@ -1,31 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
### Generate name for this client
|
||||
# Get active interface
|
||||
IFACE=$(ip route | grep default | awk '{print $5}' | head -n 1)
|
||||
|
||||
# Check if IFACE is empty
|
||||
if [ -z "$IFACE" ]; then
|
||||
echo "No active network interface found."
|
||||
fi
|
||||
|
||||
# Check if interface directory exists
|
||||
if [ ! -e "/sys/class/net/$IFACE/address" ]; then
|
||||
echo "Interface $IFACE does not exist or has no MAC address."
|
||||
fi
|
||||
|
||||
# Read MAC address and remove ':'
|
||||
MAC=$(cat /sys/class/net/$IFACE/address | tr -d ':')
|
||||
|
||||
if [ -z "$MAC" ]; then
|
||||
echo "Could not read MAC address."
|
||||
fi
|
||||
|
||||
# Generate CLIENT_NAME from it
|
||||
TEMP_CLIENT_NAME="lva-${MAC}"
|
||||
|
||||
|
||||
### Handlers
|
||||
# Handle parameters
|
||||
EXTRA_ARGS=()
|
||||
@@ -34,7 +9,6 @@ if [ "$ENABLE_DEBUG" = "1" ]; then
|
||||
EXTRA_ARGS+=( "--debug" )
|
||||
fi
|
||||
|
||||
CLIENT_NAME=${CLIENT_NAME:-$TEMP_CLIENT_NAME}
|
||||
if [ -n "${CLIENT_NAME}" ]; then
|
||||
EXTRA_ARGS+=( "--name" "$CLIENT_NAME" )
|
||||
fi
|
||||
@@ -44,6 +18,15 @@ if [ -n "${PREFERENCES_FILE}" ]; then
|
||||
EXTRA_ARGS+=( "--preferences-file" "$PREFERENCES_FILE" )
|
||||
fi
|
||||
|
||||
if [ -n "${NETWORK_INTERFACE}" ]; then
|
||||
EXTRA_ARGS+=( "--network-interface" "$NETWORK_INTERFACE" )
|
||||
fi
|
||||
|
||||
# IP-ADDRESS
|
||||
if [ -n "${HOST}" ]; then
|
||||
EXTRA_ARGS+=( "--host" "$HOST" )
|
||||
fi
|
||||
|
||||
PORT=${PORT:-6053}
|
||||
if [ -n "${PORT}" ]; then
|
||||
EXTRA_ARGS+=( "--port" "$PORT" )
|
||||
@@ -61,6 +44,38 @@ if [ "$ENABLE_THINKING_SOUND" = "1" ]; then
|
||||
EXTRA_ARGS+=( "--enable-thinking-sound" )
|
||||
fi
|
||||
|
||||
if [ -n "${WAKE_MODEL}" ]; then
|
||||
EXTRA_ARGS+=( "--wake-model" "$WAKE_MODEL" )
|
||||
fi
|
||||
|
||||
if [ -n "${STOP_MODEL}" ]; then
|
||||
EXTRA_ARGS+=( "--stop-model" "$STOP_MODEL" )
|
||||
fi
|
||||
|
||||
if [ -n "${REFACTORY_SECONDS}" ]; then
|
||||
EXTRA_ARGS+=( "--refractory-seconds" "$REFACTORY_SECONDS" )
|
||||
fi
|
||||
|
||||
if [ -n "${WAKEUP_SOUND}" ]; then
|
||||
EXTRA_ARGS+=( "--wakeup-sound" "$WAKEUP_SOUND" )
|
||||
fi
|
||||
|
||||
if [ -n "${TIMER_FINISHED_SOUND}" ]; then
|
||||
EXTRA_ARGS+=( "--timer-finished-sound" "$TIMER_FINISHED_SOUND" )
|
||||
fi
|
||||
|
||||
if [ -n "${PROCESSING_SOUND}" ]; then
|
||||
EXTRA_ARGS+=( "--processing-sound" "$PROCESSING_SOUND" )
|
||||
fi
|
||||
|
||||
if [ -n "${MUTE_SOUND}" ]; then
|
||||
EXTRA_ARGS+=( "--mute-sound" "$MUTE_SOUND" )
|
||||
fi
|
||||
|
||||
if [ -n "${UNMUTE_SOUND}" ]; then
|
||||
EXTRA_ARGS+=( "--unmute-sound" "$UNMUTE_SOUND" )
|
||||
fi
|
||||
|
||||
|
||||
### Wait for PulseAudio
|
||||
# Wait for PulseAudio to be available before starting the application
|
||||
@@ -113,7 +128,9 @@ if [ "$LIST_DEVICES" = "1" ]; then
|
||||
./script/run "$@" "${EXTRA_ARGS[@]}" --list-input-devices
|
||||
echo "list output devices"
|
||||
./script/run "$@" "${EXTRA_ARGS[@]}" --list-output-devices
|
||||
else
|
||||
echo "wait 20s and then starting the application"
|
||||
sleep 20
|
||||
fi
|
||||
|
||||
echo "starting application"
|
||||
exec ./script/run "$@" "${EXTRA_ARGS[@]}"
|
||||
fi
|
||||
+16
-30
@@ -88,29 +88,11 @@ LVA_XDG_RUNTIME_DIR="/run/user/${USERLVA_USER_ID
|
||||
# PORT="6053"
|
||||
|
||||
### Audio input device (optional):
|
||||
# AUDIO_INPUT_DEVICE="default"
|
||||
|
||||
### Audio output device (optional):
|
||||
# AUDIO_OUTPUT_DEVICE="default"
|
||||
|
||||
# Enable thinking sound (optional):
|
||||
# ENABLE_THINKING_SOUND="1"
|
||||
|
||||
# Wake model (optional):
|
||||
# WAKE-MODEL="okay_nabu"
|
||||
|
||||
# Refactory seconds (optional):
|
||||
# REFACTORY_SECONDS="2"
|
||||
|
||||
# Sound files (optional):
|
||||
# WAKEUP_SOUND="sounds/wake_word_triggered.flac"
|
||||
# TIMER_FINISHED_SOUND="sounds/timer_finished.flac"
|
||||
# PROCESSING_SOUND="sounds/processing.wav"
|
||||
# MUTE_SOUND="sounds/mute_switch_on.flac"
|
||||
# UNMUTE_SOUND="sounds/mute_switch_off.flac"
|
||||
[...]
|
||||
```
|
||||
|
||||
You can change various settings here, for example the audio sounds which are played when the wake word is detected or when the timer is finished.
|
||||
Please use the original file from the repository. You can change various settings, for example the audio sounds which are played when the wake word is detected or when the timer is finished.
|
||||
|
||||
💡 **Note:** You can exit vim with `:wq` or `:q!` if you dont want to save the changes.
|
||||
|
||||
@@ -206,13 +188,15 @@ WorkingDirectory=/home/pi/linux-voice-assistant
|
||||
Environment=PATH=/home/pi/linux-voice-assistant/.venv/bin:/usr/bin:/bin
|
||||
# Environment=ENABLE_DEBUG="1"
|
||||
# Environment=LIST_DEVICES="1"
|
||||
Environment=USERLVA_USER_ID00"
|
||||
Environment=LVA_USER_ID="1000"
|
||||
Environment=LVA_USER_GROUP="1000"
|
||||
Environment=CLIENT_NAME="My Voice Assistant Speaker"
|
||||
Environment=LVA_PULSE_SERVER="unix:/run/user/${USERLVA_USER_IDlse/native"
|
||||
Environment=LVA_XDG_RUNTIME_DIR="/run/user/${USERLVA_USER_ID
|
||||
# Environment=CLIENT_NAME="My Voice Assistant Speaker"
|
||||
Environment=LVA_PULSE_SERVER="unix:/run/user/${LVA_USER_ID}/pulse/native"
|
||||
Environment=LVA_XDG_RUNTIME_DIR="/run/user/${LVA_USER_ID}"
|
||||
Environment=PREFERENCES_FILE="/home/pi/linux-voice-assistant/preferences.json"
|
||||
Environment=PORT="6053"
|
||||
# Environment=NETWORK_INTERFACE="eth0"
|
||||
# Environment=HOST="0.0.0.0"
|
||||
# Environment=PORT="6053"
|
||||
# Environment=AUDIO_INPUT_DEVICE="default"
|
||||
# Environment=AUDIO_OUTPUT_DEVICE="default"
|
||||
# Environment=ENABLE_THINKING_SOUND="1"
|
||||
@@ -223,8 +207,8 @@ Environment=PORT="6053"
|
||||
# Environment=PROCESSING_SOUND="sounds/processing.wav"
|
||||
# Environment=MUTE_SOUND="sounds/mute_switch_on.flac"
|
||||
# Environment=UNMUTE_SOUND="sounds/mute_switch_off.flac"
|
||||
# ExecStart=/home/pi/linux-voice-assistant/docker-entrypoint.sh --additional-parameter-if-you-want
|
||||
ExecStart=/home/pi/linux-voice-assistant/docker-entrypoint.sh
|
||||
# ExecStart=/home/pi/linux-voice-assistant/docker-entrypoint.sh --additional-parameter-if-you-want
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
@@ -276,18 +260,20 @@ The following variables can be configured in the `.env` or in the service file:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `USERLVA_USER_ID`1000` | User ID for the container (usually 1000 for the first user) |
|
||||
| `USERLVA_USER_ID` | `1000` | User ID for the container (usually 1000 for the first user) |
|
||||
| `GROUP_ID` | `1000` | GROUP ID for the container (usually 1000 for the first users group) |
|
||||
| `CLIENT_NAME` | (optional) | Custom name for this voice assistant instance |
|
||||
| `LVA_PULSE_SERVER` | `unix:/run/user/${USERLVA_USER_IDlse/native` | Path to the PulseAudio/PipeWire socket |
|
||||
| `LVA_XDG_RUNTIME_DIR` | `/run/user/${USERLVA_USER_ID XDG runtime directory |
|
||||
| `LVA_PULSE_SERVER` | `unix:/run/user/${LVA_USER_ID}/pulse/native` | Path to the PulseAudio/PipeWire socket |
|
||||
| `LVA_XDG_RUNTIME_DIR` | `/run/user/${LVA_USER_ID}` | XDG runtime directory |
|
||||
| `ENABLE_DEBUG` | (optional) | Set to "1" to enable debug mode |
|
||||
| `LIST_DEVICES` | (optional) | Set to "1" to list audio devices instead of starting |
|
||||
| `PREFERENCES_FILE` | (optional) | Path to a custom preferences JSON file |
|
||||
| `NETWORK_INTERFACE` | Autodetected | network card for server |
|
||||
| `HOST` | Autodetected | API server IP-Address, can be 0.0.0.0 for all interfaces, but only one network card works for MAC-ADDRESS and ESP protocol |
|
||||
| `PORT` | `6053` | API server port |
|
||||
| `AUDIO_INPUT_DEVICE` | `default` | Audio input device name |
|
||||
| `AUDIO_OUTPUT_DEVICE` | `default` | Audio output device name |
|
||||
| `ENABLE_THINKING_SOUND` | (optional) | Set to "1" to enable thinking sound |
|
||||
| `ENABLE_THINKING_SOUND` | false | Set to "1" to enable thinking sound |
|
||||
| `WAKE_MODEL` | `okay_nabu` | Wake word model to use |
|
||||
| `REFACTORY_SECONDS` | `2` | Refractory period in seconds after wake word |
|
||||
| `WAKEUP_SOUND` | `sounds/wake_word_triggered.flac` | Sound file for wake word triggered |
|
||||
|
||||
@@ -18,8 +18,10 @@ from pyopen_wakeword import OpenWakeWord, OpenWakeWordFeatures
|
||||
from .models import AvailableWakeWord, Preferences, ServerState, WakeWordType
|
||||
from .mpv_player import MpvMediaPlayer
|
||||
from .satellite import VoiceSatelliteProtocol
|
||||
from .util import get_mac
|
||||
from getmac import get_mac_address
|
||||
from .zeroconf import HomeAssistantZeroconf
|
||||
from .util import get_default_interface
|
||||
from .util import get_default_ipv4
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_MODULE_DIR = Path(__file__).parent
|
||||
@@ -33,7 +35,9 @@ _SOUNDS_DIR = _REPO_DIR / "sounds"
|
||||
|
||||
async def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--name")
|
||||
parser.add_argument(
|
||||
"--name"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--audio-input-device",
|
||||
help="soundcard name for input device (see --list-input-devices)",
|
||||
@@ -43,7 +47,11 @@ async def main() -> None:
|
||||
action="store_true",
|
||||
help="List audio input devices and exit",
|
||||
)
|
||||
parser.add_argument("--audio-input-block-size", type=int, default=1024)
|
||||
parser.add_argument(
|
||||
"--audio-input-block-size",
|
||||
type=int,
|
||||
default=1024
|
||||
)
|
||||
parser.add_argument(
|
||||
"--audio-output-device",
|
||||
help="mpv name for output device (see --list-output-devices)",
|
||||
@@ -60,9 +68,15 @@ async def main() -> None:
|
||||
help="Directory with wake word models (.tflite) and configs (.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wake-model", default="okay_nabu", help="Id of active wake model"
|
||||
"--wake-model",
|
||||
default="okay_nabu",
|
||||
help="Id of active wake model"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-model",
|
||||
default="stop",
|
||||
help="Id of stop model"
|
||||
)
|
||||
parser.add_argument("--stop-model", default="stop", help="Id of stop model")
|
||||
parser.add_argument(
|
||||
"--download-dir",
|
||||
default=_REPO_DIR / "local",
|
||||
@@ -74,12 +88,13 @@ async def main() -> None:
|
||||
type=float,
|
||||
help="Seconds before wake word can be activated again",
|
||||
)
|
||||
#
|
||||
parser.add_argument(
|
||||
"--wakeup-sound", default=str(_SOUNDS_DIR / "wake_word_triggered.flac")
|
||||
"--wakeup-sound",
|
||||
default=str(_SOUNDS_DIR / "wake_word_triggered.flac")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timer-finished-sound", default=str(_SOUNDS_DIR / "timer_finished.flac")
|
||||
"--timer-finished-sound",
|
||||
default=str(_SOUNDS_DIR / "timer_finished.flac")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--processing-sound",
|
||||
@@ -96,17 +111,20 @@ async def main() -> None:
|
||||
default=str(_SOUNDS_DIR / "mute_switch_off.flac"),
|
||||
help="Sound to play when unmuting the assistant",
|
||||
)
|
||||
#
|
||||
parser.add_argument("--preferences-file", default=_REPO_DIR / "preferences.json")
|
||||
#
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default="0.0.0.0",
|
||||
help="Address for ESPHome server (default: 0.0.0.0)",
|
||||
help="IP-Address for ESPHome server", # 0.0.0.0 is IPv4, None is all interfaces
|
||||
)
|
||||
parser.add_argument(
|
||||
"--network-interface",
|
||||
help="Network interface to use for ESPHome server (default: will be automatically detected)",
|
||||
)
|
||||
# Note that default port is also set in docker-entrypoint.sh
|
||||
parser.add_argument(
|
||||
"--port", type=int, default=6053, help="Port for ESPHome server (default: 6053)"
|
||||
"--port", type=int,
|
||||
default=6053,
|
||||
help="Port for ESPHome server (default: 6053)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enable-thinking-sound",
|
||||
@@ -139,9 +157,41 @@ async def main() -> None:
|
||||
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
|
||||
_LOGGER.debug(args)
|
||||
|
||||
if not args.name:
|
||||
parser.error("the following arguments are required: --name")
|
||||
# Resolve network interface for mac-adress detection
|
||||
if not args.network_interface:
|
||||
print("No network interface specified, try to detect default interface")
|
||||
network_interface = get_default_interface()
|
||||
print(f"Default interface detected:", network_interface)
|
||||
else:
|
||||
print("Network interface specified")
|
||||
print(f"Using network interface: ", network_interface)
|
||||
network_interface = args.network_interface
|
||||
|
||||
# Resolve ip_address where the application will be listening
|
||||
if not args.host:
|
||||
print("No host (ip-address) specified, try to detect IP-Address")
|
||||
host_ip_address = get_default_ipv4(network_interface)
|
||||
print(f"IP-Address detected: ", host_ip_address)
|
||||
else:
|
||||
print("Host specified")
|
||||
print(f"Using host: ", args.host)
|
||||
host_ip_address = args.host
|
||||
|
||||
# Resolve mac
|
||||
mac_address = get_mac_address(interface=network_interface)
|
||||
mac_address_clean = mac_address.replace(":", "")
|
||||
|
||||
# Resolve name
|
||||
if not args.name:
|
||||
print("No name specified, try to autogenerate name")
|
||||
device_name = f"lva-{mac_address_clean}"
|
||||
print(f"Name autogenerated: {device_name}")
|
||||
else:
|
||||
print("Name specified")
|
||||
print(f"Using name: {args.name}")
|
||||
device_name = args.name
|
||||
|
||||
# Resolve download dir
|
||||
args.download_dir = Path(args.download_dir)
|
||||
args.download_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -237,8 +287,10 @@ async def main() -> None:
|
||||
assert stop_model is not None
|
||||
|
||||
state = ServerState(
|
||||
name=args.name,
|
||||
mac_address=get_mac(),
|
||||
name=device_name,
|
||||
network_interface=network_interface,
|
||||
mac_address=get_mac_address(interface=network_interface),
|
||||
ip_address=host_ip_address,
|
||||
audio_queue=Queue(),
|
||||
entities=[],
|
||||
available_wake_words=available_wake_words,
|
||||
@@ -270,16 +322,16 @@ async def main() -> None:
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(
|
||||
lambda: VoiceSatelliteProtocol(state), host=args.host, port=args.port
|
||||
lambda: VoiceSatelliteProtocol(state), host=host_ip_address, port=args.port
|
||||
)
|
||||
|
||||
# Auto discovery (zeroconf, mDNS)
|
||||
discovery = HomeAssistantZeroconf(port=args.port, name=args.name)
|
||||
discovery = HomeAssistantZeroconf(port=args.port, name=state.name, mac_address=state.mac_address, host_ip_address=host_ip_address)
|
||||
await discovery.register_server()
|
||||
|
||||
try:
|
||||
async with server:
|
||||
_LOGGER.info("Server started (host=%s, port=%s)", args.host, args.port)
|
||||
_LOGGER.info("Server started (host=%s, port=%s)", host_ip_address, args.port)
|
||||
await server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@@ -64,6 +64,8 @@ class Preferences:
|
||||
class ServerState:
|
||||
name: str
|
||||
mac_address: str
|
||||
ip_address: str
|
||||
network_interface: str
|
||||
audio_queue: "Queue[Optional[bytes]]"
|
||||
entities: "List[ESPHomeEntity]"
|
||||
available_wake_words: "Dict[str, AvailableWakeWord]"
|
||||
|
||||
@@ -1,16 +1,44 @@
|
||||
"""Utility methods."""
|
||||
|
||||
import uuid
|
||||
# netifaces lib is from netifaces2
|
||||
import netifaces
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def get_mac() -> str:
|
||||
mac = uuid.getnode()
|
||||
mac_str = ":".join(f"{(mac >> i) & 0xff:02x}" for i in range(40, -1, -8))
|
||||
return mac_str
|
||||
|
||||
|
||||
def call_all(*callables: Optional[Callable[[], None]]) -> None:
|
||||
for item in filter(None, callables):
|
||||
item()
|
||||
|
||||
|
||||
def get_default_interface():
|
||||
"""Return the default network interface name, or None if not found."""
|
||||
default_gateway = netifaces.default_gateway()
|
||||
|
||||
if not default_gateway:
|
||||
print("No default gateway found")
|
||||
return None
|
||||
|
||||
# default_gateway is e.g. {InterfaceType.AF_INET: ('192.168.33.1', 'wlp0s20f3')}
|
||||
gateway_info = default_gateway.get(netifaces.AF_INET)
|
||||
if not gateway_info:
|
||||
print("No default IPv4 gateway found")
|
||||
return None
|
||||
|
||||
# gateway_info is a tuple: (gateway_ip, interface_name)
|
||||
interface_name = gateway_info[1]
|
||||
#print(f"Default interface: {interface_name}")
|
||||
return interface_name
|
||||
|
||||
|
||||
def get_default_ipv4(interface: str):
|
||||
if not interface:
|
||||
return None
|
||||
|
||||
addresses = netifaces.ifaddresses(interface)
|
||||
ipv4_info = addresses.get(netifaces.AF_INET)
|
||||
|
||||
if not ipv4_info:
|
||||
return None
|
||||
|
||||
return ipv4_info[0]["addr"]
|
||||
@@ -18,20 +18,13 @@ MDNS_TARGET_IP = "224.0.0.251"
|
||||
|
||||
class HomeAssistantZeroconf:
|
||||
def __init__(
|
||||
self, port: int, name: Optional[str] = None, host: Optional[str] = None
|
||||
self, port: int, mac_address: str, host_ip_address: str, name: Optional[str] = None, host: Optional[str] = None
|
||||
) -> None:
|
||||
self.port = port
|
||||
self.name = name or _get_mac_address()
|
||||
self.mac_address = mac_address
|
||||
self.name = name or self.mac_address
|
||||
self.host_ip_address = host_ip_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:
|
||||
@@ -39,11 +32,11 @@ class HomeAssistantZeroconf:
|
||||
service_info = AsyncServiceInfo(
|
||||
"_esphomelib._tcp.local.",
|
||||
f"{self.name}._esphomelib._tcp.local.",
|
||||
addresses=[socket.inet_aton(self.host)],
|
||||
addresses=[socket.inet_aton(self.host_ip_address)],
|
||||
port=self.port,
|
||||
properties={
|
||||
"version": "2025.9.0",
|
||||
"mac": _get_mac_address(),
|
||||
"mac": self.mac_address,
|
||||
"board": "host",
|
||||
"platform": "HOST",
|
||||
"network": "ethernet", # or "wifi"
|
||||
@@ -53,12 +46,3 @@ class HomeAssistantZeroconf:
|
||||
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
|
||||
]
|
||||
)
|
||||
@@ -26,12 +26,14 @@ classifiers = [
|
||||
requires-python = ">=3.9.0"
|
||||
dependencies = [
|
||||
"aioesphomeapi==42.7.0",
|
||||
"netifaces2==0.0.22",
|
||||
"soundcard<1",
|
||||
"numpy>=2,<3",
|
||||
"pymicro-wakeword>=2,<3",
|
||||
"pyopen-wakeword>=1,<2",
|
||||
"python-mpv>=1,<2",
|
||||
"zeroconf<1",
|
||||
"getmac<1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
Reference in new issue
Block a user