Add switch support to the Flo integration (#38268)
* Add switch domain to Flo integration * lint * updates post rebase * fix after rebase * remove device state attrs * oops * stale name and docstring
This commit is contained in:
parent
52a9921ed3
commit
ca5e752514
7 changed files with 357 additions and 1 deletions
|
@ -19,7 +19,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["binary_sensor", "sensor"]
|
||||
PLATFORMS = ["binary_sensor", "sensor", "switch"]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
|
|
|
@ -162,6 +162,16 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
|||
or self.pending_warning_alerts_count
|
||||
)
|
||||
|
||||
@property
|
||||
def last_known_valve_state(self) -> str:
|
||||
"""Return the last known valve state for the device."""
|
||||
return self._device_information["valve"]["lastKnown"]
|
||||
|
||||
@property
|
||||
def target_valve_state(self) -> str:
|
||||
"""Return the target valve state for the device."""
|
||||
return self._device_information["valve"]["target"]
|
||||
|
||||
async def _update_device(self, *_) -> None:
|
||||
"""Update the device information from the API."""
|
||||
self._device_information = await self.api_client.device.get_info(
|
||||
|
|
59
homeassistant/components/flo/switch.py
Normal file
59
homeassistant/components/flo/switch.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""Switch representing the shutoff valve for the Flo by Moen integration."""
|
||||
|
||||
from typing import List
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import DOMAIN as FLO_DOMAIN
|
||||
from .device import FloDeviceDataUpdateCoordinator
|
||||
from .entity import FloEntity
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Flo switches from config entry."""
|
||||
devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN]["devices"]
|
||||
async_add_entities([FloSwitch(device) for device in devices])
|
||||
|
||||
|
||||
class FloSwitch(FloEntity, SwitchEntity):
|
||||
"""Switch class for the Flo by Moen valve."""
|
||||
|
||||
def __init__(self, device: FloDeviceDataUpdateCoordinator):
|
||||
"""Initialize the Flo switch."""
|
||||
super().__init__("shutoff_valve", "Shutoff Valve", device)
|
||||
self._state = self._device.last_known_valve_state == "open"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the valve is open."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use for the valve."""
|
||||
if self.is_on:
|
||||
return "mdi:valve-open"
|
||||
return "mdi:valve-closed"
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Open the valve."""
|
||||
await self._device.api_client.device.open_valve(self._device.id)
|
||||
self._state = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Close the valve."""
|
||||
await self._device.api_client.device.close_valve(self._device.id)
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_update_state(self) -> None:
|
||||
"""Retrieve the latest valve state and update the state machine."""
|
||||
self._state = self._device.last_known_valve_state == "open"
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.async_on_remove(self._device.async_add_listener(self.async_update_state))
|
|
@ -79,3 +79,19 @@ def aioclient_mock_fixture(aioclient_mock):
|
|||
status=200,
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
# Mocks the valve open call for flo.
|
||||
aioclient_mock.post(
|
||||
"https://api-gw.meetflo.com/api/v2/devices/98765",
|
||||
text=load_fixture("flo/device_info_response.json"),
|
||||
status=200,
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"valve": {"target": "open"}},
|
||||
)
|
||||
# Mocks the valve close call for flo.
|
||||
aioclient_mock.post(
|
||||
"https://api-gw.meetflo.com/api/v2/devices/98765",
|
||||
text=load_fixture("flo/device_info_response_closed.json"),
|
||||
status=200,
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"valve": {"target": "closed"}},
|
||||
)
|
||||
|
|
|
@ -45,6 +45,8 @@ async def test_device(hass, config_entry, aioclient_mock_fixture, aioclient_mock
|
|||
assert device.pending_critical_alerts_count == 0
|
||||
assert device.pending_warning_alerts_count == 2
|
||||
assert device.has_alerts is True
|
||||
assert device.last_known_valve_state == "open"
|
||||
assert device.target_valve_state == "open"
|
||||
|
||||
call_count = aioclient_mock.call_count
|
||||
|
||||
|
|
31
tests/components/flo/test_switch.py
Normal file
31
tests/components/flo/test_switch.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""Tests for the switch domain for Flo by Moen."""
|
||||
from homeassistant.components.flo.const import DOMAIN as FLO_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, STATE_OFF, STATE_ON
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import TEST_PASSWORD, TEST_USER_ID
|
||||
|
||||
|
||||
async def test_valve_switches(hass, config_entry, aioclient_mock_fixture):
|
||||
"""Test Flo by Moen valve switches."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(
|
||||
hass, FLO_DOMAIN, {CONF_USERNAME: TEST_USER_ID, CONF_PASSWORD: TEST_PASSWORD}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.data[FLO_DOMAIN]["devices"]) == 1
|
||||
|
||||
entity_id = "switch.shutoff_valve"
|
||||
assert hass.states.get(entity_id).state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True
|
||||
)
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True
|
||||
)
|
||||
assert hass.states.get(entity_id).state == STATE_ON
|
238
tests/fixtures/flo/device_info_response_closed.json
vendored
Normal file
238
tests/fixtures/flo/device_info_response_closed.json
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
{
|
||||
"isConnected": true,
|
||||
"fwVersion": "6.1.1",
|
||||
"lastHeardFromTime": "2020-07-24T12:45:00Z",
|
||||
"fwProperties": {
|
||||
"alarm_away_high_flow_rate_shut_off_enabled": true,
|
||||
"alarm_away_high_water_use_shut_off_enabled": true,
|
||||
"alarm_away_long_flow_event_shut_off_enabled": true,
|
||||
"alarm_away_v2_shut_off_enabled": true,
|
||||
"alarm_home_high_flow_rate_shut_off_deferment": 300,
|
||||
"alarm_home_high_flow_rate_shut_off_enabled": true,
|
||||
"alarm_home_high_water_use_shut_off_deferment": 300,
|
||||
"alarm_home_high_water_use_shut_off_enabled": true,
|
||||
"alarm_home_long_flow_event_shut_off_deferment": 300,
|
||||
"alarm_home_long_flow_event_shut_off_enabled": true,
|
||||
"alarm_shut_off_enabled": true,
|
||||
"alarm_shutoff_id": "",
|
||||
"alarm_shutoff_time_epoch_sec": -1,
|
||||
"alarm_snooze_enabled": true,
|
||||
"alarm_suppress_duplicate_duration": 300,
|
||||
"alarm_suppress_until_event_end": false,
|
||||
"data_flosense_force_retrain": 1,
|
||||
"data_flosense_min_flodetect_sec": 0,
|
||||
"data_flosense_min_irr_sec": 180,
|
||||
"data_flosense_status_interval": 1200,
|
||||
"data_flosense_verbosity": 1,
|
||||
"device_data_free_mb": 1465,
|
||||
"device_installed": true,
|
||||
"device_mem_available_kb": 339456,
|
||||
"device_rootfs_free_kb": 711504,
|
||||
"device_uptime_sec": 867190,
|
||||
"feature_mode": "default",
|
||||
"flodetect_post_enabled": true,
|
||||
"flodetect_post_frequency": 0,
|
||||
"flodetect_storage_days": 60,
|
||||
"flosense_action": "",
|
||||
"flosense_deployment_result": "success",
|
||||
"flosense_link": "",
|
||||
"flosense_shut_off_enabled": true,
|
||||
"flosense_shut_off_level": 3,
|
||||
"flosense_state": "active",
|
||||
"flosense_version_app": "2.5.3",
|
||||
"flosense_version_model": "2.5.0",
|
||||
"fw_ver": "6.1.1",
|
||||
"fw_ver_a": "6.1.1",
|
||||
"fw_ver_b": "6.0.3",
|
||||
"heartbeat_frequency": 1800,
|
||||
"ht_attempt_interval": 60000,
|
||||
"ht_check_window_max_pressure_decay_limit": 0.1,
|
||||
"ht_check_window_width": 30000,
|
||||
"ht_controller": "ultima",
|
||||
"ht_max_open_closed_pressure_decay_pct_limit": 2,
|
||||
"ht_max_pressure_growth_limit": 3,
|
||||
"ht_max_pressure_growth_pct_limit": 3,
|
||||
"ht_max_valve_closures_per_24h": 0,
|
||||
"ht_min_computable_point_limit": 3,
|
||||
"ht_min_pressure_limit": 10,
|
||||
"ht_min_r_squared_limit": 0.9,
|
||||
"ht_min_slope_limit": -0.6,
|
||||
"ht_phase_1_max_pressure_decay_limit": 6,
|
||||
"ht_phase_1_max_pressure_decay_pct_limit": 10,
|
||||
"ht_phase_1_time_index": 12000,
|
||||
"ht_phase_2_max_pressure_decay_limit": 6,
|
||||
"ht_phase_2_max_pressure_decay_pct_limit": 10,
|
||||
"ht_phase_2_time_index": 30000,
|
||||
"ht_phase_3_max_pressure_decay_limit": 3,
|
||||
"ht_phase_3_max_pressure_decay_pct_limit": 5,
|
||||
"ht_phase_3_time_index": 240000,
|
||||
"ht_phase_4_max_pressure_decay_limit": 1.5,
|
||||
"ht_phase_4_max_pressure_decay_pct_limit": 5,
|
||||
"ht_phase_4_time_index": 480000,
|
||||
"ht_pre_delay": 0,
|
||||
"ht_recent_flow_event_cool_down": 1000,
|
||||
"ht_retry_on_fail_interval": 900000,
|
||||
"ht_scheduler": "flosense",
|
||||
"ht_scheduler_end": "08:00",
|
||||
"ht_scheduler_start": "06:00",
|
||||
"ht_scheduler_ultima_allotted_time_1": "06:00",
|
||||
"ht_scheduler_ultima_allotted_time_2": "07:00",
|
||||
"ht_scheduler_ultima_allotted_time_3": "",
|
||||
"ht_times_per_day": 1,
|
||||
"log_bytes_sent": 0,
|
||||
"log_enabled": true,
|
||||
"log_frequency": 3600,
|
||||
"log_send": false,
|
||||
"mender_check": false,
|
||||
"mender_host": "https://mender.flotech.co",
|
||||
"mender_parts_link": "",
|
||||
"mender_ping_delay": 300,
|
||||
"mender_signature": "20200610",
|
||||
"motor_delay_close": 175,
|
||||
"motor_delay_open": 0,
|
||||
"motor_retry_count": 2,
|
||||
"motor_timeout": 5000,
|
||||
"mqtt_host": "mqtt.flosecurecloud.com",
|
||||
"mqtt_port": 8884,
|
||||
"pes_away_max_duration": 1505,
|
||||
"pes_away_max_pressure": 150,
|
||||
"pes_away_max_temperature": 226,
|
||||
"pes_away_max_volume": 91.8913240498193,
|
||||
"pes_away_min_pressure": 20,
|
||||
"pes_away_min_pressure_duration": 5,
|
||||
"pes_away_min_temperature": 36,
|
||||
"pes_away_min_temperature_duration": 10,
|
||||
"pes_away_v1_high_flow_rate": 7.825131772346,
|
||||
"pes_away_v1_high_flow_rate_duration": 5,
|
||||
"pes_away_v2_high_flow_rate": 0.5,
|
||||
"pes_away_v2_high_flow_rate_duration": 5,
|
||||
"pes_home_high_flow_rate": 1000,
|
||||
"pes_home_high_flow_rate_duration": 20,
|
||||
"pes_home_max_duration": 7431,
|
||||
"pes_home_max_pressure": 150,
|
||||
"pes_home_max_temperature": 226,
|
||||
"pes_home_max_volume": 185.56459045410156,
|
||||
"pes_home_min_pressure": 20,
|
||||
"pes_home_min_pressure_duration": 5,
|
||||
"pes_home_min_temperature": 36,
|
||||
"pes_home_min_temperature_duration": 10,
|
||||
"pes_moderately_high_pressure": 80,
|
||||
"pes_moderately_high_pressure_count": 43200,
|
||||
"pes_moderately_high_pressure_delay": 300,
|
||||
"pes_moderately_high_pressure_period": 10,
|
||||
"player_action": "disabled",
|
||||
"player_flow": 0,
|
||||
"player_min_pressure": 40,
|
||||
"player_pressure": 60,
|
||||
"player_temperature": 50,
|
||||
"power_downtime_last_24h": 91,
|
||||
"power_downtime_last_7days": 91,
|
||||
"power_downtime_last_reboot": 91,
|
||||
"pt_state": "ok",
|
||||
"reboot_count": 26,
|
||||
"reboot_count_7days": 1,
|
||||
"reboot_reason": "power_cycle",
|
||||
"s3_bucket_host": "api-bulk.meetflo.com",
|
||||
"serial_number": "111111111111",
|
||||
"system_mode": 2,
|
||||
"tag": "",
|
||||
"telemetry_batched_enabled": true,
|
||||
"telemetry_batched_hf_enabled": true,
|
||||
"telemetry_batched_hf_interval": 10800,
|
||||
"telemetry_batched_hf_poll_rate": 100,
|
||||
"telemetry_batched_interval": 300,
|
||||
"telemetry_batched_pending_storage": 30,
|
||||
"telemetry_batched_sent_storage": 30,
|
||||
"telemetry_flow_rate": 0,
|
||||
"telemetry_pressure": 42.4,
|
||||
"telemetry_realtime_change_gpm": 0,
|
||||
"telemetry_realtime_change_psi": 0,
|
||||
"telemetry_realtime_enabled": true,
|
||||
"telemetry_realtime_interval": 1,
|
||||
"telemetry_realtime_packet_uptime": 0,
|
||||
"telemetry_realtime_session_last_epoch": 1595555701518,
|
||||
"telemetry_realtime_sessions_7days": 25,
|
||||
"telemetry_realtime_storage": 7,
|
||||
"telemetry_realtime_timeout": 300,
|
||||
"telemetry_temperature": 68,
|
||||
"valve_actuation_count": 906,
|
||||
"valve_actuation_timeout_count": 0,
|
||||
"valve_state": 1,
|
||||
"vpn_enabled": false,
|
||||
"vpn_ip": "",
|
||||
"water_event_enabled": false,
|
||||
"water_event_min_duration": 2,
|
||||
"water_event_min_gallons": 0.1,
|
||||
"wifi_bytes_received": 24164,
|
||||
"wifi_bytes_sent": 18319,
|
||||
"wifi_disconnections": 76,
|
||||
"wifi_rssi": -50,
|
||||
"wifi_sta_enc": "psk2",
|
||||
"wifi_sta_ip": "192.168.1.1",
|
||||
"wifi_sta_ssid": "SOMESSID",
|
||||
"zit_auto_count": 2363,
|
||||
"zit_manual_count": 0
|
||||
},
|
||||
"id": "98765",
|
||||
"macAddress": "111111111111",
|
||||
"nickname": "Smart Water Shutoff",
|
||||
"isPaired": true,
|
||||
"deviceModel": "flo_device_075_v2",
|
||||
"deviceType": "flo_device_v2",
|
||||
"irrigationType": "sprinklers",
|
||||
"systemMode": {
|
||||
"isLocked": false,
|
||||
"shouldInherit": true,
|
||||
"lastKnown": "home",
|
||||
"target": "home"
|
||||
},
|
||||
"valve": { "target": "closed", "lastKnown": "closed" },
|
||||
"installStatus": {
|
||||
"isInstalled": true,
|
||||
"installDate": "2019-05-04T13:50:04.758Z"
|
||||
},
|
||||
"learning": { "outOfLearningDate": "2019-05-10T21:45:48.916Z" },
|
||||
"notifications": {
|
||||
"pending": {
|
||||
"infoCount": 0,
|
||||
"warningCount": 2,
|
||||
"criticalCount": 0,
|
||||
"alarmCount": [
|
||||
{ "id": 30, "severity": "warning", "count": 1 },
|
||||
{ "id": 31, "severity": "warning", "count": 1 }
|
||||
],
|
||||
"info": { "count": 0, "devices": { "count": 0, "absolute": 0 } },
|
||||
"warning": { "count": 2, "devices": { "count": 1, "absolute": 1 } },
|
||||
"critical": { "count": 0, "devices": { "count": 0, "absolute": 0 } }
|
||||
}
|
||||
},
|
||||
"hardwareThresholds": {
|
||||
"gpm": { "okMin": 0, "okMax": 29, "minValue": 0, "maxValue": 35 },
|
||||
"psi": { "okMin": 30, "okMax": 80, "minValue": 0, "maxValue": 100 },
|
||||
"lpm": { "okMin": 0, "okMax": 110, "minValue": 0, "maxValue": 130 },
|
||||
"kPa": { "okMin": 210, "okMax": 550, "minValue": 0, "maxValue": 700 },
|
||||
"tempF": { "okMin": 50, "okMax": 80, "minValue": 0, "maxValue": 100 },
|
||||
"tempC": { "okMin": 10, "okMax": 30, "minValue": 0, "maxValue": 40 }
|
||||
},
|
||||
"serialNumber": "111111111111",
|
||||
"connectivity": { "rssi": -47, "ssid": "SOMESSID" },
|
||||
"telemetry": {
|
||||
"current": {
|
||||
"gpm": 0,
|
||||
"psi": 54.20000076293945,
|
||||
"tempF": 70,
|
||||
"updated": "2020-07-24T12:20:58Z"
|
||||
}
|
||||
},
|
||||
"healthTest": {
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"timesPerDay": 1,
|
||||
"start": "02:00",
|
||||
"end": "04:00"
|
||||
}
|
||||
},
|
||||
"shutoff": { "scheduledAt": "1970-01-01T00:00:00.000Z" },
|
||||
"actionRules": [],
|
||||
"location": { "id": "12345abcde" }
|
||||
}
|
Loading…
Add table
Reference in a new issue