Skip to content
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 preview to mold_indicator #125530

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions homeassistant/components/mold_indicator/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

import voluptuous as vol

from homeassistant.components import websocket_api
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import CONF_NAME, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
Expand All @@ -22,6 +25,7 @@
NumberSelectorMode,
TextSelector,
)
from homeassistant.util.unit_system import METRIC_SYSTEM

from .const import (
CONF_CALIBRATION_FACTOR,
Expand All @@ -31,6 +35,7 @@
DEFAULT_NAME,
DOMAIN,
)
from .sensor import MoldIndicator


async def validate_duplicate(
Expand Down Expand Up @@ -75,12 +80,14 @@ async def validate_duplicate(
"user": SchemaFlowFormStep(
schema=DATA_SCHEMA_CONFIG,
validate_user_input=validate_duplicate,
preview="mold_indicator",
),
}
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(
DATA_SCHEMA_OPTIONS,
validate_user_input=validate_duplicate,
preview="mold_indicator",
)
}

Expand All @@ -94,3 +101,71 @@ class MoldIndicatorConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title."""
return cast(str, options[CONF_NAME])

@staticmethod
async def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up preview WS API."""
websocket_api.async_register_command(hass, ws_start_preview)


@websocket_api.websocket_command(
{
vol.Required("type"): "mold_indicator/start_preview",
vol.Required("flow_id"): str,
vol.Required("flow_type"): vol.Any("config_flow", "options_flow"),
vol.Required("user_input"): dict,
}
)
@callback
def ws_start_preview(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Generate a preview."""

if msg["flow_type"] == "config_flow":
flow_status = hass.config_entries.flow.async_get(msg["flow_id"])
flow_sets = hass.config_entries.flow._handler_progress_index.get( # noqa: SLF001
flow_status["handler"]
)
assert flow_sets
config_entry = hass.config_entries.async_get_entry(flow_status["handler"])
indoor_temp = msg["user_input"].get(CONF_INDOOR_TEMP)
outdoor_temp = msg["user_input"].get(CONF_OUTDOOR_TEMP)
indoor_hum = msg["user_input"].get(CONF_INDOOR_HUMIDITY)
name = msg["user_input"].get(CONF_NAME)
else:
flow_status = hass.config_entries.options.async_get(msg["flow_id"])
config_entry = hass.config_entries.async_get_entry(flow_status["handler"])
if not config_entry:
raise HomeAssistantError("Config entry not found")
indoor_temp = config_entry.options[CONF_INDOOR_TEMP]
outdoor_temp = config_entry.options[CONF_OUTDOOR_TEMP]
indoor_hum = config_entry.options[CONF_INDOOR_HUMIDITY]
name = config_entry.options[CONF_NAME]

@callback
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
"""Forward config entry state events to websocket."""
connection.send_message(
websocket_api.event_message(
msg["id"], {"attributes": attributes, "state": state}
)
)

preview_entity = MoldIndicator(
hass,
name,
hass.config.units is METRIC_SYSTEM,
indoor_temp,
outdoor_temp,
indoor_hum,
msg["user_input"].get(CONF_CALIBRATION_FACTOR),
)
preview_entity.hass = hass

connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(
async_preview_updated
)
73 changes: 60 additions & 13 deletions homeassistant/components/mold_indicator/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections.abc import Callable, Mapping
import logging
import math
from typing import TYPE_CHECKING, Any
Expand All @@ -19,12 +20,12 @@
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT,
CONF_NAME,
EVENT_HOMEASSISTANT_START,
PERCENTAGE,
STATE_UNKNOWN,
UnitOfTemperature,
)
from homeassistant.core import (
CALLBACK_TYPE,
Event,
EventStateChangedData,
HomeAssistant,
Expand Down Expand Up @@ -156,19 +157,48 @@
indoor_humidity_sensor,
outdoor_temp_sensor,
}

self._dewpoint: float | None = None
self._indoor_temp: float | None = None
self._outdoor_temp: float | None = None
self._indoor_hum: float | None = None
self._crit_temp: float | None = None
self._attr_device_info = async_device_info_to_link_from_entity(
hass,
indoor_humidity_sensor,
)
if indoor_humidity_sensor:
self._attr_device_info = async_device_info_to_link_from_entity(
hass,
indoor_humidity_sensor,
)
self._preview_callback: Callable[[str, Mapping[str, Any]], None] | None = None

@callback
def async_start_preview(
self,
preview_callback: Callable[[str, Mapping[str, Any]], None],
) -> CALLBACK_TYPE:
"""Render a preview."""
# Abort early if there is no source entity_id's or calibration factor
if (
not self._outdoor_temp_sensor
or not self._indoor_temp_sensor
or not self._indoor_humidity_sensor
or not self._calib_factor
):
self._attr_available = False
calculated_state = self._async_calculate_state()
preview_callback(calculated_state.state, calculated_state.attributes)
return self._call_on_remove_callbacks

self._preview_callback = preview_callback

self._async_setup_sensor()
return self._call_on_remove_callbacks

async def async_added_to_hass(self) -> None:
"""Register callbacks."""
"""Run when entity about to be added to hass."""
self._async_setup_sensor()

@callback
def _async_setup_sensor(self) -> None:
"""Set up the sensor and start tracking state changes."""

@callback
def mold_indicator_sensors_state_listener(
Expand All @@ -186,10 +216,17 @@
)

if self._update_sensor(entity, old_state, new_state):
self.async_schedule_update_ha_state(True)
if self._preview_callback:
calculated_state = self._async_calculate_state()
self._preview_callback(

Check warning on line 221 in homeassistant/components/mold_indicator/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mold_indicator/sensor.py#L220-L221

Added lines #L220 - L221 were not covered by tests
calculated_state.state, calculated_state.attributes
)
# only write state to the state machine if we are not in preview mode
else:
self.async_schedule_update_ha_state(True)

@callback
def mold_indicator_startup(event: Event) -> None:
def mold_indicator_startup() -> None:
"""Add listeners and get 1st state."""
_LOGGER.debug("Startup for %s", self.entity_id)

Expand Down Expand Up @@ -222,12 +259,22 @@
else schedule_update
)

if schedule_update:
if schedule_update and not self._preview_callback:
self.async_schedule_update_ha_state(True)
if self._preview_callback:
# re-calculate dewpoint and mold indicator
self._calc_dewpoint()
self._calc_moldindicator()
if self._state is None:
self._attr_available = False

Check warning on line 269 in homeassistant/components/mold_indicator/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mold_indicator/sensor.py#L269

Added line #L269 was not covered by tests
else:
self._attr_available = True
calculated_state = self._async_calculate_state()
self._preview_callback(
calculated_state.state, calculated_state.attributes
)

self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, mold_indicator_startup
)
mold_indicator_startup()

def _update_sensor(
self, entity: str, old_state: State | None, new_state: State | None
Expand Down
49 changes: 49 additions & 0 deletions tests/components/mold_indicator/snapshots/test_config_flow.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# serializer version: 1
# name: test_config_flow_preview_success[missing_calibration_factor]
dict({
'attributes': dict({
'device_class': 'humidity',
'friendly_name': 'Mold Indicator',
'state_class': 'measurement',
'unit_of_measurement': '%',
}),
'state': 'unavailable',
})
# ---
# name: test_config_flow_preview_success[missing_humidity_entity]
dict({
'attributes': dict({
'device_class': 'humidity',
'friendly_name': 'Mold Indicator',
'state_class': 'measurement',
'unit_of_measurement': '%',
}),
'state': 'unavailable',
})
# ---
# name: test_config_flow_preview_success[success]
dict({
'attributes': dict({
'device_class': 'humidity',
'dewpoint': 12.01,
'estimated_critical_temp': 19.5,
'friendly_name': 'Mold Indicator',
'state_class': 'measurement',
'unit_of_measurement': '%',
}),
'state': '61',
})
# ---
# name: test_options_flow_preview
dict({
'attributes': dict({
'device_class': 'humidity',
'dewpoint': 12.01,
'estimated_critical_temp': 19.5,
'friendly_name': 'Mold Indicator',
'state_class': 'measurement',
'unit_of_measurement': '%',
}),
'state': '61',
})
# ---
Loading