Skip polling Sonos audio input sensor when idle (#66271)

This commit is contained in:
jjlawren 2022-02-10 14:48:13 -06:00 committed by GitHub
parent c6f3c5da79
commit 4d944e35fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 4 deletions

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY
from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY, SOURCE_TV
from .entity import SonosEntity, SonosPollingEntity
from .helpers import soco_error
from .speaker import SonosSpeaker
@ -94,8 +94,14 @@ class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity):
self._attr_name = f"{self.speaker.zone_name} Audio Input Format"
self._attr_native_value = audio_format
@soco_error()
def poll_state(self) -> None:
"""Poll the state if TV source is active and state has settled."""
if self.speaker.media.source_name != SOURCE_TV and self.state == "No input":
return
self._poll_state()
@soco_error()
def _poll_state(self) -> None:
"""Poll the device for the current state."""
self._attr_native_value = self.soco.soundbar_audio_input_format

View File

@ -3,6 +3,7 @@ from copy import copy
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from soco import SoCo
from homeassistant.components import ssdp, zeroconf
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
@ -82,7 +83,9 @@ def config_entry_fixture():
@pytest.fixture(name="soco")
def soco_fixture(music_library, speaker_info, battery_info, alarm_clock):
def soco_fixture(
music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock
):
"""Create a mock soco SoCo fixture."""
with patch("homeassistant.components.sonos.SoCo", autospec=True) as mock, patch(
"socket.gethostbyname", return_value="192.168.42.2"
@ -92,6 +95,8 @@ def soco_fixture(music_library, speaker_info, battery_info, alarm_clock):
mock_soco.uid = "RINCON_test"
mock_soco.play_mode = "NORMAL"
mock_soco.music_library = music_library
mock_soco.get_current_track_info.return_value = current_track_info_empty
mock_soco.music_source_from_uri = SoCo.music_source_from_uri
mock_soco.get_speaker_info.return_value = speaker_info
mock_soco.avTransport = SonosMockService("AVTransport")
mock_soco.renderingControl = SonosMockService("RenderingControl")
@ -216,6 +221,22 @@ def speaker_info_fixture():
}
@pytest.fixture(name="current_track_info_empty")
def current_track_info_empty_fixture():
"""Create current_track_info_empty fixture."""
return {
"title": "",
"artist": "",
"album": "",
"album_art": "",
"position": "NOT_IMPLEMENTED",
"playlist_position": "1",
"duration": "NOT_IMPLEMENTED",
"uri": "",
"metadata": "NOT_IMPLEMENTED",
}
@pytest.fixture(name="battery_info")
def battery_info_fixture():
"""Create battery_info fixture."""
@ -254,6 +275,61 @@ def alarm_event_fixture(soco):
return SonosMockEvent(soco, soco.alarmClock, variables)
@pytest.fixture(name="no_media_event")
def no_media_event_fixture(soco):
"""Create no_media_event_fixture."""
variables = {
"current_crossfade_mode": "0",
"current_play_mode": "NORMAL",
"current_section": "0",
"current_track_uri": "",
"enqueued_transport_uri": "",
"enqueued_transport_uri_meta_data": "",
"transport_state": "STOPPED",
}
return SonosMockEvent(soco, soco.avTransport, variables)
@pytest.fixture(name="tv_event")
def tv_event_fixture(soco):
"""Create alarm_event fixture."""
variables = {
"transport_state": "PLAYING",
"current_play_mode": "NORMAL",
"current_crossfade_mode": "0",
"number_of_tracks": "1",
"current_track": "1",
"current_section": "0",
"current_track_uri": f"x-sonos-htastream:{soco.uid}:spdif",
"current_track_duration": "",
"current_track_meta_data": {
"title": " ",
"parent_id": "-1",
"item_id": "-1",
"restricted": True,
"resources": [],
"desc": None,
},
"next_track_uri": "",
"next_track_meta_data": "",
"enqueued_transport_uri": "",
"enqueued_transport_uri_meta_data": "",
"playback_storage_medium": "NETWORK",
"av_transport_uri": f"x-sonos-htastream:{soco.uid}:spdif",
"av_transport_uri_meta_data": {
"title": soco.uid,
"parent_id": "0",
"item_id": "spdif-input",
"restricted": False,
"resources": [],
"desc": None,
},
"current_transport_actions": "Set, Play",
"current_valid_play_modes": "",
}
return SonosMockEvent(soco, soco.avTransport, variables)
@pytest.fixture(autouse=True)
def mock_get_source_ip(mock_get_source_ip):
"""Mock network util's async_get_source_ip in all sonos tests."""

View File

@ -1,9 +1,15 @@
"""Tests for the Sonos battery sensor platform."""
from unittest.mock import PropertyMock
from soco.exceptions import NotSupportedException
from homeassistant.components.sensor import SCAN_INTERVAL
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers import entity_registry as ent_reg
from homeassistant.util import dt as dt_util
from tests.common import async_fire_time_changed
async def test_entity_registry_unsupported(hass, async_setup_sonos, soco):
@ -113,14 +119,46 @@ async def test_device_payload_without_battery_and_ignored_keys(
assert ignored_payload not in caplog.text
async def test_audio_input_sensor(hass, async_autosetup_sonos, soco):
async def test_audio_input_sensor(
hass, async_autosetup_sonos, soco, tv_event, no_media_event
):
"""Test audio input sensor."""
entity_registry = ent_reg.async_get(hass)
subscription = soco.avTransport.subscribe.return_value
sub_callback = subscription.callback
sub_callback(tv_event)
await hass.async_block_till_done()
audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"]
audio_input_state = hass.states.get(audio_input_sensor.entity_id)
assert audio_input_state.state == "Dolby 5.1"
# Set mocked input format to new value and ensure poll success
no_input_mock = PropertyMock(return_value="No input")
type(soco).soundbar_audio_input_format = no_input_mock
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()
no_input_mock.assert_called_once()
audio_input_state = hass.states.get(audio_input_sensor.entity_id)
assert audio_input_state.state == "No input"
# Ensure state is not polled when source is not TV and state is already "No input"
sub_callback(no_media_event)
await hass.async_block_till_done()
unpolled_mock = PropertyMock(return_value="Will not be polled")
type(soco).soundbar_audio_input_format = unpolled_mock
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()
unpolled_mock.assert_not_called()
audio_input_state = hass.states.get(audio_input_sensor.entity_id)
assert audio_input_state.state == "No input"
async def test_microphone_binary_sensor(
hass, async_autosetup_sonos, soco, device_properties_event