Author SHA1 Message Date
Michael Hansen 13bb024931 Merge pull request #308 from AnkushMalaker/master
Update pyproject.toml to fix broken build
2025-04-25 13:52:50 -05:00
Ankush Malaker 04fe5e6827 Update pyproject.toml 2025-04-25 21:22:21 +05:30
Michael Hansen 0efb7fb8f2 Merge pull request #225 from mrdarrengriffin/ohf-logo
Add OHF logo to README
2025-04-22 10:07:48 -05:00
Michael Hansen ac0b4c7d14 Bump version 2025-02-10 11:57:13 -06:00
Michael Hansen c237a4887d Send Played event to server 2025-02-10 11:56:21 -06:00
Michael Hansen f86d236432 Update changelog 2025-01-24 09:43:28 -06:00
Michael Hansen b2409a4a40 Move other requirements to pyproject.toml 2025-01-24 09:42:20 -06:00
Michael Hansen 2c72ad0e01 Migrate to pyproject.toml 2025-01-24 09:28:51 -06:00
Michael Hansen 3576c0fc5f Lint 2025-01-24 09:28:38 -06:00
Michael Hansen 1915d3fce3 Send audio-start to wake service after detect 2025-01-24 09:17:54 -06:00
Darren Griffin 632b392154 Add OHF logo to README 2024-09-30 17:09:19 +01:00
Michael Hansen a2bb7c8f57 Add run_usbmic script 2024-07-01 11:33:06 -05:00
Michael Hansen cc9b33e8b8 Merge pull request #126 from tobuh/respeaker-usb-4mic
Add example for ReSpeaker USB 4mic array v2.0
2024-07-01 11:32:01 -05:00
Michael Hansen 862d102405 Merge pull request #167 from tannisroot/docs_change
Change docs to suggest bookworm based Pi OS
2024-07-01 11:23:00 -05:00
Michael Hansen 65c123ffb9 Merge pull request #177 from thetic/master
Improve import error logging
2024-07-01 11:22:38 -05:00
Michael Hansen 6d11883dcb Restrict range 2024-07-01 11:21:52 -05:00
Michael Hansen 2f4276e180 Merge pull request #101 from llluis/led_brightness
led brightness argument
2024-07-01 11:20:55 -05:00
Michael Hansen 397d4c8b61 Merge pull request #109 from llluis/bugfix-wav-volume
bugfix: Remove double volume multiplier
2024-07-01 11:18:03 -05:00
Michael Hansen f6c0d29405 Merge pull request #171 from tannisroot/adjust_compile_threads
Adjust compile threads for low memory boards when installing Respeaker driver
2024-07-01 09:35:21 -05:00
Aleksandr OleinikovandMichael Hansen a80c7724a6 suggestions by Mike
Co-authored-by: Michael Hansen <hansen.mike@gmail.com>
2024-06-26 21:42:33 +03:00
Michael Hansen 2e046c6b8b Clean up tts played 2024-06-26 13:30:40 -05:00
Michael Hansen 5d3c243a88 Merge pull request #100 from llluis/fix-played-event
bugfix played event
2024-06-26 13:26:42 -05:00
tannisroot 2b394504a0 Adjust compile threads for low memory boards 2024-06-23 02:30:24 +03:00
Chad Condon c6d1dec918 Clarify import error message 2024-06-16 12:26:27 -07:00
Chad Condon 2f6ec1d3b0 Include trace in import failure errors 2024-06-16 12:24:43 -07:00
Michael Hansen 41f107548f Merge pull request #168 from rhasspy/synesthesiam-20240522-timers
Add voice timer support
2024-05-24 15:38:21 -05:00
tannisroot f0297415ed Change docs to suggest bookworm based Pi OS 2024-05-22 09:09:16 +03:00
Tobias Buhlinger 69ceeef566 Add example for ReSpeaker USB 4mic array v2.0 2024-02-27 22:14:12 +01:00
Luis Andrade 886019e04f Merge branch 'rhasspy:master' into bugfix-wav-volume 2024-02-19 14:45:56 -05:00
Luis Andrade 61ae4ce68c remove double volume 2024-02-12 12:49:34 -05:00
Luis Andrade 00188fa5da led brightness argument 2024-02-08 01:05:42 -05:00
Luis Andrade 8a5f3fb409 bugfix played event 2024-02-08 00:25:53 -05:00
26 changed files with 267 additions and 120 deletions
+1 -3
View File
@@ -1,7 +1,5 @@
* *
!requirements.txt !pyproject.toml
!MANIFEST.in
!setup.py
!script/setup !script/setup
!script/run !script/run
!wyoming_satellite/*.py !wyoming_satellite/*.py
+12
View File
@@ -1,5 +1,17 @@
# Changelog # Changelog
## 1.4.1
- Send `played` event to server
## 1.4.0
- Migrate to pyproject.toml
## 1.3.1
- Send audio-start to wake service after detect
## 1.3.0 ## 1.3.0
- Bump to wyoming 1.5.4 (timers) - Bump to wyoming 1.5.4 (timers)
+1 -1
View File
@@ -10,7 +10,7 @@ WORKDIR /app
COPY sounds/ ./sounds/ COPY sounds/ ./sounds/
COPY script/setup ./script/ COPY script/setup ./script/
COPY setup.py requirements.txt MANIFEST.in ./ COPY pyproject.toml ./
COPY wyoming_satellite/ ./wyoming_satellite/ COPY wyoming_satellite/ ./wyoming_satellite/
RUN script/setup RUN script/setup
-2
View File
@@ -1,2 +0,0 @@
include requirements.txt
include VERSION
+1
View File
@@ -13,6 +13,7 @@ Video tutorials:
* [Wyoming Voice Satellite with ChatGPT](https://www.youtube.com/watch?v=eTKgc0YDCwE) * [Wyoming Voice Satellite with ChatGPT](https://www.youtube.com/watch?v=eTKgc0YDCwE)
* [Local ChatGPT Voice Assistant](https://www.youtube.com/watch?v=pAKqKTkx5X4) * [Local ChatGPT Voice Assistant](https://www.youtube.com/watch?v=pAKqKTkx5X4)
[![An open standard from the Open Home Foundation](https://www.openhomefoundation.org/badges/ohf-open-standard.png)](https://www.openhomefoundation.org/)
--- ---
Requires: Requires:
+12 -4
View File
@@ -6,7 +6,7 @@ This tutorial should work for almost any Raspberry Pi and USB microphone. Audio
## Install OS ## Install OS
Follow instructions to [install Raspberry Pi OS](https://www.raspberrypi.com/software/). Under "Choose OS", pick "Raspberry Pi OS (other)" and "Raspberry Pi OS (Legacy, **64-bit**) Lite". Follow instructions to [install Raspberry Pi OS](https://www.raspberrypi.com/software/). Under "Choose OS", pick "Raspberry Pi OS (other)" and "Raspberry Pi OS (**64-bit**) Lite".
When asking if you'd like to apply customization settings, choose "Edit Settings" and: When asking if you'd like to apply customization settings, choose "Edit Settings" and:
@@ -55,9 +55,7 @@ python3 -m venv .venv
.venv/bin/pip3 install --upgrade wheel setuptools .venv/bin/pip3 install --upgrade wheel setuptools
.venv/bin/pip3 install \ .venv/bin/pip3 install \
-f 'https://synesthesiam.github.io/prebuilt-apps/' \ -f 'https://synesthesiam.github.io/prebuilt-apps/' \
-r requirements.txt \ -e '.[all]'
-r requirements_audio_enhancement.txt \
-r requirements_vad.txt
``` ```
If the installation was successful, you should be able to run: If the installation was successful, you should be able to run:
@@ -305,6 +303,13 @@ python3 -m venv --system-site-packages .venv
.venv/bin/pip3 install 'wyoming==1.5.2' .venv/bin/pip3 install 'wyoming==1.5.2'
``` ```
In case you use a ReSpeaker USB 4mic array v2.0, additionally install pixel-ring:
```sh
.venv/bin/pip3 install 'pixel-ring'
```
The `--system-site-packages` argument is used to access the pre-installed `gpiozero` and `spidev` Python packages. If these are **not already installed** in your system, run: The `--system-site-packages` argument is used to access the pre-installed `gpiozero` and `spidev` Python packages. If these are **not already installed** in your system, run:
```sh ```sh
@@ -385,4 +390,7 @@ Try a voice command and see if the LEDs change. Use `journalctl` to check the lo
journalctl -u 2mic_leds.service -f journalctl -u 2mic_leds.service -f
``` ```
If you encounter any issues, you can add the `--debug` argument to the command line to increase the log level.
To control the brightness of the LEDS, use the `--led-brightness ` argument, which accepts integer numbers from 1 to 31.
Make sure to run `sudo systemctl daemon-reload` every time you make changes to the service. Make sure to run `sudo systemctl daemon-reload` every time you make changes to the service.
+1 -1
View File
@@ -4,7 +4,7 @@ Create a voice satellite using a Raspberry Pi 3+ and USB microphone and speakers
## Install OS ## Install OS
Follow instructions to [install Raspberry Pi OS](https://www.raspberrypi.com/software/). Under "Choose OS", pick "Raspberry Pi OS (other)" and "Raspberry Pi OS (Legacy, **64-bit**) Lite". Follow instructions to [install Raspberry Pi OS](https://www.raspberrypi.com/software/). Under "Choose OS", pick "Raspberry Pi OS (other)" and "Raspberry Pi OS (**64-bit**) Lite".
When asking if you'd like to apply customization settings, choose "Edit Settings" and: When asking if you'd like to apply customization settings, choose "Edit Settings" and:
+7 -1
View File
@@ -43,12 +43,18 @@ mod='seeed-voicecard'
src='./' src='./'
kernel="$(uname -r)" kernel="$(uname -r)"
marker='0.0.0' marker='0.0.0'
threads="$(getconf _NPROCESSORS_ONLN)"
memory="$(LANG=C free -m|awk '/^Mem:/{print $2}')"
if [ "${memory}" -le 512 ] && [ "${threads}" -gt 2 ]; then
threads=2
fi
mkdir -p "/usr/src/${mod}-${ver}" mkdir -p "/usr/src/${mod}-${ver}"
cp -a "${src}"/* "/usr/src/${mod}-${ver}/" cp -a "${src}"/* "/usr/src/${mod}-${ver}/"
dkms add -m "${mod}" -v "${ver}" dkms add -m "${mod}" -v "${ver}"
dkms build -k "${kernel}" -m "${mod}" -v "${ver}" && { dkms build -k "${kernel}" -m "${mod}" -v "${ver}" -j "${threads}" && {
dkms install --force -k "${kernel}" -m "${mod}" -v "${ver}" dkms install --force -k "${kernel}" -m "${mod}" -v "${ver}"
} }
+10 -3
View File
@@ -41,8 +41,14 @@ async def main() -> None:
"""Main entry point.""" """Main entry point."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--uri", required=True, help="unix:// or tcp://") parser.add_argument("--uri", required=True, help="unix:// or tcp://")
#
parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") parser.add_argument("--debug", action="store_true", help="Log DEBUG messages")
parser.add_argument(
"--led-brightness",
type=int,
choices=range(1, 32),
default=31,
help="LED brightness (integer from 1 to 31)",
)
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
@@ -54,7 +60,7 @@ async def main() -> None:
led_power = gpiozero.LED(LEDS_GPIO, active_high=False) led_power = gpiozero.LED(LEDS_GPIO, active_high=False)
led_power.on() led_power.on()
leds = APA102(num_led=NUM_LEDS) leds = APA102(num_led=NUM_LEDS, global_brightness=args.led_brightness)
# Start server # Start server
server = AsyncServer.from_uri(args.uri) server = AsyncServer.from_uri(args.uri)
@@ -148,7 +154,7 @@ class APA102:
def __init__( def __init__(
self, self,
num_led, num_led,
global_brightness=MAX_BRIGHTNESS, global_brightness,
order="rgb", order="rgb",
bus=0, bus=0,
device=1, device=1,
@@ -162,6 +168,7 @@ class APA102:
self.global_brightness = self.MAX_BRIGHTNESS self.global_brightness = self.MAX_BRIGHTNESS
else: else:
self.global_brightness = global_brightness self.global_brightness = global_brightness
_LOGGER.debug("LED brightness: %d", self.global_brightness)
self.leds = [self.LED_START, 0, 0, 0] * self.num_led # Pixel buffer self.leds = [self.LED_START, 0, 0, 0] * self.num_led # Pixel buffer
self.spi = spidev.SpiDev() # Init the SPI device self.spi = spidev.SpiDev() # Init the SPI device
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Controls the LEDs on the ReSpeaker Mic Array v2.0 (USB) ."""
import argparse
import asyncio
import logging
import time
from functools import partial
from pixel_ring import pixel_ring
from wyoming.event import Event
from wyoming.satellite import (
SatelliteConnected,
SatelliteDisconnected,
StreamingStarted,
StreamingStopped,
)
from wyoming.server import AsyncEventHandler, AsyncServer
from wyoming.snd import Played
from wyoming.vad import VoiceStarted, VoiceStopped
from wyoming.wake import Detection
_LOGGER = logging.getLogger()
async def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser()
parser.add_argument("--uri", required=True, help="unix:// or tcp://")
parser.add_argument("--debug", action="store_true", help="Log DEBUG messages")
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
_LOGGER.debug(args)
_LOGGER.info("Ready")
# Turn on power to LEDs
pixel_ring.set_color_palette(0x0080FF, 0x007A37)
pixel_ring.think()
await asyncio.sleep(3)
pixel_ring.off()
# Start server
server = AsyncServer.from_uri(args.uri)
try:
await server.run(partial(LEDsEventHandler, args))
except KeyboardInterrupt:
pass
finally:
pixel_ring.off()
class LEDsEventHandler(AsyncEventHandler):
"""Event handler for clients."""
def __init__(
self,
cli_args: argparse.Namespace,
*args,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self.cli_args = cli_args
self.client_id = str(time.monotonic_ns())
_LOGGER.debug("Client connected: %s", self.client_id)
async def handle_event(self, event: Event) -> bool:
_LOGGER.debug(event)
if Detection.is_type(event.type):
_LOGGER.debug("Detection")
pixel_ring.wakeup()
elif VoiceStarted.is_type(event.type):
_LOGGER.debug("VoiceStarted")
pixel_ring.speak()
elif VoiceStopped.is_type(event.type):
_LOGGER.debug("VoiceStopped")
pixel_ring.spin()
elif StreamingStopped.is_type(event.type):
_LOGGER.debug("StreamingStopped")
pixel_ring.off()
elif SatelliteConnected.is_type(event.type):
_LOGGER.debug("SatelliteConnected")
pixel_ring.think()
await asyncio.sleep(2)
pixel_ring.off()
elif Played.is_type(event.type):
_LOGGER.debug("Played")
pixel_ring.off()
elif SatelliteDisconnected.is_type(event.type):
_LOGGER.debug("SatelliteDisconnected")
pixel_ring.mono(0xFF0000)
return True
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
+3 -7
View File
@@ -123,7 +123,7 @@ def apply_settings(settings: Settings) -> None:
): ):
result = run_with_gauge( result = run_with_gauge(
"Installing vad...", "Installing vad...",
[pip_install("-r", str(PROGRAM_DIR / "requirements_vad.txt"))], [pip_install("-e", f"{PROGRAM_DIR}[silerovad]")],
) )
if not result: if not result:
error("installing vad") error("installing vad")
@@ -135,11 +135,7 @@ def apply_settings(settings: Settings) -> None:
): ):
result = run_with_gauge( result = run_with_gauge(
"Installing audio enhancements...", "Installing audio enhancements...",
[ [pip_install("-e", f"{PROGRAM_DIR}[webrtc]")],
pip_install(
"-r", str(PROGRAM_DIR / "requirements_audio_enhancement.txt")
)
],
) )
if not result: if not result:
error("installing audio enhancements") error("installing audio enhancements")
@@ -154,7 +150,7 @@ def apply_settings(settings: Settings) -> None:
) and (not can_import("gpiozero", "spidev")): ) and (not can_import("gpiozero", "spidev")):
result = run_with_gauge( result = run_with_gauge(
"Installing event requirements...", "Installing event requirements...",
[pip_install("-r", str(PROGRAM_DIR / "requirements_respeaker.txt"))], [pip_install("-e", f"{PROGRAM_DIR}[respeaker]")],
) )
if not result: if not result:
error("installing event requirements") error("installing event requirements")
+61
View File
@@ -0,0 +1,61 @@
[build-system]
requires = ["setuptools>=62.3"]
build-backend = "setuptools.build_meta"
[project]
name = "wyoming_satellite"
version = "1.4.1"
license = {text = "MIT"}
description = "Wyoming server for remote voice satellite"
readme = "README.md"
authors = [
{name = "Michael Hansen", email = "mike@rhasspy.org"}
]
keywords = ["rhasspy", "wyoming", "satellite"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
requires-python = ">=3.9.0"
dependencies = [
"wyoming==1.5.4",
"zeroconf==0.88.0",
"pyring-buffer>=1,<2",
]
[project.optional-dependencies]
all = ["pysilero-vad==1.0.0", "webrtc-noise-gain==1.2.3"]
silerovad = ["pysilero-vad==1.0.0"]
webrtc = ["webrtc-noise-gain==1.2.3"]
respeaker = ["gpiozero", "spidev"]
dev = [
"black==22.12.0",
"flake8==6.0.0",
"isort==5.11.3",
"mypy==0.991",
"pylint==2.15.9",
"pytest==7.4.3",
"pytest-asyncio==0.23.3",
"build==1.2.2"
]
[project.scripts]
wyoming-satellite = "wyoming_satellite:__main__.run"
[project.urls]
"Source Code" = "http://github.com/rhasspy/wyoming-satellite"
[tool.setuptools]
platforms = ["any"]
zip-safe = true
include-package-data = true
[tool.setuptools.packages.find]
include = ["wyoming_satellite*"]
exclude = ["tests", "tests.*"]
-3
View File
@@ -1,3 +0,0 @@
wyoming==1.5.4
zeroconf==0.88.0
pyring-buffer==1.0.0
-1
View File
@@ -1 +0,0 @@
webrtc-noise-gain==1.2.3
-7
View File
@@ -1,7 +0,0 @@
black==22.12.0
flake8==6.0.0
isort==5.11.3
mypy==0.991
pylint==2.15.9
pytest==7.4.3
pytest-asyncio==0.23.3
-2
View File
@@ -1,2 +0,0 @@
gpiozero
spidev
-1
View File
@@ -1 +0,0 @@
pysilero-vad==1.0.0
+1 -1
View File
@@ -8,4 +8,4 @@ _PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv" _VENV_DIR = _PROGRAM_DIR / ".venv"
context = venv.EnvBuilder().ensure_directories(_VENV_DIR) context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, _PROGRAM_DIR / "setup.py", "bdist_wheel"]) subprocess.check_call([context.env_exe, "-m", "build", "--wheel", "--sdist"])
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
import sys
import subprocess
import venv
from pathlib import Path
_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call(
[context.env_exe, str(_PROGRAM_DIR / "examples" / "usbmic_service.py")]
+ sys.argv[1:]
)
+3 -4
View File
@@ -20,7 +20,6 @@ builder.create(_VENV_DIR)
# Upgrade dependencies # Upgrade dependencies
pip = [context.env_exe, "-m", "pip"] pip = [context.env_exe, "-m", "pip"]
subprocess.check_call(pip + ["install", "--upgrade", "pip"]) subprocess.check_call(pip + ["install", "--upgrade", "pip"])
subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])
# Install requirements # Install requirements
subprocess.check_call( subprocess.check_call(
@@ -31,13 +30,13 @@ subprocess.check_call(
"https://www.piwheels.org/simple", "https://www.piwheels.org/simple",
"-f", "-f",
"https://synesthesiam.github.io/prebuilt-apps/", "https://synesthesiam.github.io/prebuilt-apps/",
"-r", "-e",
str(_PROGRAM_DIR / "requirements.txt"), str(_PROGRAM_DIR),
] ]
) )
if args.dev: if args.dev:
# Install dev requirements # Install dev requirements
subprocess.check_call( subprocess.check_call(
pip + ["install", "-r", str(_PROGRAM_DIR / "requirements_dev.txt")] pip + ["install", "-e", f"{_PROGRAM_DIR}[dev]"]
) )
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
from typing import List
import setuptools
from setuptools import setup
this_dir = Path(__file__).parent
module_dir = this_dir / "wyoming_satellite"
version_path = module_dir / "VERSION"
version = version_path.read_text(encoding="utf-8").strip()
def get_requirements(req_path: Path) -> List[str]:
if not req_path.is_file():
return []
requirements: List[str] = []
with open(req_path, "r", encoding="utf-8") as req_file:
for line in req_file:
line = line.strip()
if not line:
continue
requirements.append(line)
return requirements
install_requires = get_requirements(this_dir / "requirements.txt")
extras_require = {
"silerovad": get_requirements(this_dir / "requirements_vad.txt"),
"webrtc": get_requirements(this_dir / "requirements_audio_enhancement.txt"),
}
# -----------------------------------------------------------------------------
setup(
name="wyoming_satellite",
version="1.1.0",
description="Wyoming server for remote voice satellite",
url="http://github.com/rhasspy/wyoming-satellite",
author="Michael Hansen",
author_email="mike@rhasspy.org",
packages=setuptools.find_packages(),
package_data={
"wyoming_satellite": [str(p.relative_to(module_dir)) for p in (version_path,)]
},
install_requires=install_requires,
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
keywords="rhasspy wyoming satellite",
extras_require=extras_require,
entry_points={
"console_scripts": ["wyoming-satellite = wyoming_satellite:__main__.run"]
},
)
+1 -1
View File
@@ -1,6 +1,6 @@
[tox] [tox]
env_list = env_list =
py{39,310,311} py{39,310,311,312,313}
minversion = 4.12.1 minversion = 4.12.1
[testenv] [testenv]
-1
View File
@@ -1 +0,0 @@
1.3.0
+3 -1
View File
@@ -1,4 +1,6 @@
"""Voice satellite using the Wyoming protocol.""" """Voice satellite using the Wyoming protocol."""
import importlib.metadata
from pathlib import Path from pathlib import Path
from .satellite import ( from .satellite import (
@@ -17,7 +19,7 @@ from .settings import (
) )
_DIR = Path(__file__).parent _DIR = Path(__file__).parent
__version__ = (_DIR / "VERSION").read_text(encoding="utf-8").strip() __version__ = importlib.metadata.version("wyoming-satellite")
__all__ = [ __all__ = [
"__version__", "__version__",
+2 -2
View File
@@ -308,14 +308,14 @@ async def main() -> None:
try: try:
import webrtc_noise_gain # noqa: F401 import webrtc_noise_gain # noqa: F401
except ImportError: except ImportError:
_LOGGER.fatal("Install extras for webrtc") _LOGGER.exception("Extras for webrtc are not installed")
sys.exit(1) sys.exit(1)
if needs_silero(args): if needs_silero(args):
try: try:
import pysilero_vad # noqa: F401 import pysilero_vad # noqa: F401
except ImportError: except ImportError:
_LOGGER.fatal("Install extras for silerovad") _LOGGER.exception("Extras for silerovad are not installed")
sys.exit(1) sys.exit(1)
if args.awake_wav and (not Path(args.awake_wav).is_file()): if args.awake_wav and (not Path(args.awake_wav).is_file()):
+29 -10
View File
@@ -1,10 +1,12 @@
"""Satellite code.""" """Satellite code."""
import array import array
import asyncio import asyncio
import logging import logging
import math import math
import time import time
import wave import wave
from dataclasses import dataclass
from enum import Enum, auto from enum import Enum, auto
from pathlib import Path from pathlib import Path
from typing import Callable, Dict, Final, List, Optional, Set, Union from typing import Callable, Dict, Final, List, Optional, Set, Union
@@ -60,6 +62,12 @@ class State(Enum):
STOPPED = auto() STOPPED = auto()
@dataclass
class SoundEvent:
event: Event
is_tts: bool
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -76,7 +84,7 @@ class SatelliteBase:
self._mic_task: Optional[asyncio.Task] = None self._mic_task: Optional[asyncio.Task] = None
self._mic_webrtc: Optional[Callable[[bytes], bytes]] = None self._mic_webrtc: Optional[Callable[[bytes], bytes]] = None
self._snd_task: Optional[asyncio.Task] = None self._snd_task: Optional[asyncio.Task] = None
self._snd_queue: "Optional[asyncio.Queue[Event]]" = None self._snd_queue: "Optional[asyncio.Queue[SoundEvent]]" = None
self._wake_task: Optional[asyncio.Task] = None self._wake_task: Optional[asyncio.Task] = None
self._wake_queue: "Optional[asyncio.Queue[Event]]" = None self._wake_queue: "Optional[asyncio.Queue[Event]]" = None
self._event_task: Optional[asyncio.Task] = None self._event_task: Optional[asyncio.Task] = None
@@ -507,9 +515,11 @@ class SatelliteBase:
event = AudioChunk( event = AudioChunk(
rate=chunk.rate, rate=chunk.rate,
width=chunk.width, width=chunk.width,
channels=chunk.channels channels=(
if (self.settings.mic.channel_index is None) chunk.channels
else 1, if (self.settings.mic.channel_index is None)
else 1
),
audio=audio_bytes, audio=audio_bytes,
).event() ).event()
else: else:
@@ -543,10 +553,10 @@ class SatelliteBase:
# Sound # Sound
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
async def event_to_snd(self, event: Event) -> None: async def event_to_snd(self, event: Event, is_tts: bool = True) -> None:
"""Send an event to the sound service.""" """Send an event to the sound service."""
if self._snd_queue is not None: if self._snd_queue is not None:
self._snd_queue.put_nowait(event) self._snd_queue.put_nowait(SoundEvent(event, is_tts))
def _make_snd_client(self) -> Optional[AsyncClient]: def _make_snd_client(self) -> Optional[AsyncClient]:
"""Create client for snd service.""" """Create client for snd service."""
@@ -581,7 +591,8 @@ class SatelliteBase:
if self._snd_queue is None: if self._snd_queue is None:
self._snd_queue = asyncio.Queue() self._snd_queue = asyncio.Queue()
event = await self._snd_queue.get() snd_event = await self._snd_queue.get()
event = snd_event.event
if snd_client is None: if snd_client is None:
snd_client = self._make_snd_client() snd_client = self._make_snd_client()
@@ -608,7 +619,9 @@ class SatelliteBase:
event.type event.type
): ):
await _disconnect() await _disconnect()
await self.trigger_played() if snd_event.is_tts:
await self.event_to_server(Played().event())
await self.trigger_played()
snd_client = None # reconnect on next event snd_client = None # reconnect on next event
except asyncio.CancelledError: except asyncio.CancelledError:
break break
@@ -652,9 +665,8 @@ class SatelliteBase:
for event in wav_to_events( for event in wav_to_events(
wav_path, wav_path,
samples_per_chunk=self.settings.snd.samples_per_chunk, samples_per_chunk=self.settings.snd.samples_per_chunk,
volume_multiplier=self.settings.snd.volume_multiplier,
): ):
await self.event_to_snd(event) await self.event_to_snd(event, is_tts=False)
except Exception: except Exception:
# Unmute in case of an error # Unmute in case of an error
self.microphone_muted = False self.microphone_muted = False
@@ -797,6 +809,13 @@ class SatelliteBase:
wake_names = [w.name for w in self.settings.wake.names] wake_names = [w.name for w in self.settings.wake.names]
await self.event_to_wake(Detect(names=wake_names).event()) await self.event_to_wake(Detect(names=wake_names).event())
await self.event_to_wake(
AudioStart(
rate=self.settings.mic.rate,
width=self.settings.mic.width,
channels=self.settings.mic.channels,
).event()
)
await self.trigger_detect() await self.trigger_detect()
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------