Merge branch 'main' into Volume-Persistence-Across-Restarts
This commit is contained in:
13 files changed
+123
-12
No files matched your search
@@ -8,3 +8,5 @@
|
||||
!/sounds/*
|
||||
!/wakewords/*
|
||||
!/wakewords/openWakeWord/*
|
||||
!/version.txt
|
||||
!/version_githash.txt
|
||||
@@ -44,6 +44,12 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create version files
|
||||
run: |
|
||||
echo "${{ env.CLEAN_BRANCH_NAME }}" > version.txt
|
||||
echo "${{ github.sha }}" > version_githash.txt
|
||||
ls -ltr
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
|
||||
@@ -25,6 +25,7 @@ jobs:
|
||||
run: |
|
||||
echo "REGISTRY_LOWER=$(echo '${{ env.REGISTRY }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
echo "REPOSITORY_LOWER=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
||||
with:
|
||||
@@ -36,6 +37,13 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create version files
|
||||
run: |
|
||||
echo "nightly" > version.txt
|
||||
echo "${{ github.sha }}" > version_githash.txt
|
||||
ls -ltr
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
||||
|
||||
@@ -45,6 +45,13 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
- name: Create version files
|
||||
run: |
|
||||
echo "${{ steps.meta.outputs.version }}" > version.txt
|
||||
echo "${{ github.sha }}" > version_githash.txt
|
||||
ls -ltr
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
||||
|
||||
@@ -21,3 +21,5 @@ venv/
|
||||
|
||||
# Docker
|
||||
.env
|
||||
version.txt
|
||||
version_githash.txt
|
||||
@@ -54,6 +54,8 @@ COPY sounds/ ./sounds/
|
||||
COPY ../wakewords/ ./wakewords/
|
||||
COPY linux_voice_assistant/ ./linux_voice_assistant/
|
||||
COPY docker-entrypoint.sh ./
|
||||
COPY version.txt ./
|
||||
COPY version_githash.txt ./
|
||||
|
||||
### Run installation:
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
|
||||
@@ -47,6 +47,8 @@ services:
|
||||
- ${LVA_XDG_RUNTIME_DIR}:${LVA_XDG_RUNTIME_DIR}
|
||||
# Mount source code for development
|
||||
- ./linux_voice_assistant:/app/linux_voice_assistant
|
||||
- ./version.txt:/app/version.txt
|
||||
- ./version_githash.txt:/app/version_githash.txt
|
||||
depends_on:
|
||||
- fix-permissions
|
||||
# Health check for process
|
||||
|
||||
@@ -22,6 +22,8 @@ from getmac import get_mac_address
|
||||
from .zeroconf import HomeAssistantZeroconf
|
||||
from .util import get_default_interface
|
||||
from .util import get_default_ipv4
|
||||
from .util import get_version
|
||||
from .util import get_esphome_version
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_MODULE_DIR = Path(__file__).parent
|
||||
@@ -183,13 +185,25 @@ async def main() -> None:
|
||||
|
||||
# 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}")
|
||||
print("No friendly name specified, try to autogenerate name")
|
||||
friendly_name = f"LVA - {mac_address_clean}"
|
||||
print(f"Friendly name autogenerated: {friendly_name}")
|
||||
else:
|
||||
print("Name specified")
|
||||
print(f"Using name: {args.name}")
|
||||
device_name = args.name
|
||||
print("Friendly name specified")
|
||||
print(f"Using friendly name: {args.name}")
|
||||
friendly_name = args.name
|
||||
|
||||
mac_no_colon = mac_address.replace(":", "").lower()
|
||||
device_name = f"lva-{mac_no_colon}"
|
||||
print(f"Device name: {device_name}")
|
||||
|
||||
# Resolve version
|
||||
version = get_version()
|
||||
print(f"Version: {version}")
|
||||
|
||||
# Resolve esphome version
|
||||
esphome_version = get_esphome_version()
|
||||
print(f"ESPHome api version: {esphome_version}")
|
||||
|
||||
# Resolve download dir
|
||||
args.download_dir = Path(args.download_dir)
|
||||
@@ -293,9 +307,12 @@ async def main() -> None:
|
||||
|
||||
state = ServerState(
|
||||
name=device_name,
|
||||
friendly_name=friendly_name,
|
||||
network_interface=network_interface,
|
||||
mac_address=get_mac_address(interface=network_interface),
|
||||
ip_address=host_ip_address,
|
||||
version=version,
|
||||
esphome_version=esphome_version,
|
||||
audio_queue=Queue(),
|
||||
entities=[],
|
||||
available_wake_words=available_wake_words,
|
||||
|
||||
@@ -64,9 +64,12 @@ class Preferences:
|
||||
@dataclass
|
||||
class ServerState:
|
||||
name: str
|
||||
friendly_name: str
|
||||
mac_address: str
|
||||
ip_address: str
|
||||
network_interface: str
|
||||
version: str
|
||||
esphome_version: str
|
||||
audio_queue: "Queue[Optional[bytes]]"
|
||||
entities: "List[ESPHomeEntity]"
|
||||
available_wake_words: "Dict[str, AvailableWakeWord]"
|
||||
|
||||
@@ -294,15 +294,15 @@ class VoiceSatelliteProtocol(APIServer):
|
||||
elif isinstance(msg, VoiceAssistantTimerEventResponse):
|
||||
self.handle_timer_event(VoiceAssistantTimerEventType(msg.event_type), msg)
|
||||
elif isinstance(msg, DeviceInfoRequest):
|
||||
# Compute dynamic device name
|
||||
base_name = re.sub(r"[\s-]+", "-", self.state.name.lower()).strip("-")
|
||||
mac_no_colon = self.state.mac_address.replace(":", "").lower()
|
||||
mac_last6 = mac_no_colon[-6:]
|
||||
device_name = f"{base_name}-{mac_last6}"
|
||||
_LOGGER.debug("Device info request")
|
||||
|
||||
yield DeviceInfoResponse(
|
||||
uses_password=False,
|
||||
name=device_name,
|
||||
name=self.state.name,
|
||||
friendly_name=self.state.friendly_name,
|
||||
project_name="Open Home Foundation.Linux Voice Assistant",
|
||||
project_version=self.state.version,
|
||||
esphome_version=self.state.esphome_version,
|
||||
mac_address=self.state.mac_address,
|
||||
manufacturer="Open Home Foundation",
|
||||
model="Linux Voice Assistant",
|
||||
|
||||
@@ -4,8 +4,68 @@ import uuid
|
||||
# netifaces lib is from netifaces2
|
||||
import netifaces
|
||||
from collections.abc import Callable
|
||||
from importlib.metadata import version, PackageNotFoundError
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Cache for version to avoid repeated file reading
|
||||
_version_cache: Optional[str] = None
|
||||
_esphome_version_cache: Optional[str] = None
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
"""
|
||||
Read the version from version.txt file.
|
||||
|
||||
This function reads the content safely without risk of code injection,
|
||||
as it only reads raw text and performs no evaluation.
|
||||
|
||||
Returns:
|
||||
str: The version from version.txt or 'unknown' if the file
|
||||
does not exist or cannot be read.
|
||||
"""
|
||||
global _version_cache
|
||||
|
||||
if _version_cache is not None:
|
||||
return _version_cache
|
||||
|
||||
version_file = Path(__file__).parent.parent / "version.txt"
|
||||
|
||||
try:
|
||||
# Sicher lesen: nur Rohtext, keine Evaluierung
|
||||
version = version_file.read_text(encoding="utf-8").strip()
|
||||
_version_cache = version if version else "unknown"
|
||||
except (FileNotFoundError, PermissionError, OSError):
|
||||
_version_cache = "unknown"
|
||||
|
||||
return _version_cache
|
||||
|
||||
|
||||
def get_esphome_version() -> str:
|
||||
"""
|
||||
Read the version of the installed aioesphomeapi package.
|
||||
|
||||
This function uses importlib.metadata to safely retrieve the version
|
||||
of an installed Python package without executing any code from the
|
||||
package itself.
|
||||
|
||||
Returns:
|
||||
str: The version of aioesphomeapi (e.g., '42.7.0'), or 'unknown'
|
||||
if the package is not installed or the version cannot be read.
|
||||
"""
|
||||
global _esphome_version_cache
|
||||
|
||||
if _esphome_version_cache is not None:
|
||||
return _esphome_version_cache
|
||||
|
||||
try:
|
||||
_esphome_version_cache = version("aioesphomeapi")
|
||||
except PackageNotFoundError:
|
||||
_esphome_version_cache = "unknown"
|
||||
|
||||
return _esphome_version_cache
|
||||
|
||||
|
||||
def call_all(*callables: Optional[Callable[[], None]]) -> None:
|
||||
for item in filter(None, callables):
|
||||
item()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
placeholder
|
||||
@@ -0,0 +1 @@
|
||||
placeholder
|
||||
Reference in new issue
Block a user