LaCrosse View new endpoint (#137284)

* Switch to new endpoint in LaCrosse View

* Coverage

* Avoid merge conflict

* Switch to UpdateFailed
This commit is contained in:
IceBotYT 2025-02-08 03:14:00 -05:00 committed by GitHub
parent 64886f717d
commit 332a0c5082
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 151 additions and 45 deletions

View File

@ -10,8 +10,8 @@ from lacrosse_view import HTTPError, LaCrosse, Location, LoginError, Sensor
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import SCAN_INTERVAL from .const import SCAN_INTERVAL
@ -26,6 +26,7 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
name: str name: str
id: str id: str
hass: HomeAssistant hass: HomeAssistant
devices: list[Sensor] | None = None
def __init__( def __init__(
self, self,
@ -60,24 +61,34 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
except LoginError as error: except LoginError as error:
raise ConfigEntryAuthFailed from error raise ConfigEntryAuthFailed from error
if self.devices is None:
_LOGGER.debug("Getting devices")
try:
self.devices = await self.api.get_devices(
location=Location(id=self.id, name=self.name),
)
except HTTPError as error:
raise UpdateFailed from error
try: try:
# Fetch last hour of data # Fetch last hour of data
sensors = await self.api.get_sensors( for sensor in self.devices:
location=Location(id=self.id, name=self.name), sensor.data = (
tz=self.hass.config.time_zone, await self.api.get_sensor_status(
start=str(now - 3600), sensor=sensor,
end=str(now), tz=self.hass.config.time_zone,
) )
except HTTPError as error: )["data"]["current"]
raise ConfigEntryNotReady from error _LOGGER.debug("Got data: %s", sensor.data)
_LOGGER.debug("Got data: %s", sensors) except HTTPError as error:
raise UpdateFailed from error
# Verify that we have permission to read the sensors # Verify that we have permission to read the sensors
for sensor in sensors: for sensor in self.devices:
if not sensor.permissions.get("read", False): if not sensor.permissions.get("read", False):
raise ConfigEntryAuthFailed( raise ConfigEntryAuthFailed(
f"This account does not have permission to read {sensor.name}" f"This account does not have permission to read {sensor.name}"
) )
return sensors return self.devices

View File

@ -48,7 +48,7 @@ def get_value(sensor: Sensor, field: str) -> float | int | str | None:
field_data = sensor.data.get(field) if sensor.data is not None else None field_data = sensor.data.get(field) if sensor.data is not None else None
if field_data is None: if field_data is None:
return None return None
value = field_data["values"][-1]["s"] value = field_data["spot"]["value"]
try: try:
value = float(value) value = float(value)
except ValueError: except ValueError:

View File

@ -15,7 +15,13 @@ TEST_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"Temperature": {"values": [{"s": "2"}], "unit": "degrees_celsius"}}, data={
"data": {
"current": {
"Temperature": {"spot": {"value": "2"}, "unit": "degrees_celsius"}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -26,7 +32,13 @@ TEST_NO_PERMISSION_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"Temperature": {"values": [{"s": "2"}], "unit": "degrees_celsius"}}, data={
"data": {
"current": {
"Temperature": {"spot": {"value": "2"}, "unit": "degrees_celsius"}
}
}
},
permissions={"read": False}, permissions={"read": False},
model="Test", model="Test",
) )
@ -37,7 +49,16 @@ TEST_UNSUPPORTED_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["SomeUnsupportedField"], sensor_field_names=["SomeUnsupportedField"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"SomeUnsupportedField": {"values": [{"s": "2"}], "unit": "degrees_celsius"}}, data={
"data": {
"current": {
"SomeUnsupportedField": {
"spot": {"value": "2"},
"unit": "degrees_celsius",
}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -48,7 +69,13 @@ TEST_FLOAT_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"Temperature": {"values": [{"s": "2.3"}], "unit": "degrees_celsius"}}, data={
"data": {
"current": {
"Temperature": {"spot": {"value": "2.3"}, "unit": "degrees_celsius"}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -59,7 +86,9 @@ TEST_STRING_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["WetDry"], sensor_field_names=["WetDry"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"WetDry": {"values": [{"s": "dry"}], "unit": "wet_dry"}}, data={
"data": {"current": {"WetDry": {"spot": {"value": "dry"}, "unit": "wet_dry"}}}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -70,7 +99,13 @@ TEST_ALREADY_FLOAT_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["HeatIndex"], sensor_field_names=["HeatIndex"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"HeatIndex": {"values": [{"s": 2.3}], "unit": "degrees_fahrenheit"}}, data={
"data": {
"current": {
"HeatIndex": {"spot": {"value": 2.3}, "unit": "degrees_fahrenheit"}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -81,7 +116,13 @@ TEST_ALREADY_INT_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["WindSpeed"], sensor_field_names=["WindSpeed"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"WindSpeed": {"values": [{"s": 2}], "unit": "kilometers_per_hour"}}, data={
"data": {
"current": {
"WindSpeed": {"spot": {"value": 2}, "unit": "kilometers_per_hour"}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -92,7 +133,7 @@ TEST_NO_FIELD_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={}, data={"data": {"current": {}}},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -103,7 +144,7 @@ TEST_MISSING_FIELD_DATA_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"Temperature": None}, data={"data": {"current": {"Temperature": None}}},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )
@ -114,7 +155,13 @@ TEST_UNITS_OVERRIDE_SENSOR = Sensor(
sensor_id="2", sensor_id="2",
sensor_field_names=["Temperature"], sensor_field_names=["Temperature"],
location=Location(id="1", name="Test"), location=Location(id="1", name="Test"),
data={"Temperature": {"values": [{"s": "2.1"}], "unit": "degrees_fahrenheit"}}, data={
"data": {
"current": {
"Temperature": {"spot": {"value": "2.1"}, "unit": "degrees_fahrenheit"}
}
}
},
permissions={"read": True}, permissions={"read": True},
model="Test", model="Test",
) )

View File

@ -4,7 +4,7 @@
'coordinator_data': list([ 'coordinator_data': list([
dict({ dict({
'__type': "<class 'lacrosse_view.Sensor'>", '__type': "<class 'lacrosse_view.Sensor'>",
'repr': "Sensor(name='Test', device_id='1', type='Test', sensor_id='2', sensor_field_names=['Temperature'], location=Location(id='1', name='Test'), permissions={'read': True}, model='Test', data={'Temperature': {'values': [{'s': '2'}], 'unit': 'degrees_celsius'}})", 'repr': "Sensor(name='Test', device_id='1', type='Test', sensor_id='2', sensor_field_names=['Temperature'], location=Location(id='1', name='Test'), permissions={'read': True}, model='Test', data={'Temperature': {'spot': {'value': '2'}, 'unit': 'degrees_celsius'}})",
}), }),
]), ]),
'entry': dict({ 'entry': dict({

View File

@ -26,9 +26,14 @@ async def test_entry_diagnostics(
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch("lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR]), patch("lacrosse_view.LaCrosse.get_devices", return_value=[sensor]),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -20,12 +20,17 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -68,7 +73,7 @@ async def test_http_error(hass: HomeAssistant) -> None:
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch("lacrosse_view.LaCrosse.get_sensors", side_effect=HTTPError), patch("lacrosse_view.LaCrosse.get_devices", side_effect=HTTPError),
): ):
assert not await hass.config_entries.async_setup(config_entry.entry_id) assert not await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -84,12 +89,17 @@ async def test_new_token(hass: HomeAssistant, freezer: FrozenDateTimeFactory) ->
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch("lacrosse_view.LaCrosse.login", return_value=True) as login,
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -103,7 +113,7 @@ async def test_new_token(hass: HomeAssistant, freezer: FrozenDateTimeFactory) ->
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch("lacrosse_view.LaCrosse.login", return_value=True) as login,
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_SENSOR], return_value=[TEST_SENSOR],
), ),
): ):
@ -121,12 +131,17 @@ async def test_failed_token(
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch("lacrosse_view.LaCrosse.login", return_value=True) as login,
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -32,9 +32,14 @@ async def test_entities_added(hass: HomeAssistant) -> None:
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch("lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR]), patch("lacrosse_view.LaCrosse.get_devices", return_value=[sensor]),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -54,12 +59,17 @@ async def test_sensor_permission(
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_NO_PERMISSION_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_NO_PERMISSION_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert not await hass.config_entries.async_setup(config_entry.entry_id) assert not await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -79,11 +89,14 @@ async def test_field_not_supported(
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_UNSUPPORTED_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch("lacrosse_view.LaCrosse.get_devices", return_value=[sensor]),
"lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_UNSUPPORTED_SENSOR] patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -114,12 +127,17 @@ async def test_field_types(
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = test_input.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[test_input], return_value=[test_input],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -137,12 +155,17 @@ async def test_no_field(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_NO_FIELD_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_NO_FIELD_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -160,12 +183,17 @@ async def test_field_data_missing(hass: HomeAssistant) -> None:
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA)
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
sensor = TEST_MISSING_FIELD_DATA_SENSOR.model_copy()
status = sensor.data
sensor.data = None
with ( with (
patch("lacrosse_view.LaCrosse.login", return_value=True), patch("lacrosse_view.LaCrosse.login", return_value=True),
patch( patch(
"lacrosse_view.LaCrosse.get_sensors", "lacrosse_view.LaCrosse.get_devices",
return_value=[TEST_MISSING_FIELD_DATA_SENSOR], return_value=[sensor],
), ),
patch("lacrosse_view.LaCrosse.get_sensor_status", return_value=status),
): ):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()