Add Rainmachine config entry (#18419)
* Initial stuff * More work in place * Starting with tests * Device registry in place * Hound * Linting * Member comments (including extracting device registry) * Member comments (plus I forgot cleanup!) * Hound * More Hound * Removed old import * Adding config entry test to coverage * Updated strings
This commit is contained in:
parent
312872961f
commit
8aa1283adc
15 changed files with 400 additions and 87 deletions
|
@ -271,7 +271,7 @@ omit =
|
|||
homeassistant/components/raincloud.py
|
||||
homeassistant/components/*/raincloud.py
|
||||
|
||||
homeassistant/components/rainmachine/*
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/*/rainmachine.py
|
||||
|
||||
homeassistant/components/raspihats.py
|
||||
|
|
|
@ -8,28 +8,29 @@ import logging
|
|||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.rainmachine import (
|
||||
BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE,
|
||||
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
|
||||
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
BINARY_SENSORS, DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN,
|
||||
SENSOR_UPDATE_TOPIC, TYPE_FREEZE, TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS,
|
||||
TYPE_HOURLY, TYPE_MONTH, TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY,
|
||||
RainMachineEntity)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['rainmachine']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the RainMachine Switch platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
"""Set up RainMachine binary sensors based on the old way."""
|
||||
pass
|
||||
|
||||
rainmachine = hass.data[DATA_RAINMACHINE]
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up RainMachine binary sensors based on a config entry."""
|
||||
rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
||||
binary_sensors = []
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
for sensor_type in rainmachine.binary_sensor_conditions:
|
||||
name, icon = BINARY_SENSORS[sensor_type]
|
||||
binary_sensors.append(
|
||||
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
|
||||
|
@ -70,15 +71,20 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
|
|||
return '{0}_{1}'.format(
|
||||
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
|
||||
|
||||
@callback
|
||||
def _update_data(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
|
||||
@callback
|
||||
def update(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, SENSOR_UPDATE_TOPIC, update)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state."""
|
||||
|
|
19
homeassistant/components/rainmachine/.translations/en.json
Normal file
19
homeassistant/components/rainmachine/.translations/en.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Account already registered",
|
||||
"invalid_credentials": "Invalid credentials"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "Hostname or IP Address",
|
||||
"password": "Password",
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "Fill in your information"
|
||||
}
|
||||
},
|
||||
"title": "RainMachine"
|
||||
}
|
||||
}
|
|
@ -9,25 +9,25 @@ from datetime import timedelta
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, CONF_BINARY_SENSORS, CONF_IP_ADDRESS, CONF_PASSWORD,
|
||||
CONF_PORT, CONF_SCAN_INTERVAL, CONF_SENSORS, CONF_SSL,
|
||||
CONF_MONITORED_CONDITIONS, CONF_SWITCHES)
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client, config_validation as cv, discovery)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
REQUIREMENTS = ['regenmaschine==1.0.2']
|
||||
from .config_flow import configured_instances
|
||||
from .const import DATA_CLIENT, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
|
||||
REQUIREMENTS = ['regenmaschine==1.0.7']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_RAINMACHINE = 'data_rainmachine'
|
||||
DOMAIN = 'rainmachine'
|
||||
|
||||
NOTIFICATION_ID = 'rainmachine_notification'
|
||||
NOTIFICATION_TITLE = 'RainMachine Component Setup'
|
||||
DATA_LISTENER = 'listener'
|
||||
|
||||
PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN)
|
||||
SENSOR_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN)
|
||||
|
@ -39,8 +39,6 @@ CONF_ZONE_RUN_TIME = 'zone_run_time'
|
|||
|
||||
DEFAULT_ATTRIBUTION = 'Data provided by Green Electronics LLC'
|
||||
DEFAULT_ICON = 'mdi:water'
|
||||
DEFAULT_PORT = 8080
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
||||
DEFAULT_SSL = True
|
||||
DEFAULT_ZONE_RUN = 60 * 10
|
||||
|
||||
|
@ -120,48 +118,73 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the RainMachine component."""
|
||||
from regenmaschine import Client
|
||||
from regenmaschine.errors import RequestError
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||
hass.data[DOMAIN][DATA_LISTENER] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
ip_address = conf[CONF_IP_ADDRESS]
|
||||
password = conf[CONF_PASSWORD]
|
||||
port = conf[CONF_PORT]
|
||||
ssl = conf[CONF_SSL]
|
||||
|
||||
if conf[CONF_IP_ADDRESS] in configured_instances(hass):
|
||||
return True
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={'source': SOURCE_IMPORT},
|
||||
data=conf))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up RainMachine as config entry."""
|
||||
from regenmaschine import login
|
||||
from regenmaschine.errors import RainMachineError
|
||||
|
||||
ip_address = config_entry.data[CONF_IP_ADDRESS]
|
||||
password = config_entry.data[CONF_PASSWORD]
|
||||
port = config_entry.data[CONF_PORT]
|
||||
ssl = config_entry.data.get(CONF_SSL, DEFAULT_SSL)
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
try:
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
client = Client(ip_address, websession, port=port, ssl=ssl)
|
||||
await client.authenticate(password)
|
||||
rainmachine = RainMachine(client)
|
||||
client = await login(
|
||||
ip_address, password, websession, port=port, ssl=ssl)
|
||||
rainmachine = RainMachine(
|
||||
client,
|
||||
config_entry.data.get(CONF_BINARY_SENSORS, {}).get(
|
||||
CONF_MONITORED_CONDITIONS, list(BINARY_SENSORS)),
|
||||
config_entry.data.get(CONF_SENSORS, {}).get(
|
||||
CONF_MONITORED_CONDITIONS, list(SENSORS)),
|
||||
config_entry.data.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN)
|
||||
)
|
||||
await rainmachine.async_update()
|
||||
hass.data[DATA_RAINMACHINE] = rainmachine
|
||||
except RequestError as err:
|
||||
_LOGGER.error('An error occurred: %s', str(err))
|
||||
hass.components.persistent_notification.create(
|
||||
'Error: {0}<br />'
|
||||
'You will need to restart hass after fixing.'
|
||||
''.format(err),
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
return False
|
||||
except RainMachineError as err:
|
||||
_LOGGER.error('An error occurred: %s', err)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
for component, schema in [
|
||||
('binary_sensor', conf[CONF_BINARY_SENSORS]),
|
||||
('sensor', conf[CONF_SENSORS]),
|
||||
('switch', conf[CONF_SWITCHES]),
|
||||
]:
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = rainmachine
|
||||
|
||||
for component in ('binary_sensor', 'sensor', 'switch'):
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(hass, component, DOMAIN, schema,
|
||||
config))
|
||||
hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, component))
|
||||
|
||||
async def refresh_sensors(event_time):
|
||||
async def refresh(event_time):
|
||||
"""Refresh RainMachine sensor data."""
|
||||
_LOGGER.debug('Updating RainMachine sensor data')
|
||||
await rainmachine.async_update()
|
||||
async_dispatcher_send(hass, SENSOR_UPDATE_TOPIC)
|
||||
|
||||
async_track_time_interval(hass, refresh_sensors, conf[CONF_SCAN_INTERVAL])
|
||||
hass.data[DOMAIN][DATA_LISTENER][
|
||||
config_entry.entry_id] = async_track_time_interval(
|
||||
hass,
|
||||
refresh,
|
||||
timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL]))
|
||||
|
||||
async def start_program(service):
|
||||
"""Start a particular program."""
|
||||
|
@ -170,8 +193,8 @@ async def async_setup(hass, config):
|
|||
|
||||
async def start_zone(service):
|
||||
"""Start a particular zone for a certain amount of time."""
|
||||
await rainmachine.client.zones.start(service.data[CONF_ZONE_ID],
|
||||
service.data[CONF_ZONE_RUN_TIME])
|
||||
await rainmachine.client.zones.start(
|
||||
service.data[CONF_ZONE_ID], service.data[CONF_ZONE_RUN_TIME])
|
||||
async_dispatcher_send(hass, ZONE_UPDATE_TOPIC)
|
||||
|
||||
async def stop_all(service):
|
||||
|
@ -201,14 +224,34 @@ async def async_setup(hass, config):
|
|||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload an OpenUV config entry."""
|
||||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||
|
||||
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(
|
||||
config_entry.entry_id)
|
||||
remove_listener()
|
||||
|
||||
for component in ('binary_sensor', 'sensor', 'switch'):
|
||||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, component)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class RainMachine:
|
||||
"""Define a generic RainMachine object."""
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(
|
||||
self, client, binary_sensor_conditions, sensor_conditions,
|
||||
default_zone_runtime):
|
||||
"""Initialize."""
|
||||
self.binary_sensor_conditions = binary_sensor_conditions
|
||||
self.client = client
|
||||
self.default_zone_runtime = default_zone_runtime
|
||||
self.device_mac = self.client.mac
|
||||
self.restrictions = {}
|
||||
self.sensor_conditions = sensor_conditions
|
||||
|
||||
async def async_update(self):
|
||||
"""Update sensor/binary sensor data."""
|
||||
|
@ -224,6 +267,7 @@ class RainMachineEntity(Entity):
|
|||
def __init__(self, rainmachine):
|
||||
"""Initialize."""
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
self._name = None
|
||||
self.rainmachine = rainmachine
|
||||
|
||||
|
|
85
homeassistant/components/rainmachine/config_flow.py
Normal file
85
homeassistant/components/rainmachine/config_flow.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
"""Config flow to configure the RainMachine component."""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import (
|
||||
CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL)
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
def configured_instances(hass):
|
||||
"""Return a set of configured RainMachine instances."""
|
||||
return set(
|
||||
entry.data[CONF_IP_ADDRESS]
|
||||
for entry in hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class RainMachineFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle a RainMachine config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the config flow."""
|
||||
self.data_schema = OrderedDict()
|
||||
self.data_schema[vol.Required(CONF_IP_ADDRESS)] = str
|
||||
self.data_schema[vol.Required(CONF_PASSWORD)] = str
|
||||
self.data_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int
|
||||
|
||||
async def _show_form(self, errors=None):
|
||||
"""Show the form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id='user',
|
||||
data_schema=vol.Schema(self.data_schema),
|
||||
errors=errors if errors else {},
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
return await self.async_step_user(import_config)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the start of the config flow."""
|
||||
from regenmaschine import login
|
||||
from regenmaschine.errors import RainMachineError
|
||||
|
||||
if not user_input:
|
||||
return await self._show_form()
|
||||
|
||||
if user_input[CONF_IP_ADDRESS] in configured_instances(self.hass):
|
||||
return await self._show_form({
|
||||
CONF_IP_ADDRESS: 'identifier_exists'
|
||||
})
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||
|
||||
try:
|
||||
await login(
|
||||
user_input[CONF_IP_ADDRESS],
|
||||
user_input[CONF_PASSWORD],
|
||||
websession,
|
||||
port=user_input.get(CONF_PORT, DEFAULT_PORT),
|
||||
ssl=True)
|
||||
except RainMachineError:
|
||||
return await self._show_form({
|
||||
CONF_PASSWORD: 'invalid_credentials'
|
||||
})
|
||||
|
||||
scan_interval = user_input.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||
user_input[CONF_SCAN_INTERVAL] = scan_interval.seconds
|
||||
|
||||
# Unfortunately, RainMachine doesn't provide a way to refresh the
|
||||
# access token without using the IP address and password, so we have to
|
||||
# store it:
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_IP_ADDRESS], data=user_input)
|
14
homeassistant/components/rainmachine/const.py
Normal file
14
homeassistant/components/rainmachine/const.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
"""Define constants for the SimpliSafe component."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
LOGGER = logging.getLogger('homeassistant.components.rainmachine')
|
||||
|
||||
DOMAIN = 'rainmachine'
|
||||
|
||||
DATA_CLIENT = 'client'
|
||||
|
||||
DEFAULT_PORT = 8080
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
TOPIC_UPDATE = 'update_{0}'
|
19
homeassistant/components/rainmachine/strings.json
Normal file
19
homeassistant/components/rainmachine/strings.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "RainMachine",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Fill in your information",
|
||||
"data": {
|
||||
"ip_address": "Hostname or IP Address",
|
||||
"password": "Password",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"identifier_exists": "Account already registered",
|
||||
"invalid_credentials": "Invalid credentials"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,26 +7,27 @@ https://home-assistant.io/components/sensor.rainmachine/
|
|||
import logging
|
||||
|
||||
from homeassistant.components.rainmachine import (
|
||||
DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, SENSORS, RainMachineEntity)
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, SENSOR_UPDATE_TOPIC, SENSORS,
|
||||
RainMachineEntity)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['rainmachine']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the RainMachine Switch platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
"""Set up RainMachine sensors based on the old way."""
|
||||
pass
|
||||
|
||||
rainmachine = hass.data[DATA_RAINMACHINE]
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up RainMachine sensors based on a config entry."""
|
||||
rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
||||
sensors = []
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
for sensor_type in rainmachine.sensor_conditions:
|
||||
name, icon, unit = SENSORS[sensor_type]
|
||||
sensors.append(
|
||||
RainMachineSensor(rainmachine, sensor_type, name, icon, unit))
|
||||
|
@ -73,15 +74,20 @@ class RainMachineSensor(RainMachineEntity):
|
|||
"""Return the unit the value is expressed in."""
|
||||
return self._unit
|
||||
|
||||
@callback
|
||||
def _update_data(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
|
||||
@callback
|
||||
def update(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, SENSOR_UPDATE_TOPIC, update)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor's state."""
|
||||
|
|
|
@ -7,8 +7,8 @@ https://home-assistant.io/components/switch.rainmachine/
|
|||
import logging
|
||||
|
||||
from homeassistant.components.rainmachine import (
|
||||
CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, DEFAULT_ZONE_RUN,
|
||||
PROGRAM_UPDATE_TOPIC, ZONE_UPDATE_TOPIC, RainMachineEntity)
|
||||
DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC,
|
||||
ZONE_UPDATE_TOPIC, RainMachineEntity)
|
||||
from homeassistant.const import ATTR_ID
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.core import callback
|
||||
|
@ -101,15 +101,13 @@ VEGETATION_MAP = {
|
|||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the RainMachine Switch platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
"""Set up RainMachine switches sensor based on the old way."""
|
||||
pass
|
||||
|
||||
_LOGGER.debug('Config received: %s', discovery_info)
|
||||
|
||||
zone_run_time = discovery_info.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN)
|
||||
|
||||
rainmachine = hass.data[DATA_RAINMACHINE]
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up RainMachine switches based on a config entry."""
|
||||
rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
||||
entities = []
|
||||
|
||||
|
@ -127,7 +125,9 @@ async def async_setup_platform(
|
|||
continue
|
||||
|
||||
_LOGGER.debug('Adding zone: %s', zone)
|
||||
entities.append(RainMachineZone(rainmachine, zone, zone_run_time))
|
||||
entities.append(
|
||||
RainMachineZone(
|
||||
rainmachine, zone, rainmachine.default_zone_runtime))
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
@ -186,9 +186,14 @@ class RainMachineProgram(RainMachineSwitch):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the program off."""
|
||||
from regenmaschine.errors import RequestError
|
||||
|
|
|
@ -149,6 +149,7 @@ FLOWS = [
|
|||
'mqtt',
|
||||
'nest',
|
||||
'openuv',
|
||||
'rainmachine',
|
||||
'simplisafe',
|
||||
'smhi',
|
||||
'sonos',
|
||||
|
|
|
@ -1336,7 +1336,7 @@ raincloudy==0.0.5
|
|||
# raspihats==2.2.3
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==1.0.2
|
||||
regenmaschine==1.0.7
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==4.0b6
|
||||
|
|
|
@ -210,6 +210,9 @@ pyunifi==2.13
|
|||
# homeassistant.components.notify.html5
|
||||
pywebpush==1.6.0
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==1.0.7
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==4.0b6
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ TEST_REQUIREMENTS = (
|
|||
'pyunifi',
|
||||
'pyupnp-async',
|
||||
'pywebpush',
|
||||
'regenmaschine',
|
||||
'restrictedpython',
|
||||
'rflink',
|
||||
'ring_doorbell',
|
||||
|
|
1
tests/components/rainmachine/__init__.py
Normal file
1
tests/components/rainmachine/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Define tests for the RainMachine component."""
|
109
tests/components/rainmachine/test_config_flow.py
Normal file
109
tests/components/rainmachine/test_config_flow.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
"""Define tests for the OpenUV config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.rainmachine import DOMAIN, config_flow
|
||||
from homeassistant.const import (
|
||||
CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_SCAN_INTERVAL)
|
||||
|
||||
from tests.common import MockConfigEntry, mock_coro
|
||||
|
||||
|
||||
async def test_duplicate_error(hass):
|
||||
"""Test that errors are shown when duplicates are added."""
|
||||
conf = {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
}
|
||||
|
||||
MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass)
|
||||
flow = config_flow.RainMachineFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_IP_ADDRESS: 'identifier_exists'}
|
||||
|
||||
|
||||
async def test_invalid_password(hass):
|
||||
"""Test that an invalid password throws an error."""
|
||||
from regenmaschine.errors import RainMachineError
|
||||
|
||||
conf = {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'bad_password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
}
|
||||
|
||||
flow = config_flow.RainMachineFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('regenmaschine.login',
|
||||
return_value=mock_coro(exception=RainMachineError)):
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_PASSWORD: 'invalid_credentials'}
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
flow = config_flow.RainMachineFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=None)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result['step_id'] == 'user'
|
||||
|
||||
|
||||
async def test_step_import(hass):
|
||||
"""Test that the import step works."""
|
||||
conf = {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
}
|
||||
|
||||
flow = config_flow.RainMachineFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('regenmaschine.login', return_value=mock_coro(True)):
|
||||
result = await flow.async_step_import(import_config=conf)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result['title'] == '192.168.1.100'
|
||||
assert result['data'] == {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
CONF_SCAN_INTERVAL: 60,
|
||||
}
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test that the user step works."""
|
||||
conf = {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
}
|
||||
|
||||
flow = config_flow.RainMachineFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('regenmaschine.login', return_value=mock_coro(True)):
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result['title'] == '192.168.1.100'
|
||||
assert result['data'] == {
|
||||
CONF_IP_ADDRESS: '192.168.1.100',
|
||||
CONF_PASSWORD: 'password',
|
||||
CONF_PORT: 8080,
|
||||
CONF_SSL: True,
|
||||
CONF_SCAN_INTERVAL: 60,
|
||||
}
|
Loading…
Add table
Reference in a new issue