Enable map support #1
@ -8,6 +8,7 @@ from typing import Any
|
|||||||
from pybotvac.exceptions import NeatoException, NeatoRobotException
|
from pybotvac.exceptions import NeatoException, NeatoRobotException
|
||||||
from pybotvac.robot import Robot
|
from pybotvac.robot import Robot
|
||||||
from pybotvac.vorwerk import Vorwerk
|
from pybotvac.vorwerk import Vorwerk
|
||||||
|
from pybotvac.session import PasswordlessSession
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.vacuum import (
|
from homeassistant.components.vacuum import (
|
||||||
@ -24,6 +25,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
from homeassistant.const import CONF_TOKEN
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ACTION,
|
ACTION,
|
||||||
@ -97,6 +99,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
|||||||
{
|
{
|
||||||
VORWERK_ROBOT_API: r,
|
VORWERK_ROBOT_API: r,
|
||||||
VORWERK_ROBOT_COORDINATOR: _create_coordinator(hass, r),
|
VORWERK_ROBOT_COORDINATOR: _create_coordinator(hass, r),
|
||||||
|
CONF_TOKEN: entry.data[CONF_TOKEN]
|
||||||
}
|
}
|
||||||
for r in robot_states
|
for r in robot_states
|
||||||
]
|
]
|
||||||
|
1
const.py
1
const.py
@ -23,6 +23,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
|
|||||||
ATTR_NAVIGATION = "navigation"
|
ATTR_NAVIGATION = "navigation"
|
||||||
ATTR_CATEGORY = "category"
|
ATTR_CATEGORY = "category"
|
||||||
ATTR_ZONE = "zone"
|
ATTR_ZONE = "zone"
|
||||||
|
ATTR_MAP = "map"
|
||||||
|
|
||||||
ROBOT_STATE_INVALID = 0
|
ROBOT_STATE_INVALID = 0
|
||||||
ROBOT_STATE_IDLE = 1
|
ROBOT_STATE_IDLE = 1
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/vorwerk",
|
"documentation": "https://www.home-assistant.io/integrations/vorwerk",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pybotvac==0.0.20"
|
"pybotvac==0.0.23"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@trunneml"
|
"@trunneml"
|
||||||
|
@ -16,3 +16,6 @@ custom_cleaning:
|
|||||||
zone:
|
zone:
|
||||||
description: Only supported on the VR300. Name of the zone to clean. Defaults to no zone i.e. complete house cleanup.
|
description: Only supported on the VR300. Name of the zone to clean. Defaults to no zone i.e. complete house cleanup.
|
||||||
example: "Kitchen"
|
example: "Kitchen"
|
||||||
|
map:
|
||||||
|
description: Only supported on the VR300. Name of the map for the zone to clean. Defaults to no zone i.e. complete house cleanup.
|
||||||
|
example: "Ground Floor"
|
102
vacuum.py
102
vacuum.py
@ -5,6 +5,8 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pybotvac import Robot
|
from pybotvac import Robot
|
||||||
|
from pybotvac.session import PasswordlessSession
|
||||||
|
from pybotvac.account import Account
|
||||||
from pybotvac.exceptions import NeatoRobotException
|
from pybotvac.exceptions import NeatoRobotException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -24,23 +26,24 @@ from homeassistant.components.vacuum import (
|
|||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
StateVacuumEntity,
|
StateVacuumEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_MODE
|
from homeassistant.const import ATTR_MODE, CONF_TOKEN
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import VorwerkState
|
from . import VorwerkState
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_CATEGORY,
|
ATTR_CATEGORY,
|
||||||
ATTR_NAVIGATION,
|
ATTR_NAVIGATION,
|
||||||
ATTR_ZONE,
|
ATTR_ZONE,
|
||||||
|
ATTR_MAP,
|
||||||
VORWERK_DOMAIN,
|
VORWERK_DOMAIN,
|
||||||
VORWERK_ROBOT_API,
|
VORWERK_ROBOT_API,
|
||||||
VORWERK_ROBOT_COORDINATOR,
|
VORWERK_ROBOT_COORDINATOR,
|
||||||
VORWERK_ROBOTS,
|
VORWERK_ROBOTS,
|
||||||
|
VORWERK_CLIENT_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -65,7 +68,7 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
VorwerkConnectedVacuum(
|
VorwerkConnectedVacuum(
|
||||||
robot[VORWERK_ROBOT_API], robot[VORWERK_ROBOT_COORDINATOR]
|
robot[VORWERK_ROBOT_API], robot[VORWERK_ROBOT_COORDINATOR], robot[CONF_TOKEN]
|
||||||
)
|
)
|
||||||
for robot in hass.data[VORWERK_DOMAIN][entry.entry_id][VORWERK_ROBOTS]
|
for robot in hass.data[VORWERK_DOMAIN][entry.entry_id][VORWERK_ROBOTS]
|
||||||
],
|
],
|
||||||
@ -82,6 +85,7 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
vol.Optional(ATTR_NAVIGATION, default=1): cv.positive_int,
|
vol.Optional(ATTR_NAVIGATION, default=1): cv.positive_int,
|
||||||
vol.Optional(ATTR_CATEGORY, default=4): cv.positive_int,
|
vol.Optional(ATTR_CATEGORY, default=4): cv.positive_int,
|
||||||
vol.Optional(ATTR_ZONE): cv.string,
|
vol.Optional(ATTR_ZONE): cv.string,
|
||||||
|
vol.Optional(ATTR_MAP): cv.string,
|
||||||
},
|
},
|
||||||
"vorwerk_custom_cleaning",
|
"vorwerk_custom_cleaning",
|
||||||
)
|
)
|
||||||
@ -91,7 +95,7 @@ class VorwerkConnectedVacuum(CoordinatorEntity, StateVacuumEntity):
|
|||||||
"""Representation of a Vorwerk Connected Vacuum."""
|
"""Representation of a Vorwerk Connected Vacuum."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, robot_state: VorwerkState, coordinator: DataUpdateCoordinator[Any]
|
self, robot_state: VorwerkState, coordinator: DataUpdateCoordinator[Any], token
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Vorwerk Connected Vacuum."""
|
"""Initialize the Vorwerk Connected Vacuum."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
@ -100,7 +104,8 @@ class VorwerkConnectedVacuum(CoordinatorEntity, StateVacuumEntity):
|
|||||||
|
|
||||||
self._name = f"{self.robot.name}"
|
self._name = f"{self.robot.name}"
|
||||||
self._robot_serial = self.robot.serial
|
self._robot_serial = self.robot.serial
|
||||||
self._robot_boundaries: list = []
|
self._robot_boundaries: list[str] = []
|
||||||
|
self._token = token
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -214,24 +219,89 @@ class VorwerkConnectedVacuum(CoordinatorEntity, StateVacuumEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def vorwerk_custom_cleaning(
|
def vorwerk_custom_cleaning(
|
||||||
self, mode: str, navigation: str, category: str, zone: str | None = None
|
self, mode: str, navigation: str, category: str, zone: str, map: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Zone cleaning service call."""
|
"""Zone cleaning service call."""
|
||||||
|
_LOGGER.debug("vorwerk_custom_cleaning called for %s / %s with token %s", map, zone, self._token)
|
||||||
|
|
||||||
|
# create Vorwerk API session + account object and populate the robot list
|
||||||
|
# (this necessary to update pybotvac internal states)
|
||||||
|
session = PasswordlessSession(client_id=VORWERK_CLIENT_ID, token=self._token)
|
||||||
|
account = Account(session)
|
||||||
|
robots = account.robots
|
||||||
|
|
||||||
|
_LOGGER.debug(" Robot list = %s", robots)
|
||||||
|
|
||||||
|
map_id = None
|
||||||
boundary_id = None
|
boundary_id = None
|
||||||
if zone is not None:
|
|
||||||
for boundary in self._robot_boundaries:
|
if map is not None:
|
||||||
if zone in boundary["name"]:
|
# search map
|
||||||
boundary_id = boundary["id"]
|
maps = account.persistent_maps[self._robot_serial]
|
||||||
if boundary_id is None:
|
|
||||||
_LOGGER.error(
|
_LOGGER.debug(" Persistent map list = %s", maps)
|
||||||
"Zone '%s' was not found for the robot '%s'", zone, self.entity_id
|
|
||||||
)
|
map_obj = None
|
||||||
|
available_maps = []
|
||||||
|
for m in maps:
|
||||||
|
available_maps.append(m['name'])
|
||||||
|
if map in m['name']:
|
||||||
|
map_obj = m
|
||||||
|
|
||||||
|
if map_obj is None:
|
||||||
|
_LOGGER.error("Map '%s' was not found for the robot '%s', list of valid maps: %s", map, self.entity_id, available_maps)
|
||||||
return
|
return
|
||||||
_LOGGER.info("Start cleaning zone '%s' with robot %s", zone, self.entity_id)
|
|
||||||
|
map_id = map_obj['id']
|
||||||
|
_LOGGER.debug(" Found map %s = ID %s", map, map_id)
|
||||||
|
|
||||||
|
if zone is not None:
|
||||||
|
# search zone = boundary ID
|
||||||
|
boundaries = self.robot.get_map_boundaries(map_id).json()
|
||||||
|
|
||||||
|
_LOGGER.debug(" Boundary list = %s", boundaries)
|
||||||
|
|
||||||
|
boundary_obj = None
|
||||||
|
available_zones = []
|
||||||
|
for b in boundaries['data']['boundaries']:
|
||||||
|
available_zones.append(b['name'])
|
||||||
|
if zone in b['name']:
|
||||||
|
boundary_obj = b
|
||||||
|
|
||||||
|
if boundary_obj is None:
|
||||||
|
_LOGGER.error("Zone '%s' was not found for the robot '%s' on map '%s', list of valid zones: %s", zone, self.entity_id, map, available_zones)
|
||||||
|
return
|
||||||
|
|
||||||
|
boundary_id = boundary_obj['id']
|
||||||
|
_LOGGER.debug(" Found baundary / zone %s = ID %s", zone, boundary_id)
|
||||||
|
|
||||||
|
# start cleaning now
|
||||||
|
_LOGGER.info("Start cleaning zone '%s' on map '%s' with robot %s", zone, map, self.entity_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.robot.start_cleaning(mode, navigation, category, boundary_id)
|
self.robot.start_cleaning(mode, navigation, category, boundary_id, map_id)
|
||||||
except NeatoRobotException as ex:
|
except NeatoRobotException as ex:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Vorwerk vacuum connection error for '%s': %s", self.entity_id, ex
|
"Vorwerk vacuum connection error for '%s': %s", self.entity_id, ex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: OLD CODE
|
||||||
|
# boundary_id = None
|
||||||
|
# if zone is not None:
|
||||||
|
# for boundary in self._robot_boundaries:
|
||||||
|
# if zone in boundary["name"]:
|
||||||
|
# boundary_id = boundary["id"]
|
||||||
|
# if boundary_id is None:
|
||||||
|
# _LOGGER.error(
|
||||||
|
# "Zone '%s' was not found for the robot '%s'", zone, self.entity_id
|
||||||
|
# )
|
||||||
|
# return
|
||||||
|
# _LOGGER.info("Start cleaning zone '%s' with robot %s", zone, self.entity_id)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# self.robot.start_cleaning(mode, navigation, category, boundary_id)
|
||||||
|
# except NeatoRobotException as ex:
|
||||||
|
# _LOGGER.error(
|
||||||
|
# "Vorwerk vacuum connection error for '%s': %s", self.entity_id, ex
|
||||||
|
# )
|
||||||
|
Loading…
Reference in New Issue
Block a user