Skip to content

Commit

Permalink
Add romy vacuum core integration
Browse files Browse the repository at this point in the history
  • Loading branch information
xeniter committed May 29, 2023
1 parent 3c5475a commit c787668
Show file tree
Hide file tree
Showing 18 changed files with 701 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,9 @@ omit =
homeassistant/components/ripple/sensor.py
homeassistant/components/roborock/coordinator.py
homeassistant/components/rocketchat/notify.py
homeassistant/components/romy/__init__.py
homeassistant/components/romy/coordinator.py
homeassistant/components/romy/vacuum.py
homeassistant/components/roomba/__init__.py
homeassistant/components/roomba/binary_sensor.py
homeassistant/components/roomba/braava.py
Expand Down
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ homeassistant.components.rhasspy.*
homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.*
homeassistant.components.roku.*
homeassistant.components.romy.*
homeassistant.components.rpi_power.*
homeassistant.components.rss_feed_template.*
homeassistant.components.rtsp_to_webrtc.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,8 @@ build.json @home-assistant/supervisor
/tests/components/roborock/ @humbertogontijo @Lash-L
/homeassistant/components/roku/ @ctalkington
/tests/components/roku/ @ctalkington
/homeassistant/components/romy/ @xeniter
/tests/components/romy/ @xeniter
/homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn
/tests/components/roomba/ @pschmitt @cyr-ius @shenxn
/homeassistant/components/roon/ @pavoni
Expand Down
44 changes: 44 additions & 0 deletions homeassistant/components/romy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""ROMY Integration."""

import romy

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD
from homeassistant.core import HomeAssistant

from .const import DOMAIN, LOGGER, PLATFORMS
from .coordinator import RomyVacuumCoordinator


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""TODO."""

password = ""
if CONF_PASSWORD in config_entry.data:
password = config_entry.data[CONF_PASSWORD]

new_romy = await romy.create_romy(config_entry.data[CONF_HOST], password)

name = config_entry.data[CONF_NAME]
if name != new_romy.name:
await new_romy.set_name(name)
LOGGER.info("Settings ROMY's name to: %s", new_romy.name)

coordinator = RomyVacuumCoordinator(hass, new_romy)
await coordinator.async_config_entry_first_refresh()

# hass.data.setdefault(DOMAIN, {})
# hass.data[DOMAIN][config_entry.entry_id] = coordinator
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator

await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
return unload_ok
128 changes: 128 additions & 0 deletions homeassistant/components/romy/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Config flow for ROMY integration."""
from __future__ import annotations

import romy
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv

from .const import DOMAIN, LOGGER


def _schema_with_defaults(
host: str = "", port: int = 8080, name: str = ""
) -> vol.Schema:
return vol.Schema(
{
vol.Required(CONF_HOST, default=host): cv.string,
vol.Optional(CONF_NAME, default=name): cv.string,
},
)


def _schema_with_defaults_and_password(
host: str = "", port: int = 8080, name: str = "", password: str = ""
) -> vol.Schema:
return vol.Schema(
{
vol.Required(CONF_HOST, default=host): cv.string,
vol.Optional(CONF_NAME, default=name): cv.string,
vol.Required(CONF_PASSWORD, default=password): vol.All(str, vol.Length(8)),
},
)


class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle config flow for ROMY."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

def __init__(self) -> None:
"""Handle a config flow for ROMY."""
self.discovery_schema = None
self.host: str = ""
self.name: str = ""
self.password: str = ""

async def async_step_user(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the user step."""
errors = {}
data = self.discovery_schema or _schema_with_defaults()

if user_input is not None:
# Validate the user input
if "host" not in user_input:
errors["host"] = "Please enter a host."

if not errors:
## Save the user input and finish the setup
self.host = user_input["host"]
self.name = user_input["name"]
if "password" in user_input:
self.password = user_input["password"]

new_romy = await romy.create_romy(self.host, self.password)

if not new_romy.is_initialized:
errors[CONF_HOST] = "wrong host"
return self.async_show_form(
step_id="user", data_schema=data, errors=errors
)

if not new_romy.is_unlocked:
errors[CONF_PASSWORD] = "wrong password"
return self.async_show_form(
step_id="user", data_schema=data, errors=errors
)

return self.async_create_entry(
title=user_input["name"], data=user_input
)

return self.async_show_form(step_id="user", data_schema=data, errors=errors)

async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""

LOGGER.debug("Zeroconf discovery_info: %s", discovery_info)

# extract unique id and stop discovery if robot is already added
unique_id = discovery_info.hostname.split(".")[0]
LOGGER.debug("Unique_id: %s", unique_id)
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()

# get ROMY's name and check if local http interface is locked
new_discovered_romy = await romy.create_romy(discovery_info.host, "")
discovery_info.name = new_discovered_romy.name

self.context.update(
{
"title_placeholders": {
"name": f"{unique_id.split('-')[1]} ({discovery_info.host})"
},
"configuration_url": f"http://{discovery_info.host}:{new_discovered_romy.port}",
}
)

if new_discovered_romy.is_unlocked:
self.discovery_schema = _schema_with_defaults(
host=discovery_info.host,
name=discovery_info.name,
)
else:
self.discovery_schema = _schema_with_defaults_and_password(
host=discovery_info.host,
name=discovery_info.name,
password="",
)
return await self.async_step_user()
14 changes: 14 additions & 0 deletions homeassistant/components/romy/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Constants for the ROMY integration."""

from datetime import timedelta
import logging

from homeassistant.const import Platform

# This is the internal name of the integration, it should also match the directory
# name for the integration.
DOMAIN = "romy"
ICON = "mdi:robot-vacuum"
PLATFORMS = [Platform.VACUUM]
UPDATE_INTERVAL = timedelta(seconds=5)
LOGGER = logging.getLogger(__package__)
22 changes: 22 additions & 0 deletions homeassistant/components/romy/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""ROMY coordinator."""

from romy import RomyRobot

from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import DOMAIN, LOGGER, UPDATE_INTERVAL


class RomyVacuumCoordinator(DataUpdateCoordinator):
"""ROMY Vacuum Coordinator."""

def __init__(self, hass: HomeAssistant, romy: RomyRobot) -> None:
"""Setuping ROMY Vacuum Coordinator class."""
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
self.hass = hass
self.romy = romy

async def _async_update_data(self) -> None:
"""Update ROMY Vacuum Cleaner data."""
await self.romy.async_update()
11 changes: 11 additions & 0 deletions homeassistant/components/romy/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"domain": "romy",
"name": "ROMY Vacuum Cleaner",
"codeowners": ["@xeniter"],
"config_flow": true,
"dependencies": [],
"documentation": "https://www.home-assistant.io/integrations/romy",
"iot_class": "local_polling",
"requirements": ["romy==0.0.2"],
"zeroconf": ["_aicu-http._tcp.local."]
}
21 changes: 21 additions & 0 deletions homeassistant/components/romy/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"config": {
"flow_title": "{name}",
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
"name": "[%key:common::config_flow::data::name%]"
},
"title": "Please provide ROMYs IP Address"
}
}
}
}
Loading

0 comments on commit c787668

Please sign in to comment.