-
-
Notifications
You must be signed in to change notification settings - Fork 32k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add force activate button to wyoming satellite #114291
Add force activate button to wyoming satellite #114291
Conversation
Hey there @balloob, @synesthesiam, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. |
Ping to keep open |
Let's keep this open for now. I am converting to a draft to indicate we're still discussing how we want to implement this feature. It may be a button, or likely a service call on |
ac36b79
to
59f2b30
Compare
59f2b30
to
045d68d
Compare
Glad to see that the feature is "in the pipeline" 😄 In the meanwhile I rebased on dev to keep it fresh. Existing tests still pass (once the latest wyoming from git is installed). |
I was looking into this today for a project with our lab (UGA Innovation Factory) for a project using a converted wall telephone and was very pleased to see that work for this is already so far progressed. I agree fully with @synesthesiam that it should likely be implemented as a service call rather than a button. @chatziko seemed to have pretty good implementation for the I cherry-picked @chatziko 's commit over the latest dev branch and modified what would be required to remove the button but reimplement as a service call through assist_pipeline. I used a new event listener (modified from the button's call implementation) and modified assist_pipeline to accept a new service call and write an event to the bus for Wyoming Satellite to listen for and react accordingly. The service call takes the following format: service: assist_pipeline.force_activate
target:
device_id: [device id of the Wyoming satellite]
I've attached a diff below for the changes I made versus the commit active for this draft.
index f481411e55..2a3f8496dd 100644
--- a/homeassistant/components/assist_pipeline/__init__.py
+++ b/homeassistant/components/assist_pipeline/__init__.py
@@ -7,7 +7,8 @@ from collections.abc import AsyncIterable
import voluptuous as vol
from homeassistant.components import stt
-from homeassistant.core import Context, HomeAssistant
+from homeassistant.const import CONF_DEVICE_ID
+from homeassistant.core import Context, HomeAssistant, ServiceCall
from homeassistant.helpers.typing import ConfigType
from .const import (
@@ -16,6 +17,8 @@ from .const import (
DATA_LAST_WAKE_UP,
DOMAIN,
EVENT_RECORDING,
+ OVERRIDE_START_EVENT_TYPE,
+ SERVICE_FORCE_ACTIVATE,
)
from .error import PipelineNotFound
from .pipeline import (
@@ -53,6 +56,7 @@ __all__ = (
"PipelineNotFound",
"WakeWordSettings",
"EVENT_RECORDING",
+ "OVERRIDE_START_EVENT_TYPE",
)
CONFIG_SCHEMA = vol.Schema(
@@ -74,6 +78,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
# wake_word_id -> timestamp of last detection (monotonic_ns)
hass.data[DATA_LAST_WAKE_UP] = {}
+ def force_activate(call: ServiceCall) -> None:
+ """Handle force_activate service call."""
+ device_id = call.data.get(CONF_DEVICE_ID)
+ hass.bus.async_fire(OVERRIDE_START_EVENT_TYPE, {CONF_DEVICE_ID: device_id})
+
+ hass.services.async_register(DOMAIN, SERVICE_FORCE_ACTIVATE, force_activate)
+
await async_setup_pipeline_store(hass)
await async_run_migrations(hass)
async_register_websocket_api(hass)
diff --git a/homeassistant/components/assist_pipeline/const.py b/homeassistant/components/assist_pipeline/const.py
index 36b72dad69..74dd4e9881 100644
--- a/homeassistant/components/assist_pipeline/const.py
+++ b/homeassistant/components/assist_pipeline/const.py
@@ -9,6 +9,9 @@ DEFAULT_PIPELINE_TIMEOUT = 60 * 5 # seconds
DEFAULT_WAKE_WORD_TIMEOUT = 3 # seconds
+OVERRIDE_START_EVENT_TYPE = "force_start_pipeline"
+SERVICE_FORCE_ACTIVATE = "force_activate"
+
CONF_DEBUG_RECORDING_DIR = "debug_recording_dir"
DATA_LAST_WAKE_UP = f"{DOMAIN}.last_wake_up"
diff --git a/homeassistant/components/assist_pipeline/icons.json b/homeassistant/components/assist_pipeline/icons.json
new file mode 100644
index 0000000000..4b433d30d8
--- /dev/null
+++ b/homeassistant/components/assist_pipeline/icons.json
@@ -0,0 +1,5 @@
+{
+ "services": {
+ "force_activate": "mdi:phone"
+ }
+}
diff --git a/homeassistant/components/assist_pipeline/services.yaml b/homeassistant/components/assist_pipeline/services.yaml
new file mode 100644
index 0000000000..97e0519826
--- /dev/null
+++ b/homeassistant/components/assist_pipeline/services.yaml
@@ -0,0 +1,5 @@
+# Services relating to the assist pipeline
+
+force_activate:
+ target:
+ device:
diff --git a/homeassistant/components/assist_pipeline/strings.json b/homeassistant/components/assist_pipeline/strings.json
index 8fa67879fc..6cb5cbf2b4 100644
--- a/homeassistant/components/assist_pipeline/strings.json
+++ b/homeassistant/components/assist_pipeline/strings.json
@@ -21,5 +21,11 @@
}
}
}
+ },
+ "services": {
+ "force_activate": {
+ "name": "Force Activate",
+ "description": "Force the device to activate the assistant pipeline"
+ }
}
}
diff --git a/homeassistant/components/wyoming/__init__.py b/homeassistant/components/wyoming/__init__.py
index 0d72c20233..9d62bdd9ed 100644
--- a/homeassistant/components/wyoming/__init__.py
+++ b/homeassistant/components/wyoming/__init__.py
@@ -20,7 +20,6 @@ _LOGGER = logging.getLogger(__name__)
SATELLITE_PLATFORMS = [
Platform.BINARY_SENSOR,
- Platform.BUTTON,
Platform.SELECT,
Platform.SWITCH,
Platform.NUMBER,
@@ -50,6 +49,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if (satellite_info := service.info.satellite) is not None:
# Create satellite device, etc.
item.satellite = _make_satellite(hass, entry, service)
+ hass.bus.async_listen(
+ hass.components.assist_pipeline.OVERRIDE_START_EVENT_TYPE,
+ item.satellite.device.force_activate,
+ )
# Set up satellite sensors, switches, etc.
await hass.config_entries.async_forward_entry_setups(entry, SATELLITE_PLATFORMS)
diff --git a/homeassistant/components/wyoming/button.py b/homeassistant/components/wyoming/button.py
deleted file mode 100644
index dd125097f1..0000000000
--- a/homeassistant/components/wyoming/button.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""Wyoming button entities."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, Any
-
-from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
-from homeassistant.config_entries import ConfigEntry
-from homeassistant.core import HomeAssistant
-from homeassistant.helpers.entity_platform import AddEntitiesCallback
-
-from .const import DOMAIN
-from .entity import WyomingSatelliteEntity
-
-if TYPE_CHECKING:
- from .models import DomainDataItem
-
-
-async def async_setup_entry(
- hass: HomeAssistant,
- config_entry: ConfigEntry,
- async_add_entities: AddEntitiesCallback,
-) -> None:
- """Set up button entities."""
- item: DomainDataItem = hass.data[DOMAIN][config_entry.entry_id]
-
- # Setup is only forwarded for satellites
- assert item.satellite is not None
-
- async_add_entities([WyomingSatelliteForceActivate(item.satellite.device)])
-
-
-class WyomingSatelliteForceActivate(WyomingSatelliteEntity, ButtonEntity):
- """Manually activate the satellite instead of using a wake word."""
-
- entity_description = ButtonEntityDescription(
- key="activate",
- translation_key="activat",
- )
-
- async def async_press(self, **kwargs: Any) -> None:
- """Turn on."""
- self._device.force_activate()
diff --git a/homeassistant/components/wyoming/devices.py b/homeassistant/components/wyoming/devices.py
index 44f9c77fa8..395bcb98f8 100644
--- a/homeassistant/components/wyoming/devices.py
+++ b/homeassistant/components/wyoming/devices.py
@@ -2,10 +2,10 @@
from __future__ import annotations
-from collections.abc import Callable
+from collections.abc import Callable, Coroutine
from dataclasses import dataclass
-from homeassistant.core import HomeAssistant, callback
+from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from .const import DOMAIN
@@ -26,7 +26,7 @@ class SatelliteDevice:
_is_active_listener: Callable[[], None] | None = None
_is_muted_listener: Callable[[], None] | None = None
- _force_activate_listener: Callable[[], None] | None = None
+ _force_activate_listener: Callable[[Event], Coroutine] | None = None
_pipeline_listener: Callable[[], None] | None = None
_audio_settings_listener: Callable[[], None] | None = None
@@ -47,10 +47,11 @@ class SatelliteDevice:
self._is_muted_listener()
@callback
- def force_activate(self) -> None:
+ def force_activate(self, event: Event) -> Coroutine | None:
"""Request to activate without a wake word."""
if self._force_activate_listener is not None:
- self._force_activate_listener()
+ return self._force_activate_listener(event)
+ return None
@callback
def set_pipeline_name(self, pipeline_name: str) -> None:
@@ -96,7 +97,8 @@ class SatelliteDevice:
@callback
def set_force_activate_listener(
- self, force_activate_listener: Callable[[], None]
+ self,
+ force_activate_listener: Callable,
) -> None:
"""Listen for force activate requests."""
self._force_activate_listener = force_activate_listener
diff --git a/homeassistant/components/wyoming/strings.json b/homeassistant/components/wyoming/strings.json
index 464a863d7b..f2768e45eb 100644
--- a/homeassistant/components/wyoming/strings.json
+++ b/homeassistant/components/wyoming/strings.json
@@ -53,11 +53,6 @@
"name": "Mute"
}
},
- "button": {
- "activat": {
- "name": "Activate"
- }
- },
"number": {
"auto_gain": {
"name": "Auto gain" |
@Jyumpp can you open a PR and add a link in the description to this PR? That way, we can discuss your specific changes there. Thanks! |
There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. |
Proposed change
This PR adds a server-side "force activate" button to wyoming satellite that causes the satellite to go directly to ASR (a sort of "push to talk" button). This allows to activate the satellite via any automation.
Example use-case: when my TV is on wake-word detection is very unreliable. At the same time I have a remote control in my hand. So I setup an automation that mutes the satellite when the TV is on to avoid false detections, and I configure a button of the TV remote to run another automation that lowers the TV volume, activates the satellite, and raises the volume afterwards. So I can just press the button and give whatever command I want while watching TV.
I considered various ways to implement this, here are some design considerations:
run-pipeline
is normally initiated by the satellite, not the serverSo I ended up with the following design (open to discussion, of course):
run-satellite
is sent to the satellite, withstart_stage == asr
. This event was already sent to unpause the satellite, onlystart_stage
is added.run-satellite
withstart_stage == asr
it unpauses as usual, but goes directly to streaming.run-pipeline
is sent from the satellite as usual, but withstart_stage == asr
.is_muted == True
, the server will sendpause-satellite
to re-pause the satellite.Overall this design covers the requirements with relatively small number of changes in both the server and client.
Some more notes:
Type of change
Additional information
Checklist
ruff format homeassistant tests
)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest
.requirements_all.txt
.Updated by running
python3 -m script.gen_requirements_all
..coveragerc
.To help with the load of incoming pull requests: