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 import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 .entity import SonosEntity, SonosPollingEntity
from .helpers import soco_error from .helpers import soco_error
from .speaker import SonosSpeaker 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_name = f"{self.speaker.zone_name} Audio Input Format"
self._attr_native_value = audio_format self._attr_native_value = audio_format
@soco_error()
def poll_state(self) -> None: 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.""" """Poll the device for the current state."""
self._attr_native_value = self.soco.soundbar_audio_input_format 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 from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest import pytest
from soco import SoCo
from homeassistant.components import ssdp, zeroconf from homeassistant.components import ssdp, zeroconf
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
@ -82,7 +83,9 @@ def config_entry_fixture():
@pytest.fixture(name="soco") @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.""" """Create a mock soco SoCo fixture."""
with patch("homeassistant.components.sonos.SoCo", autospec=True) as mock, patch( with patch("homeassistant.components.sonos.SoCo", autospec=True) as mock, patch(
"socket.gethostbyname", return_value="192.168.42.2" "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.uid = "RINCON_test"
mock_soco.play_mode = "NORMAL" mock_soco.play_mode = "NORMAL"
mock_soco.music_library = music_library 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.get_speaker_info.return_value = speaker_info
mock_soco.avTransport = SonosMockService("AVTransport") mock_soco.avTransport = SonosMockService("AVTransport")
mock_soco.renderingControl = SonosMockService("RenderingControl") 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") @pytest.fixture(name="battery_info")
def battery_info_fixture(): def battery_info_fixture():
"""Create battery_info fixture.""" """Create battery_info fixture."""
@ -254,6 +275,61 @@ def alarm_event_fixture(soco):
return SonosMockEvent(soco, soco.alarmClock, variables) 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) @pytest.fixture(autouse=True)
def mock_get_source_ip(mock_get_source_ip): def mock_get_source_ip(mock_get_source_ip):
"""Mock network util's async_get_source_ip in all sonos tests.""" """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.""" """Tests for the Sonos battery sensor platform."""
from unittest.mock import PropertyMock
from soco.exceptions import NotSupportedException 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.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers import entity_registry as ent_reg 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): 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 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.""" """Test audio input sensor."""
entity_registry = ent_reg.async_get(hass) 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_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"]
audio_input_state = hass.states.get(audio_input_sensor.entity_id) audio_input_state = hass.states.get(audio_input_sensor.entity_id)
assert audio_input_state.state == "Dolby 5.1" 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( async def test_microphone_binary_sensor(
hass, async_autosetup_sonos, soco, device_properties_event hass, async_autosetup_sonos, soco, device_properties_event