├── .devcontainer ├── configuration.yaml └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.md ├── dependabot.yml ├── labels.toml └── workflows │ ├── lint.yml │ ├── release.yml │ └── validate.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── custom_components ├── __init__.py └── roborock │ ├── __init__.py │ ├── binary_sensor.py │ ├── button.py │ ├── camera.py │ ├── common │ ├── image_handler.py │ ├── map_data.py │ ├── map_data_parser.py │ └── types.py │ ├── config_flow.py │ ├── const.py │ ├── coordinator.py │ ├── device.py │ ├── domain.py │ ├── manifest.json │ ├── number.py │ ├── roborock_typing.py │ ├── select.py │ ├── sensor.py │ ├── services.yaml │ ├── store.py │ ├── switch.py │ ├── time.py │ ├── translations │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── en.json │ ├── fr.json │ ├── he.json │ ├── it.json │ ├── lv.json │ ├── nl.json │ ├── no.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt.json │ ├── ru.json │ └── sv.json │ ├── utils.py │ └── vacuum.py ├── hacs.json ├── info.md ├── package.yaml ├── project.inlang.json ├── requirements-dev.txt ├── requirements.txt ├── ruff.toml └── tests ├── __init__.py ├── common.py ├── conftest.py ├── mock_data.py ├── test_binary_sensor.py ├── test_camera.py ├── test_config_flow.py ├── test_platform.py ├── test_select.py ├── test_sensor.py └── test_vacuum.py /.devcontainer/configuration.yaml: -------------------------------------------------------------------------------- 1 | default_config: 2 | 3 | logger: 4 | default: info 5 | logs: 6 | custom_components.roborock: debug 7 | roborock: debug 8 | 9 | debugpy: 10 | 11 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "ghcr.io/ludeeus/devcontainer/integration:stable", 3 | "name": "Roborock integration development", 4 | "context": "..", 5 | "appPort": [ 6 | "9123:8123" 7 | ], 8 | "postCreateCommand": "container install", 9 | "customizations": { 10 | "vscode": { 11 | "extensions": [ 12 | "ms-python.python", 13 | "github.vscode-pull-request-github", 14 | "ryanluker.vscode-coverage-gutters", 15 | "ms-python.vscode-pylance" 16 | ] 17 | } 18 | }, 19 | "settings": { 20 | "files.eol": "\n", 21 | "editor.tabSize": 4, 22 | "terminal.integrated.shell.linux": "/bin/bash", 23 | "python.pythonPath": "/usr/bin/python3", 24 | "python.analysis.autoSearchPaths": false, 25 | "python.linting.pylintEnabled": true, 26 | "python.linting.enabled": true, 27 | "python.formatting.provider": "black", 28 | "editor.formatOnPaste": false, 29 | "editor.formatOnSave": true, 30 | "editor.formatOnType": true, 31 | "files.trimTrailingWhitespace": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | ## Is your feature request related to a problem? Please describe. 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | ## Describe the solution you'd like 10 | 11 | 12 | ## Describe alternatives you've considered 13 | 14 | 15 | ## Vacuum 16 | 17 | 18 | ## Additional context 19 | 20 | 21 | ## Mqtt request 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 13 | 14 | ## Version of the custom_component 15 | 16 | 18 | 19 | ## What vacuum are you using? 20 | 21 | 22 | ## Describe the bug 23 | 24 | A clear and concise description of what the bug is. 25 | 26 | ## Debug log 27 | 28 | 29 | 30 | ```text 31 | 32 | Add your logs here. 33 | 34 | ``` -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | - package-ecosystem: "pip" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | ignore: 14 | # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json 15 | - dependency-name: "homeassistant" -------------------------------------------------------------------------------- /.github/labels.toml: -------------------------------------------------------------------------------- 1 | [bug] 2 | color = "d73a4a" 3 | name = "bug" 4 | description = "Something isn't working" 5 | 6 | [documentation] 7 | color = "1bc4a5" 8 | name = "documentation" 9 | description = "Improvements or additions to documentation" 10 | 11 | [duplicate] 12 | color = "cfd3d7" 13 | name = "duplicate" 14 | description = "This issue or pull request already exists" 15 | 16 | [enhancement] 17 | color = "a2eeef" 18 | name = "enhancement" 19 | description = "New feature or request" 20 | 21 | ["good first issue"] 22 | color = "7057ff" 23 | name = "good first issue" 24 | description = "Good for newcomers" 25 | 26 | ["help wanted"] 27 | color = "008672" 28 | name = "help wanted" 29 | description = "Extra attention is needed" 30 | 31 | [question] 32 | color = "d876e3" 33 | name = "question" 34 | description = "Further information is requested" 35 | 36 | [removed] 37 | color = "e99695" 38 | name = "removed" 39 | description = "Removed piece of functionalities." 40 | 41 | [tests] 42 | color = "bfd4f2" 43 | name = "tests" 44 | description = "CI, CD and testing related changes" 45 | 46 | [notplanned] 47 | color = "ffffff" 48 | name = "wontfix" 49 | description = "This will not be worked on" 50 | 51 | [planned] 52 | color= "0366d6" 53 | name="planned" 54 | description = "This will be added in a future release" 55 | 56 | [discussion] 57 | color = "c2e0c6" 58 | name = "discussion" 59 | description = "Some discussion around the project" 60 | 61 | [answered] 62 | color = "0ee2b6" 63 | name = "answered" 64 | description = "Automatically closes as answered after a delay" 65 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: "Lint" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "main" 10 | 11 | jobs: 12 | ruff: 13 | name: "Ruff" 14 | runs-on: "ubuntu-latest" 15 | steps: 16 | - name: "Checkout the repository" 17 | uses: "actions/checkout@v3.5.3" 18 | 19 | - name: "Set up Python" 20 | uses: actions/setup-python@v4.7.0 21 | with: 22 | python-version: "3.10" 23 | cache: "pip" 24 | 25 | - name: "Install requirements" 26 | run: python3 -m pip install -r requirements.txt 27 | 28 | - name: "Run" 29 | run: python3 -m ruff check . 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | 3 | on: 4 | release: 5 | types: 6 | - "published" 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | release: 12 | name: "Release" 13 | runs-on: "ubuntu-latest" 14 | permissions: 15 | contents: write 16 | steps: 17 | - name: "Checkout the repository" 18 | uses: "actions/checkout@v3.5.3" 19 | 20 | - name: "Adjust version number" 21 | shell: "bash" 22 | run: | 23 | yq -i -o json '.version="${{ github.event.release.tag_name }}"' \ 24 | "${{ github.workspace }}/custom_components/roborock/manifest.json" 25 | 26 | - name: "ZIP the integration directory" 27 | shell: "bash" 28 | run: | 29 | cd "${{ github.workspace }}/custom_components/roborock" 30 | zip roborock.zip -r ./ 31 | 32 | - name: "Upload the ZIP file to the release" 33 | uses: softprops/action-gh-release@v0.1.15 34 | with: 35 | files: ${{ github.workspace }}/custom_components/roborock/roborock.zip 36 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: "Validate" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | push: 8 | branches: 9 | - "main" 10 | pull_request: 11 | branches: 12 | - "main" 13 | 14 | jobs: 15 | hassfest: # https://developers.home-assistant.io/blog/2020/04/16/hassfest 16 | name: "Hassfest Validation" 17 | runs-on: "ubuntu-latest" 18 | steps: 19 | - name: "Checkout the repository" 20 | uses: "actions/checkout@v3.5.3" 21 | 22 | - name: "Run hassfest validation" 23 | uses: "home-assistant/actions/hassfest@master" 24 | 25 | hacs: # https://github.com/hacs/action 26 | name: "HACS Validation" 27 | runs-on: "ubuntu-latest" 28 | steps: 29 | - name: "Checkout the repository" 30 | uses: "actions/checkout@v3.5.3" 31 | 32 | - name: "Run HACS validation" 33 | uses: "hacs/action@main" 34 | with: 35 | category: "integration" 36 | # Remove this 'ignore' key when you have added brand images for your integration to https://github.com/home-assistant/brands 37 | ignore: "brands" 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dev 2 | poetry.lock 3 | .idea 4 | 5 | 6 | # artifacts 7 | __pycache__ 8 | .pytest* 9 | *.egg-info 10 | */build/* 11 | */dist/* 12 | 13 | 14 | # misc 15 | .coverage 16 | coverage.xml 17 | 18 | 19 | # Home Assistant configuration 20 | config/* 21 | !config/configuration.yaml -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Attach Local", 6 | "type": "python", 7 | "request": "attach", 8 | "port": 5678, 9 | "host": "localhost", 10 | "justMyCode": false, 11 | "pathMappings": [ 12 | { 13 | "localRoot": "${workspaceFolder}", 14 | "remoteRoot": "." 15 | } 16 | ] 17 | }, 18 | { 19 | "name": "Python: Attach Remote", 20 | "type": "python", 21 | "request": "attach", 22 | "port": 5678, 23 | "host": "homeassistant.local", 24 | "justMyCode": false, 25 | "pathMappings": [ 26 | { 27 | "localRoot": "${workspaceFolder}", 28 | "remoteRoot": "/config" 29 | } 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": true, 3 | "python.linting.enabled": true, 4 | "python.pythonPath": "/usr/local/bin/python", 5 | "files.associations": { 6 | "*.yaml": "home-assistant" 7 | } 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run Home Assistant on port 9123", 6 | "type": "shell", 7 | "command": "container start", 8 | "problemMatcher": [] 9 | }, 10 | { 11 | "label": "Run Home Assistant configuration against /config", 12 | "type": "shell", 13 | "command": "container check", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Upgrade Home Assistant to latest dev", 18 | "type": "shell", 19 | "command": "container install", 20 | "problemMatcher": [] 21 | }, 22 | { 23 | "label": "Install a specific version of Home Assistant", 24 | "type": "shell", 25 | "command": "container set-version", 26 | "problemMatcher": [] 27 | }, { 28 | "label": "Kill Home Assistant on port 9123", 29 | "type": "shell", 30 | "command": "sudo kill -9 $(lsof -t -i:8123 -sTCP:LISTEN)", 31 | "problemMatcher": [] 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Roborock integration for HomeAsssistant 2 | 3 | ## IMPORTANT NOTE!! 4 | It is recommended you switch to the core version of this integration. Due to the time that the maintainers have, most time goes into supporting the core version of this integration as there are approximately 5 times as many users using that integration. There are multiple known issues with the custom component version of Roborock that are not planned to be fixed. If a user is so inclined - they can look at the fixes on the core component and carry them over here and we will approve PR. But otherwise, expect updates and fixes to this repository to be very limited at least for the time being. 5 | 6 | ## About this repo 7 | I've bought a Roborock S7 Maxv and hated the fact that I had to use the Roborock App or the previous existing HomeAssistant integration. But not both. 8 | 9 | Using the Xiaomi integration there is also a limit to the number of map request which this integration doesn't have 10 | 11 | Hope everyone can enjoy this integration along side the Roborock App 12 | 13 | [![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/humbertogontijo) 14 | 15 | --- 16 | 17 | ## Installation 18 | 19 | I recommend installing it through [HACS](https://github.com/hacs/integration) 20 | 21 | ## IMPORTANT 22 | Ensure that your HA instance can listen for device broadcasts on port 58866 23 | Ensure that your HA instance can communicate to device on port 58867 24 | 25 | ### Installing via HACS 26 | [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=humbertogontijo&repository=homeassistant-roborock&category=integration) 27 | 28 | or 29 | 30 | 1. Go to HACS->Integrations 31 | 1. Add this repo into your HACS custom repositories 32 | 1. Search for Roborock and Download it 33 | 1. Restart your HomeAssistant 34 | 1. Go to Settings->Devices & Services 35 | 1. Shift reload your browser 36 | 37 | ### Setup the Integration 38 | 39 | [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=roborock) 40 | 41 | 1. Click Add Integration 42 | 1. Search for Roborock 43 | 1. Type your username used in the Roborock App and hit submit 44 | 1. You will receive an Email with a verification code 45 | 1. Type the verification code and hit submit 46 | 1. You're all set 47 | 48 | 49 | --- 50 | ## Functionality 51 | 52 | ### Vacuum 53 | - Start the vacuum 54 | - Stop the vacuum 55 | - Pause the vacuum 56 | - Dock the vacuum 57 | - Control vacuum fan speed 58 | - Vacuum battery 59 | - Locate vacuum 60 | - Clean Spot 61 | 62 | Additional Vacuum functionality that is supported through services: 63 | - Remote control 64 | - Clean zone 65 | - Go to 66 | - Clean segment 67 | - Set mop mode 68 | - Set mop intensity 69 | - Reset consumables 70 | 71 | ### Map 72 | There is a map built in to the integration that shows you your most recent/current run of the vacuum. 73 | 74 | ### Sensors 75 | - DND start 76 | - DND end 77 | - Last clean start 78 | - Last clean end 79 | - last clean duration 80 | - last clean area 81 | - current error 82 | - current clean duration 83 | - current clean area 84 | - total duration 85 | - total clean area 86 | - total clean count 87 | - total dust collection count 88 | - main brush left 89 | - side brush left 90 | - filter left 91 | - sensor dirty left 92 | - battery 93 | 94 | ### Binary Sensors 95 | - Mop attached 96 | - Water box attached 97 | - Water shortage 98 | 99 | ## Camera 100 | 101 | If your vacuum has a builtin camera, you can use [go2rtc](https://github.com/AlexxIT/go2rtc) made by @AlexxIT. Steps [here](https://github.com/AlexxIT/go2rtc#source-roborock) 102 | 103 | --- 104 | ## Special thanks 105 | 106 | Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 107 | And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor 108 | 109 | --- 110 | -------------------------------------------------------------------------------- /custom_components/__init__.py: -------------------------------------------------------------------------------- 1 | """Custom Integrations for Home Assistant.""" 2 | -------------------------------------------------------------------------------- /custom_components/roborock/__init__.py: -------------------------------------------------------------------------------- 1 | """The Roborock component.""" 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import logging 6 | from datetime import timedelta 7 | from pathlib import Path 8 | 9 | from roborock import RoborockException 10 | from roborock.web_api import RoborockApiClient 11 | from roborock.version_1_apis import RoborockMqttClientV1 as RoborockMqttClient 12 | from roborock.version_1_apis import RoborockLocalClientV1 as RoborockLocalClient 13 | from roborock.containers import HomeData, HomeDataProduct, UserData 14 | from roborock.protocol import RoborockProtocol 15 | from slugify import slugify 16 | 17 | from homeassistant.config_entries import ConfigEntry 18 | from homeassistant.core import HomeAssistant 19 | from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady 20 | from .const import ( 21 | CONF_CLOUD_INTEGRATION, 22 | CONF_HOME_DATA, 23 | CONF_INCLUDE_SHARED, 24 | DOMAIN, 25 | PLATFORMS, 26 | VACUUM, 27 | ) 28 | from .coordinator import RoborockDataUpdateCoordinator 29 | from .domain import EntryData 30 | from .roborock_typing import ConfigEntryData, DeviceNetwork, RoborockHassDeviceInfo 31 | from .store import LocalCalendarStore, STORAGE_PATH 32 | 33 | SCAN_INTERVAL = timedelta(seconds=30) 34 | 35 | _LOGGER = logging.getLogger(__name__) 36 | 37 | 38 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 39 | """Set up roborock from a config entry.""" 40 | _LOGGER.debug("Integration async setup entry: %s", entry.as_dict()) 41 | hass.data.setdefault(DOMAIN, {}) 42 | 43 | data: ConfigEntryData = entry.data 44 | user_data = UserData.from_dict(data.get("user_data")) 45 | base_url = data.get("base_url") 46 | username = data.get("username") 47 | vacuum_options = entry.options.get(VACUUM, {}) 48 | integration_options = entry.options.get(DOMAIN, {}) 49 | cloud_integration = integration_options.get(CONF_CLOUD_INTEGRATION, False) 50 | include_shared = ( 51 | vacuum_options.get(CONF_INCLUDE_SHARED, True) 52 | ) 53 | 54 | device_network = data.get("device_network", {}) 55 | try: 56 | api_client = RoborockApiClient(username, base_url) 57 | _LOGGER.debug("Requesting home data") 58 | home_data = await api_client.get_home_data(user_data) 59 | hass.config_entries.async_update_entry( 60 | entry, data={CONF_HOME_DATA: home_data.as_dict(), **data} 61 | ) 62 | if home_data is None: 63 | raise ConfigEntryError("Missing home data. Could not found it in cache") 64 | except Exception as e: 65 | conf_home_data = data.get("home_data") 66 | home_data = HomeData.from_dict(conf_home_data) if conf_home_data else None 67 | if home_data is None: 68 | raise e 69 | 70 | _LOGGER.debug("Got home data %s", home_data) 71 | 72 | platforms = [platform for platform in PLATFORMS if entry.options.get(platform, True)] 73 | 74 | entry_data: EntryData = hass.data.setdefault(DOMAIN, {}).setdefault( 75 | entry.entry_id, 76 | EntryData(devices={}, platforms=platforms) 77 | ) 78 | devices_entry_data = entry_data["devices"] 79 | 80 | devices = ( 81 | home_data.devices + home_data.received_devices 82 | if include_shared 83 | else home_data.devices 84 | ) 85 | if not cloud_integration: 86 | devices_without_ip = [_device for _device in devices if _device.duid not in device_network] 87 | if len(devices_without_ip) > 0: 88 | device_network.update(await get_local_devices_info()) 89 | for _device in devices: 90 | device_id = _device.duid 91 | try: 92 | product: HomeDataProduct = next( 93 | product 94 | for product in home_data.products 95 | if product.id == _device.product_id 96 | ) 97 | 98 | device_info = RoborockHassDeviceInfo( 99 | device=_device, 100 | model=product.model, 101 | ) 102 | 103 | map_client = await hass.async_add_executor_job(RoborockMqttClient, user_data, device_info) 104 | 105 | if not cloud_integration: 106 | network = device_network.get(device_id) 107 | if network is None: 108 | networking = await map_client.get_networking() 109 | network = DeviceNetwork(ip=networking.ip, mac="") 110 | device_network[device_id] = network 111 | hass.config_entries.async_update_entry( 112 | entry, data={"device_network": device_network, **data} 113 | ) 114 | device_info.host = network.get("ip") 115 | 116 | main_client = RoborockLocalClient(device_info) 117 | else: 118 | main_client = map_client 119 | data_coordinator = RoborockDataUpdateCoordinator( 120 | hass, main_client, map_client, device_info, home_data.rooms 121 | ) 122 | path = Path(hass.config.path(STORAGE_PATH.format(key=f"{DOMAIN}.{entry.entry_id}.{slugify(device_id)}"))) 123 | devices_entry_data[device_id] = { 124 | "coordinator": data_coordinator, 125 | "calendar": LocalCalendarStore(hass, path) 126 | } 127 | except RoborockException: 128 | _LOGGER.warning(f"Failing setting up device {device_id}") 129 | 130 | await asyncio.gather( 131 | *( 132 | device_entry_data["coordinator"].async_config_entry_first_refresh() 133 | for device_entry_data in devices_entry_data.values() 134 | ), 135 | return_exceptions=True 136 | ) 137 | 138 | success_coordinators = [] 139 | for device_id, device_entry_data in devices_entry_data.items(): 140 | _coordinator = device_entry_data["coordinator"] 141 | if not _coordinator.last_update_success: 142 | _coordinator.release() 143 | devices_entry_data[device_id] = None 144 | else: 145 | success_coordinators.append(_coordinator) 146 | 147 | if len(success_coordinators) == 0: 148 | # Don't start if no coordinators succeeded. 149 | raise ConfigEntryNotReady("There are no devices that can currently be reached.") 150 | 151 | await hass.config_entries.async_forward_entry_setups(entry, platforms) 152 | 153 | entry.async_on_unload(entry.add_update_listener(async_reload_entry)) 154 | return True 155 | 156 | 157 | async def get_local_devices_info() -> dict[str, DeviceNetwork]: 158 | """Get local device info.""" 159 | discovered_devices = await RoborockProtocol(timeout=10).discover() 160 | 161 | devices_network = { 162 | discovered_device.duid: DeviceNetwork(ip=discovered_device.ip, mac="") 163 | for discovered_device in discovered_devices 164 | } 165 | 166 | return devices_network 167 | 168 | 169 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 170 | """Handle removal of an entry.""" 171 | data: EntryData = hass.data[DOMAIN].get( 172 | entry.entry_id 173 | ) 174 | unloaded = all( 175 | await asyncio.gather( 176 | *[ 177 | hass.config_entries.async_forward_entry_unload(entry, platform) 178 | for platform in data.get("platforms") 179 | ] 180 | ) 181 | ) 182 | if unloaded: 183 | hass.data[DOMAIN].pop(entry.entry_id) 184 | for device_entry_data in data.get("devices").values(): 185 | device_entry_data["coordinator"].release() 186 | 187 | return unloaded 188 | 189 | 190 | async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: 191 | """Reload config entry.""" 192 | await async_unload_entry(hass, entry) 193 | await async_setup_entry(hass, entry) 194 | -------------------------------------------------------------------------------- /custom_components/roborock/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock binary sensors.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from collections.abc import Callable 6 | from dataclasses import dataclass 7 | 8 | from homeassistant.components.binary_sensor import ( 9 | BinarySensorDeviceClass, 10 | BinarySensorEntity, 11 | BinarySensorEntityDescription, 12 | ) 13 | from homeassistant.config_entries import ConfigEntry 14 | from homeassistant.core import HomeAssistant, callback 15 | from homeassistant.helpers.entity import EntityCategory 16 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 17 | from homeassistant.util import slugify 18 | 19 | from . import EntryData 20 | from .const import ( 21 | DOMAIN, 22 | MODELS_VACUUM_WITH_MOP, 23 | MODELS_VACUUM_WITH_SEPARATE_MOP, 24 | ) 25 | from .coordinator import RoborockDataUpdateCoordinator 26 | from .device import RoborockCoordinatedEntity 27 | from .roborock_typing import RoborockHassDeviceInfo 28 | 29 | _LOGGER = logging.getLogger(__name__) 30 | 31 | ATTR_MOP_ATTACHED = "is_water_box_carriage_attached" 32 | ATTR_WATER_BOX_ATTACHED = "is_water_box_attached" 33 | ATTR_WATER_SHORTAGE = "is_water_shortage" 34 | ATTR_MOP_DRYING = "dry_status" 35 | 36 | @dataclass 37 | class RoborockBinarySensorDescription(BinarySensorEntityDescription): 38 | """A class that describes binary sensor entities.""" 39 | 40 | value: Callable = None 41 | parent_key: str = None 42 | 43 | 44 | VACUUM_SENSORS = { 45 | ATTR_WATER_BOX_ATTACHED: RoborockBinarySensorDescription( 46 | key="water_box_status", 47 | name="Water box attached", 48 | translation_key="water_box_attached", 49 | icon="mdi:water", 50 | parent_key="status", 51 | entity_registry_enabled_default=True, 52 | device_class=BinarySensorDeviceClass.CONNECTIVITY, 53 | entity_category=EntityCategory.DIAGNOSTIC, 54 | ), 55 | ATTR_WATER_SHORTAGE: RoborockBinarySensorDescription( 56 | key="water_shortage_status", 57 | name="Water shortage", 58 | translation_key="water_shortage", 59 | icon="mdi:water", 60 | parent_key="status", 61 | entity_registry_enabled_default=True, 62 | device_class=BinarySensorDeviceClass.PROBLEM, 63 | entity_category=EntityCategory.DIAGNOSTIC, 64 | ), 65 | ATTR_MOP_DRYING: RoborockBinarySensorDescription( 66 | key="dry_status", 67 | icon="mdi:heat-wave", 68 | parent_key="status", 69 | name="Mop drying", 70 | translation_key="mop_drying_status", 71 | device_class=BinarySensorDeviceClass.RUNNING, 72 | entity_category=EntityCategory.DIAGNOSTIC, 73 | ), 74 | } 75 | 76 | VACUUM_SENSORS_SEPARATE_MOP = { 77 | **VACUUM_SENSORS, 78 | ATTR_MOP_ATTACHED: RoborockBinarySensorDescription( 79 | key="water_box_carriage_status", 80 | name="Mop attached", 81 | translation_key="mop_attached", 82 | icon="mdi:square-rounded", 83 | parent_key="status", 84 | entity_registry_enabled_default=True, 85 | device_class=BinarySensorDeviceClass.CONNECTIVITY, 86 | entity_category=EntityCategory.DIAGNOSTIC, 87 | ), 88 | } 89 | 90 | 91 | async def async_setup_entry( 92 | hass: HomeAssistant, 93 | config_entry: ConfigEntry, 94 | async_add_entities: AddEntitiesCallback, 95 | ) -> None: 96 | """Only vacuums with mop should have binary sensor registered.""" 97 | domain_data: EntryData = hass.data[DOMAIN][ 98 | config_entry.entry_id 99 | ] 100 | 101 | entities: list[RoborockBinarySensor] = [] 102 | for _device_id, device_entry_data in domain_data.get("devices").items(): 103 | coordinator = device_entry_data["coordinator"] 104 | device_info = coordinator.data 105 | model = device_info.model 106 | if model not in MODELS_VACUUM_WITH_MOP: 107 | return 108 | 109 | sensors = VACUUM_SENSORS 110 | if model in MODELS_VACUUM_WITH_SEPARATE_MOP: 111 | sensors = VACUUM_SENSORS_SEPARATE_MOP 112 | unique_id = slugify(device_info.device.duid) 113 | if coordinator.data: 114 | device_prop = device_info.props 115 | if device_prop: 116 | for sensor, description in sensors.items(): 117 | parent_key_data = getattr(device_prop, description.parent_key) 118 | if parent_key_data is None: 119 | _LOGGER.debug( 120 | "It seems the %s does not support the %s as the initial value is None", 121 | device_info.model, 122 | sensor, 123 | ) 124 | continue 125 | entities.append( 126 | RoborockBinarySensor( 127 | f"{sensor}_{unique_id}", 128 | device_info, 129 | coordinator, 130 | description, 131 | ) 132 | ) 133 | else: 134 | _LOGGER.warning("Failed setting up binary sensors no Roborock data") 135 | 136 | async_add_entities(entities) 137 | 138 | 139 | class RoborockBinarySensor(RoborockCoordinatedEntity, BinarySensorEntity): 140 | """Representation of a Roborock binary sensor.""" 141 | 142 | entity_description: RoborockBinarySensorDescription 143 | 144 | def __init__( 145 | self, 146 | unique_id: str, 147 | device_info: RoborockHassDeviceInfo, 148 | coordinator: RoborockDataUpdateCoordinator, 149 | description: RoborockBinarySensorDescription, 150 | ) -> None: 151 | """Initialize the entity.""" 152 | BinarySensorEntity.__init__(self) 153 | RoborockCoordinatedEntity.__init__(self, device_info, coordinator, unique_id) 154 | self.entity_description = description 155 | self._attr_entity_registry_enabled_default = ( 156 | description.entity_registry_enabled_default 157 | ) 158 | self._attr_is_on = self._determine_native_value() 159 | 160 | @callback 161 | def _handle_coordinator_update(self) -> None: 162 | native_value = self._determine_native_value() 163 | if native_value is not None: 164 | self._attr_is_on = native_value 165 | super()._handle_coordinator_update() 166 | 167 | def _determine_native_value(self): 168 | """Determine native value.""" 169 | data = self.coordinator.data.props 170 | if data is None: 171 | return 172 | if self.entity_description.parent_key: 173 | data = getattr(data, self.entity_description.parent_key) 174 | if data is None: 175 | return 176 | 177 | native_value = getattr(data, self.entity_description.key) 178 | if native_value is not None and self.entity_description.value: 179 | return self.entity_description.value(native_value) 180 | 181 | return native_value 182 | -------------------------------------------------------------------------------- /custom_components/roborock/button.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock button.""" 2 | from __future__ import annotations 3 | 4 | from dataclasses import dataclass 5 | 6 | from homeassistant.components.button import ( 7 | ButtonDeviceClass, 8 | ButtonEntity, 9 | ButtonEntityDescription, 10 | ) 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | from homeassistant.util import slugify 15 | from roborock.roborock_typing import RoborockCommand 16 | 17 | from . import EntryData 18 | from .const import DOMAIN 19 | from .coordinator import RoborockDataUpdateCoordinator 20 | from .device import RoborockCoordinatedEntity 21 | from .roborock_typing import RoborockHassDeviceInfo 22 | 23 | 24 | @dataclass 25 | class RoborockButtonDescriptionMixin: 26 | """Define an entity description mixin for button entities.""" 27 | 28 | command: RoborockCommand 29 | param: list | dict | None 30 | 31 | 32 | @dataclass 33 | class RoborockButtonDescription( 34 | ButtonEntityDescription, RoborockButtonDescriptionMixin 35 | ): 36 | """Describes a Roborock button entity.""" 37 | 38 | 39 | CONSUMABLE_BUTTON_DESCRIPTIONS = [ 40 | RoborockButtonDescription( 41 | key="consumable_reset_sensor", 42 | device_class=ButtonDeviceClass.UPDATE, 43 | translation_key="reset_sensor_consumable", 44 | name="Reset sensor consumable", 45 | command=RoborockCommand.RESET_CONSUMABLE, 46 | param=["sensor_dirty_time"], 47 | ), 48 | RoborockButtonDescription( 49 | key="consumable_reset_filter", 50 | device_class=ButtonDeviceClass.UPDATE, 51 | translation_key="reset_filter_consumable", 52 | name="Reset filter consumable", 53 | command=RoborockCommand.RESET_CONSUMABLE, 54 | param=["filter_work_time"], 55 | ), 56 | RoborockButtonDescription( 57 | key="consumable_reset_side_brush", 58 | device_class=ButtonDeviceClass.UPDATE, 59 | translation_key="reset_side_brush_consumable", 60 | name="Reset side brush consumable", 61 | command=RoborockCommand.RESET_CONSUMABLE, 62 | param=["side_brush_work_time"], 63 | ), 64 | RoborockButtonDescription( 65 | key="consumable_reset_main_brush", 66 | device_class=ButtonDeviceClass.UPDATE, 67 | translation_key="reset_main_brush_consumable", 68 | name="Reset main brush consumable", 69 | command=RoborockCommand.RESET_CONSUMABLE, 70 | param=["main_brush_work_time"], 71 | ), 72 | ] 73 | 74 | 75 | async def async_setup_entry( 76 | hass: HomeAssistant, 77 | config_entry: ConfigEntry, 78 | async_add_entities: AddEntitiesCallback, 79 | ) -> None: 80 | """Set up Roborock button platform.""" 81 | domain_data: EntryData = hass.data[DOMAIN][ 82 | config_entry.entry_id 83 | ] 84 | 85 | entities: list[RoborockButtonEntity] = [] 86 | for device_id, device_entry_data in domain_data.get("devices").items(): 87 | coordinator = device_entry_data["coordinator"] 88 | device_info = coordinator.data 89 | for description in CONSUMABLE_BUTTON_DESCRIPTIONS: 90 | entities.append( 91 | RoborockButtonEntity( 92 | f"{description.key}_{slugify(device_id)}", 93 | device_info, 94 | coordinator, 95 | description, 96 | ) 97 | ) 98 | async_add_entities(entities) 99 | 100 | 101 | class RoborockButtonEntity(RoborockCoordinatedEntity, ButtonEntity): 102 | """A class to define Roborock button entities.""" 103 | 104 | entity_description: RoborockButtonDescription 105 | 106 | def __init__( 107 | self, 108 | unique_id: str, 109 | device_info: RoborockHassDeviceInfo, 110 | coordinator: RoborockDataUpdateCoordinator, 111 | entity_description: RoborockButtonDescription, 112 | ) -> None: 113 | """Create a button entity.""" 114 | super().__init__(device_info, coordinator, unique_id) 115 | self.entity_description = entity_description 116 | 117 | async def async_press(self) -> None: 118 | """Press the button.""" 119 | await self.send(self.entity_description.command, self.entity_description.param) 120 | -------------------------------------------------------------------------------- /custom_components/roborock/common/types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List, Tuple, Union 2 | 3 | Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]] 4 | Colors = Dict[str, Color] 5 | Drawables = List[str] 6 | Texts = List[Any] 7 | Sizes = Dict[str, float] 8 | ImageConfig = Dict[str, Any] 9 | CalibrationPoints = List[Dict[str, Dict[str, Union[float, int]]]] 10 | -------------------------------------------------------------------------------- /custom_components/roborock/coordinator.py: -------------------------------------------------------------------------------- 1 | """Coordinatory for Roborock devices.""" 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import logging 6 | from datetime import timedelta 7 | 8 | from homeassistant.core import HomeAssistant 9 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed 10 | from roborock.version_1_apis import RoborockClientV1 as RoborockClient 11 | from roborock.version_1_apis import RoborockMqttClientV1 as RoborockMqttClient 12 | from roborock.containers import HomeDataRoom, MultiMapsList, RoborockBase 13 | from roborock.exceptions import RoborockException 14 | 15 | from .const import DOMAIN 16 | from .roborock_typing import RoborockHassDeviceInfo 17 | 18 | SCAN_INTERVAL = timedelta(seconds=30) 19 | 20 | _LOGGER = logging.getLogger(__name__) 21 | 22 | 23 | class RoborockDataUpdateCoordinator( 24 | DataUpdateCoordinator[RoborockHassDeviceInfo] 25 | ): 26 | """Class to manage fetching data from the API.""" 27 | 28 | def __init__( 29 | self, 30 | hass: HomeAssistant, 31 | client: RoborockClient, 32 | map_client: RoborockMqttClient, 33 | device_info: RoborockHassDeviceInfo, 34 | rooms: list[HomeDataRoom] 35 | ) -> None: 36 | """Initialize.""" 37 | super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) 38 | self.api = client 39 | self.map_api = map_client 40 | self.devices_maps: dict[str, MultiMapsList] = {} 41 | self.device_info = device_info 42 | self.rooms = rooms 43 | self.scheduled_refresh: asyncio.TimerHandle | None = None 44 | 45 | def schedule_refresh(self) -> None: 46 | """Schedule coordinator refresh after 1 second.""" 47 | if self.scheduled_refresh: 48 | self.scheduled_refresh.cancel() 49 | self.scheduled_refresh = self.hass.loop.call_later( 50 | 1, lambda: asyncio.create_task(self.async_refresh()) 51 | ) 52 | 53 | def release(self) -> None: 54 | """Disconnect from API.""" 55 | if self.scheduled_refresh: 56 | self.scheduled_refresh.cancel() 57 | self.api.sync_disconnect() 58 | if self.api != self.map_api: 59 | try: 60 | self.map_api.sync_disconnect() 61 | except RoborockException: 62 | _LOGGER.warning("Failed to disconnect from map api") 63 | 64 | async def fill_device_prop(self, device_info: RoborockHassDeviceInfo) -> None: 65 | """Get device properties.""" 66 | device_prop = await self.api.get_prop() 67 | if device_prop: 68 | if device_info.props: 69 | device_info.props.update(device_prop) 70 | else: 71 | device_info.props = device_prop 72 | 73 | def update_device(self, device_id: str, attribute: str, data: RoborockBase): 74 | """Update device based on prop attribute.""" 75 | if device_id == self.device_info.device.duid: 76 | setattr(self.device_info.props, attribute, data) 77 | self.hass.loop.call_soon_threadsafe(self.async_set_updated_data, self.device_info) 78 | 79 | async def fill_room_mapping(self, device_info: RoborockHassDeviceInfo) -> None: 80 | """Build the room mapping - only works for local api.""" 81 | if device_info.room_mapping is None: 82 | room_mapping = await self.api.get_room_mapping() 83 | if room_mapping: 84 | room_iot_name = {str(room.id): room.name for room in self.rooms} 85 | device_info.room_mapping = { 86 | rm.segment_id: room_iot_name.get(str(rm.iot_id)) 87 | for rm in room_mapping 88 | } 89 | 90 | async def fill_device_multi_maps_list(self, device_info: RoborockHassDeviceInfo) -> None: 91 | """Get multi maps list.""" 92 | if device_info.map_mapping is None: 93 | multi_maps_list = await self.api.get_multi_maps_list() 94 | if multi_maps_list: 95 | map_mapping = { 96 | map_info.mapFlag: map_info.name for map_info in multi_maps_list.map_info} 97 | device_info.map_mapping = map_mapping 98 | 99 | async def fill_device_info(self, device_info: RoborockHassDeviceInfo): 100 | """Merge device information.""" 101 | await asyncio.gather( 102 | *([ 103 | self.fill_device_prop(device_info), 104 | asyncio.gather( 105 | *([ 106 | self.fill_device_multi_maps_list(device_info), 107 | self.fill_room_mapping(device_info), 108 | ]), return_exceptions=True 109 | ) 110 | ]) 111 | ) 112 | 113 | async def _async_update_data(self) -> RoborockHassDeviceInfo: 114 | """Update data via library.""" 115 | try: 116 | await self.fill_device_info(self.device_info) 117 | except RoborockException as ex: 118 | raise UpdateFailed(ex) from ex 119 | return self.device_info 120 | -------------------------------------------------------------------------------- /custom_components/roborock/device.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock device base class.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from typing import Optional 6 | 7 | from homeassistant.exceptions import HomeAssistantError 8 | from homeassistant.helpers.entity import DeviceInfo, Entity 9 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 10 | from roborock.version_1_apis import RoborockClientV1 as RoborockClient 11 | from roborock.version_1_apis.roborock_client_v1 import RT 12 | from roborock.containers import Status, Consumable 13 | from roborock.exceptions import RoborockException 14 | from roborock.roborock_typing import RoborockCommand 15 | 16 | from .const import DOMAIN 17 | from .coordinator import RoborockDataUpdateCoordinator 18 | from .roborock_typing import RoborockHassDeviceInfo 19 | 20 | _LOGGER = logging.getLogger(__name__) 21 | 22 | 23 | class RoborockEntity(Entity): 24 | """Representation of a base a coordinated Roborock Entity.""" 25 | 26 | _attr_has_entity_name = True 27 | 28 | def __init__( 29 | self, 30 | device_info: RoborockHassDeviceInfo, 31 | unique_id: str, 32 | api: RoborockClient, 33 | ) -> None: 34 | """Initialize the coordinated Roborock Device.""" 35 | self._device_name = device_info.device.name 36 | self._attr_unique_id = unique_id 37 | self._device_id = str(device_info.device.duid) 38 | self._device_model = device_info.model 39 | self._fw_version = device_info.device.fv 40 | self._device_info = device_info 41 | self.api = api 42 | 43 | @property 44 | def device_info(self) -> DeviceInfo: 45 | """Return the device info.""" 46 | return DeviceInfo( 47 | name=self._device_name, 48 | identifiers={(DOMAIN, self._device_id)}, 49 | manufacturer="Roborock", 50 | model=self._device_model, 51 | sw_version=self._fw_version, 52 | ) 53 | 54 | @property 55 | def _device_status(self) -> Status: 56 | props = self._device_info.props 57 | if props is None: 58 | return Status() 59 | status = props.status 60 | if status is None: 61 | return Status() 62 | return status 63 | 64 | def is_valid_map(self) -> bool: 65 | """Check if map is valid.""" 66 | return self._device_info.is_map_valid 67 | 68 | def set_valid_map(self) -> None: 69 | """Set map as valid to avoid unnecessary updates.""" 70 | self._device_info.is_map_valid = True 71 | 72 | def set_invalid_map(self) -> None: 73 | """Set map as invalid so it can be updated.""" 74 | self._device_info.is_map_valid = False 75 | 76 | async def send( 77 | self, 78 | method: RoborockCommand, 79 | params: Optional[list | dict] = None, 80 | return_type: Optional[type[RT]] = None, 81 | ) -> RT: 82 | """Send a command to a vacuum cleaner.""" 83 | try: 84 | response = await self.api.send_command(method, params, return_type) 85 | except RoborockException as err: 86 | raise HomeAssistantError( 87 | f"Error while calling {method.name} with {params}" 88 | ) from err 89 | return response 90 | 91 | 92 | class RoborockCoordinatedEntity(RoborockEntity, CoordinatorEntity[RoborockDataUpdateCoordinator]): 93 | """Representation of a base a coordinated Roborock Entity.""" 94 | 95 | _attr_has_entity_name = True 96 | 97 | def __init__( 98 | self, 99 | device_info: RoborockHassDeviceInfo, 100 | coordinator: RoborockDataUpdateCoordinator, 101 | unique_id: str | None = None, 102 | ) -> None: 103 | """Initialize the coordinated Roborock Device.""" 104 | RoborockEntity.__init__(self, device_info, unique_id, coordinator.api) 105 | CoordinatorEntity.__init__(self, coordinator) 106 | self._device_name = device_info.device.name 107 | self._attr_unique_id = unique_id 108 | self._device_id = str(device_info.device.duid) 109 | self._device_model = device_info.model 110 | self._fw_version = device_info.device.fv 111 | 112 | async def send( 113 | self, 114 | method: RoborockCommand, 115 | params: Optional[list | dict] = None, 116 | return_type: Optional[type[RT]] = None, 117 | ) -> RT: 118 | """Send a command to a vacuum cleaner.""" 119 | response = await super().send(method, params, return_type) 120 | self.coordinator.schedule_refresh() 121 | return response 122 | 123 | def _update_from_listener(self, value: Status | Consumable): 124 | """Update the status or consumable data from a listener and then write the new entity state.""" 125 | if isinstance(value, Status): 126 | self.coordinator.device_info.props.status = value 127 | else: 128 | self.coordinator.device_info.props.consumable = value 129 | self.coordinator.data = self.coordinator.device_info.props 130 | self.schedule_update_ha_state() 131 | 132 | -------------------------------------------------------------------------------- /custom_components/roborock/domain.py: -------------------------------------------------------------------------------- 1 | """Domain dict for Roborock.""" 2 | from typing import Optional, TypedDict 3 | 4 | from . import RoborockDataUpdateCoordinator 5 | from .store import LocalCalendarStore 6 | 7 | 8 | class DeviceEntryData(TypedDict): 9 | """Define integration device entry data.""" 10 | 11 | coordinator: RoborockDataUpdateCoordinator 12 | calendar: LocalCalendarStore 13 | 14 | 15 | class EntryData(TypedDict): 16 | """Define integration entry data.""" 17 | 18 | devices: dict[str, Optional[DeviceEntryData]] 19 | platforms: list[str] 20 | -------------------------------------------------------------------------------- /custom_components/roborock/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "roborock", 3 | "name": "Roborock", 4 | "codeowners": [ 5 | "@humbertogontijo" 6 | ], 7 | "config_flow": true, 8 | "documentation": "https://github.com/humbertogontijo/homeassistant-roborock", 9 | "integration_type": "hub", 10 | "iot_class": "local_polling", 11 | "issue_tracker": "https://github.com/humbertogontijo/homeassistant-roborock/issues", 12 | "loggers": [ 13 | "custom_components.roborock", 14 | "roborock" 15 | ], 16 | "requirements": [ 17 | "python-roborock==2.12.1", 18 | "dacite==1.8.0" 19 | ], 20 | "version": "1.0.16" 21 | } 22 | -------------------------------------------------------------------------------- /custom_components/roborock/number.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock button.""" 2 | from __future__ import annotations 3 | 4 | from dataclasses import dataclass 5 | from collections.abc import Callable 6 | from typing import Any 7 | from collections.abc import Coroutine 8 | 9 | from homeassistant.components.number import NumberEntity, NumberEntityDescription 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | from homeassistant.util import slugify 14 | from roborock.version_1_apis import AttributeCache 15 | from roborock.command_cache import CacheableAttribute 16 | 17 | from . import EntryData 18 | from .const import DOMAIN 19 | from .coordinator import RoborockDataUpdateCoordinator 20 | from .device import RoborockCoordinatedEntity 21 | from .roborock_typing import RoborockHassDeviceInfo 22 | 23 | 24 | @dataclass 25 | class RoborockNumberDescriptionMixin: 26 | """Define an entity description mixin for button entities.""" 27 | 28 | # Gets the status of the switch 29 | cache_key: CacheableAttribute 30 | # Sets the status of the switch 31 | update_value: Callable[[AttributeCache, bool], Coroutine[Any, Any, dict]] 32 | 33 | 34 | @dataclass 35 | class RoborockNumberDescription( 36 | NumberEntityDescription, RoborockNumberDescriptionMixin 37 | ): 38 | """Describes a Roborock button entity.""" 39 | 40 | 41 | NUMBER_DESCRIPTIONS = [ 42 | RoborockNumberDescription( 43 | key="sound_volume", 44 | native_unit_of_measurement="%", 45 | native_max_value=100, 46 | native_min_value=0, 47 | native_step=1, 48 | translation_key="sound_volume", 49 | name="Sound Volume", 50 | cache_key=CacheableAttribute.sound_volume, 51 | update_value=lambda cache, value: cache.update_value([value]), 52 | ) 53 | ] 54 | 55 | 56 | async def async_setup_entry( 57 | hass: HomeAssistant, 58 | config_entry: ConfigEntry, 59 | async_add_entities: AddEntitiesCallback, 60 | ) -> None: 61 | """Set up Roborock button platform.""" 62 | domain_data: EntryData = hass.data[DOMAIN][ 63 | config_entry.entry_id 64 | ] 65 | 66 | entities: list[RoborockNumberEntity] = [] 67 | for _device_id, device_entry_data in domain_data.get("devices").items(): 68 | coordinator = device_entry_data["coordinator"] 69 | device_info = coordinator.data 70 | for description in NUMBER_DESCRIPTIONS: 71 | entities.append( 72 | RoborockNumberEntity( 73 | f"{description.key}_{slugify(device_info.device.duid)}", 74 | device_info, 75 | coordinator, 76 | description, 77 | ) 78 | ) 79 | async_add_entities(entities) 80 | 81 | 82 | class RoborockNumberEntity(RoborockCoordinatedEntity, NumberEntity): 83 | """A class to define Roborock button entities.""" 84 | 85 | entity_description: RoborockNumberDescription 86 | 87 | def __init__( 88 | self, 89 | unique_id: str, 90 | device_info: RoborockHassDeviceInfo, 91 | coordinator: RoborockDataUpdateCoordinator, 92 | entity_description: RoborockNumberDescription, 93 | ) -> None: 94 | """Create a button entity.""" 95 | super().__init__(device_info, coordinator, unique_id) 96 | self.entity_description = entity_description 97 | 98 | @property 99 | def native_value(self) -> float | None: 100 | """Get native value.""" 101 | return self.api.cache.get(self.entity_description.cache_key).value 102 | 103 | async def async_set_native_value(self, value: float) -> None: 104 | """Set native value.""" 105 | int_value = int(value) 106 | await self.entity_description.update_value(self.api.cache.get(self.entity_description.cache_key), int_value) 107 | -------------------------------------------------------------------------------- /custom_components/roborock/roborock_typing.py: -------------------------------------------------------------------------------- 1 | """Typing for Roborock integration.""" 2 | from dataclasses import dataclass 3 | from typing import Optional, TypedDict 4 | 5 | from roborock import DeviceData, DeviceProp 6 | 7 | 8 | class DeviceNetwork(TypedDict): 9 | """Define any network information needed.""" 10 | 11 | ip: str 12 | mac: str 13 | 14 | 15 | class ConfigEntryData(TypedDict): 16 | """Define data stored by integration.""" 17 | 18 | user_data: dict 19 | home_data: dict 20 | base_url: str 21 | username: str 22 | device_network: dict[str, DeviceNetwork] 23 | 24 | 25 | @dataclass 26 | class RoborockHassDeviceInfo(DeviceData): 27 | """Define a help class to carry device information.""" 28 | 29 | props: Optional[DeviceProp] = None 30 | is_map_valid: Optional[bool] = False 31 | map_mapping: Optional[dict[int, str]] = None 32 | room_mapping: Optional[dict[int, str]] = None 33 | current_room: Optional[int] = None 34 | -------------------------------------------------------------------------------- /custom_components/roborock/select.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock select.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from collections.abc import Callable 6 | from dataclasses import dataclass 7 | 8 | from roborock.roborock_message import RoborockDataProtocol 9 | 10 | from homeassistant.components.select import SelectEntity, SelectEntityDescription 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | from homeassistant.util import slugify 15 | from roborock.containers import Status 16 | from roborock.roborock_typing import RoborockCommand 17 | 18 | from . import EntryData, RoborockHassDeviceInfo 19 | from .const import DOMAIN 20 | from .coordinator import RoborockDataUpdateCoordinator 21 | from .device import RoborockCoordinatedEntity 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | 26 | @dataclass 27 | class RoborockSelectDescriptionMixin: 28 | """Define an entity description mixin for select entities.""" 29 | 30 | api_command: RoborockCommand 31 | value_fn: Callable[[Status], str] 32 | options_lambda: Callable[[Status], list[str]] 33 | option_lambda: Callable[[tuple[str, Status]], list[int]] 34 | 35 | 36 | @dataclass 37 | class RoborockSelectDescription( 38 | SelectEntityDescription, RoborockSelectDescriptionMixin 39 | ): 40 | """Class to describe an Roborock select entity.""" 41 | 42 | protocol_listener: RoborockDataProtocol | None = None 43 | 44 | 45 | 46 | SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [ 47 | RoborockSelectDescription( 48 | key="water_box_mode", 49 | translation_key="mop_intensity", 50 | api_command=RoborockCommand.SET_WATER_BOX_CUSTOM_MODE, 51 | value_fn=lambda data: data.water_box_mode.name if data and data.water_box_mode else None, 52 | options_lambda=lambda data: data.water_box_mode.keys() if data and data.water_box_mode else None, 53 | option_lambda=lambda data: [ 54 | v for k, v in data[1].water_box_mode.items() if k == data[0] 55 | ], 56 | protocol_listener=RoborockDataProtocol.WATER_BOX_MODE 57 | ), 58 | RoborockSelectDescription( 59 | key="mop_mode", 60 | translation_key="mop_mode", 61 | api_command=RoborockCommand.SET_MOP_MODE, 62 | value_fn=lambda data: data.mop_mode.name if data and data.mop_mode else None, 63 | options_lambda=lambda data: data.mop_mode.keys() if data and data.mop_mode else None, 64 | option_lambda=lambda data: [ 65 | v for k, v in data[1].mop_mode.items() if k == data[0] 66 | ], 67 | ), 68 | ] 69 | 70 | 71 | async def async_setup_entry( 72 | hass: HomeAssistant, 73 | config_entry: ConfigEntry, 74 | async_add_entities: AddEntitiesCallback, 75 | ) -> None: 76 | """Set up Roborock select platform.""" 77 | domain_data: EntryData = hass.data[DOMAIN][ 78 | config_entry.entry_id 79 | ] 80 | entities: list[RoborockSelectEntity] = [] 81 | for _device_id, device_entry_data in domain_data.get("devices").items(): 82 | coordinator = device_entry_data["coordinator"] 83 | device_info = coordinator.data 84 | unique_id = slugify(device_info.device.duid) 85 | device_prop = device_info.props 86 | if device_prop: 87 | for description in SELECT_DESCRIPTIONS: 88 | if description.options_lambda(device_prop.status) is not None: 89 | entities.append( 90 | RoborockSelectEntity( 91 | f"{description.key}_{unique_id}", 92 | device_info, 93 | coordinator, 94 | description, 95 | ) 96 | ) 97 | else: 98 | _LOGGER.warning("Failed setting up selects: No Roborock data") 99 | async_add_entities(entities) 100 | 101 | 102 | class RoborockSelectEntity(RoborockCoordinatedEntity, SelectEntity): 103 | """A class to let you set options on a Roborock vacuum where the potential options are fixed.""" 104 | 105 | entity_description: RoborockSelectDescription 106 | 107 | def __init__( 108 | self, 109 | unique_id: str, 110 | device_info: RoborockHassDeviceInfo, 111 | coordinator: RoborockDataUpdateCoordinator, 112 | entity_description: RoborockSelectDescription, 113 | ) -> None: 114 | """Create a select entity.""" 115 | self.entity_description = entity_description 116 | self._attr_options = self.entity_description.options_lambda( 117 | device_info.props.status 118 | ) 119 | super().__init__(device_info, coordinator, unique_id) 120 | if (protocol := self.entity_description.protocol_listener) is not None: 121 | self.api.add_listener(protocol, self._update_from_listener, self.api.cache) 122 | 123 | async def async_select_option(self, option: str) -> None: 124 | """Set the option.""" 125 | await self.send( 126 | self.entity_description.api_command, 127 | self.entity_description.option_lambda((option, self._device_status)), 128 | ) 129 | 130 | @property 131 | def current_option(self) -> str | None: 132 | """Get the current status of the select entity from device_status.""" 133 | return self.entity_description.value_fn(self._device_status) 134 | -------------------------------------------------------------------------------- /custom_components/roborock/services.yaml: -------------------------------------------------------------------------------- 1 | vacuum_remote_control_start: 2 | name: Vacuum remote control start 3 | description: Start remote control of the vacuum cleaner. You can then move it with `remote_control_move`, when done call `remote_control_stop`. 4 | target: 5 | entity: 6 | integration: roborock 7 | domain: vacuum 8 | 9 | vacuum_remote_control_stop: 10 | name: Vacuum remote control stop 11 | description: Stop remote control mode of the vacuum cleaner. 12 | target: 13 | entity: 14 | integration: roborock 15 | domain: vacuum 16 | 17 | vacuum_remote_control_move: 18 | name: Vacuum remote control move 19 | description: Remote control the vacuum cleaner, make sure you first set it in remote control mode with `remote_control_start`. 20 | target: 21 | entity: 22 | integration: roborock 23 | domain: vacuum 24 | fields: 25 | velocity: 26 | name: Velocity 27 | description: Speed. 28 | selector: 29 | number: 30 | min: -0.29 31 | max: 0.29 32 | step: 0.01 33 | rotation: 34 | name: Rotation 35 | description: Rotation, between -179 degrees and 179 degrees. 36 | selector: 37 | number: 38 | min: -179 39 | max: 179 40 | unit_of_measurement: "°" 41 | duration: 42 | name: Duration 43 | description: Duration of the movement. 44 | selector: 45 | number: 46 | min: 1 47 | max: 86400 48 | unit_of_measurement: seconds 49 | 50 | vacuum_remote_control_move_step: 51 | name: Vacuum remote control move step 52 | description: Remote control the vacuum cleaner, only makes one move and then stops. 53 | target: 54 | entity: 55 | integration: roborock 56 | domain: vacuum 57 | fields: 58 | velocity: 59 | name: Velocity 60 | description: Speed. 61 | selector: 62 | number: 63 | min: -0.29 64 | max: 0.29 65 | step: 0.01 66 | rotation: 67 | name: Rotation 68 | description: Rotation. 69 | selector: 70 | number: 71 | min: -179 72 | max: 179 73 | unit_of_measurement: "°" 74 | duration: 75 | name: Duration 76 | description: Duration of the movement. 77 | selector: 78 | number: 79 | min: 1 80 | max: 86400 81 | unit_of_measurement: seconds 82 | 83 | vacuum_clean_zone: 84 | name: Vacuum clean zone 85 | description: Start the cleaning operation in the selected areas for the number of repeats indicated. 86 | target: 87 | entity: 88 | integration: roborock 89 | domain: vacuum 90 | fields: 91 | zone: 92 | name: Zone 93 | description: Array of zones. Each zone is an array of 4 integer values. 94 | required: true 95 | example: "[[23510,25311,25110,26362]]" 96 | selector: 97 | object: 98 | repeats: 99 | name: Repeats 100 | description: Number of cleaning repeats for each zone. 101 | selector: 102 | number: 103 | min: 1 104 | max: 3 105 | 106 | vacuum_goto: 107 | name: Vacuum go to 108 | description: Go to the specified coordinates 109 | target: 110 | entity: 111 | integration: roborock 112 | domain: vacuum 113 | fields: 114 | x_coord: 115 | name: X coordinate 116 | description: x-coordinate 117 | required: true 118 | example: 26300 119 | selector: 120 | text: 121 | y_coord: 122 | name: Y coordinate 123 | description: y-coordinate 124 | required: true 125 | example: 22500 126 | selector: 127 | text: 128 | 129 | vacuum_clean_segment: 130 | name: Vacuum clean segment 131 | description: Start cleaning of the specified segment(s). 132 | target: 133 | entity: 134 | integration: roborock 135 | domain: vacuum 136 | fields: 137 | segments: 138 | name: Segments 139 | description: Segments. 140 | required: true 141 | example: "[1,2]" 142 | selector: 143 | object: 144 | repeats: 145 | name: Repeats 146 | description: Number of cleaning repeats for each segment. 147 | selector: 148 | number: 149 | min: 1 150 | max: 3 151 | 152 | vacuum_load_multi_map: 153 | name: Vacuum camera map load multi map 154 | description: Change vacuum camera map. 155 | target: 156 | entity: 157 | integration: roborock 158 | domain: vacuum 159 | fields: 160 | map_flag: 161 | name: Map flag 162 | description: The id of the map to be loaded 163 | required: true 164 | example: 0 165 | selector: 166 | number: 167 | min: 0 168 | max: 4 169 | -------------------------------------------------------------------------------- /custom_components/roborock/store.py: -------------------------------------------------------------------------------- 1 | """Local storage for the Local Calendar integration.""" 2 | 3 | import asyncio 4 | from pathlib import Path 5 | 6 | from homeassistant.core import HomeAssistant 7 | 8 | STORAGE_PATH = ".storage/{key}.ics" 9 | 10 | 11 | class LocalCalendarStore: 12 | """Local calendar storage.""" 13 | 14 | def __init__(self, hass: HomeAssistant, path: Path) -> None: 15 | """Initialize LocalCalendarStore.""" 16 | self._hass = hass 17 | self._path = path 18 | self._lock = asyncio.Lock() 19 | 20 | async def async_load(self) -> str: 21 | """Load the calendar from disk.""" 22 | async with self._lock: 23 | return await self._hass.async_add_executor_job(self._load) 24 | 25 | def _load(self) -> str: 26 | """Load the calendar from disk.""" 27 | if not self._path.exists(): 28 | return "" 29 | return self._path.read_text() 30 | 31 | async def async_store(self, ics_content: str) -> None: 32 | """Persist the calendar to storage.""" 33 | async with self._lock: 34 | await self._hass.async_add_executor_job(self._store, ics_content) 35 | 36 | def _store(self, ics_content: str) -> None: 37 | """Persist the calendar to storage.""" 38 | self._path.write_text(ics_content) 39 | -------------------------------------------------------------------------------- /custom_components/roborock/switch.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock switch.""" 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import logging 6 | from collections.abc import Callable, Coroutine 7 | from dataclasses import dataclass 8 | from typing import Any 9 | 10 | from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.const import EntityCategory 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | from homeassistant.util import slugify 16 | from roborock.version_1_apis import AttributeCache 17 | from roborock.version_1_apis import RoborockClientV1 as RoborockClient 18 | from roborock.command_cache import CacheableAttribute 19 | 20 | from . import EntryData, RoborockHassDeviceInfo 21 | from .const import DOMAIN 22 | from .coordinator import RoborockDataUpdateCoordinator 23 | from .device import RoborockEntity 24 | 25 | _LOGGER = logging.getLogger(__name__) 26 | 27 | 28 | @dataclass 29 | class RoborockSwitchDescriptionMixin: 30 | """Define an entity description mixin for switch entities.""" 31 | 32 | # Gets the status of the switch 33 | cache_key: CacheableAttribute 34 | # Sets the status of the switch 35 | update_value: Callable[[AttributeCache, bool], Coroutine[Any, Any, dict]] 36 | # Attribute from cache 37 | attribute: str 38 | 39 | 40 | @dataclass 41 | class RoborockSwitchDescription( 42 | SwitchEntityDescription, RoborockSwitchDescriptionMixin 43 | ): 44 | """Class to describe an Roborock switch entity.""" 45 | 46 | 47 | SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [ 48 | RoborockSwitchDescription( 49 | cache_key=CacheableAttribute.child_lock_status, 50 | update_value=lambda cache, value: cache.update_value({"lock_status": 1 if value else 0}), 51 | attribute="lock_status", 52 | key="child_lock", 53 | name="Child lock", 54 | translation_key="child_lock", 55 | icon="mdi:account-lock", 56 | entity_category=EntityCategory.CONFIG, 57 | ), 58 | RoborockSwitchDescription( 59 | cache_key=CacheableAttribute.flow_led_status, 60 | update_value=lambda cache, value: cache.update_value({"status": 1 if value else 0}), 61 | attribute="status", 62 | key="flow_led_status", 63 | name="Status Indicator Light", 64 | translation_key="flow_led_status", 65 | icon="mdi:alarm-light-outline", 66 | entity_category=EntityCategory.CONFIG, 67 | ), 68 | RoborockSwitchDescription( 69 | cache_key=CacheableAttribute.dnd_timer, 70 | update_value=lambda cache, value: cache.update_value([ 71 | cache.value.get("start_hour"), 72 | cache.value.get("start_minute"), 73 | cache.value.get("end_hour"), 74 | cache.value.get("end_minute"), 75 | ]) if value 76 | else cache.close_value(), 77 | attribute="enabled", 78 | key="dnd_switch", 79 | name="DnD switch", 80 | translation_key="dnd_switch", 81 | icon="mdi:bell-cancel", 82 | entity_category=EntityCategory.CONFIG, 83 | ), 84 | RoborockSwitchDescription( 85 | cache_key=CacheableAttribute.valley_electricity_timer, 86 | update_value=lambda cache, value: cache.update_value([ 87 | cache.value.get("start_hour"), 88 | cache.value.get("start_minute"), 89 | cache.value.get("end_hour"), 90 | cache.value.get("end_minute"), 91 | ]) if value 92 | else cache.close_value(), 93 | attribute="enabled", 94 | key="valley_electricity_switch", 95 | name="Off-Peak charging switch", 96 | translation_key="valley_electricity_switch", 97 | icon="mdi:bell-cancel", 98 | entity_category=EntityCategory.CONFIG, 99 | ), 100 | ] 101 | 102 | 103 | async def async_setup_entry( 104 | hass: HomeAssistant, 105 | config_entry: ConfigEntry, 106 | async_add_entities: AddEntitiesCallback, 107 | ) -> None: 108 | """Set up Roborock switch platform.""" 109 | domain_data: EntryData = hass.data[DOMAIN][config_entry.entry_id] 110 | coordinators = [device_entry_data["coordinator"] for device_entry_data in domain_data.get("devices").values()] 111 | possible_entities: list[ 112 | tuple[RoborockDataUpdateCoordinator, RoborockSwitchDescription] 113 | ] = [ 114 | (coordinator, description) 115 | for coordinator in coordinators 116 | for description in SWITCH_DESCRIPTIONS 117 | ] 118 | # We need to check if this function is supported by the device. 119 | results = await asyncio.gather( 120 | *(coordinator.api.cache.get(description.cache_key).async_value() 121 | for coordinator, description in possible_entities), 122 | return_exceptions=True 123 | ) 124 | valid_entities: list[RoborockSwitch] = [] 125 | for (coordinator, description), result in zip(possible_entities, results): 126 | device_info = coordinator.data 127 | if result is None or isinstance(result, Exception): 128 | _LOGGER.debug("Not adding entity because of %s", result) 129 | else: 130 | valid_entities.append( 131 | RoborockSwitch( 132 | f"{description.key}_{slugify(coordinator.data.device.duid)}", 133 | device_info, 134 | description, 135 | coordinator.api, 136 | ) 137 | ) 138 | async_add_entities(valid_entities) 139 | 140 | 141 | class RoborockSwitch(RoborockEntity, SwitchEntity): 142 | """A class to let you turn functionality on Roborock devices on and off that does need a coordinator.""" 143 | 144 | entity_description: RoborockSwitchDescription 145 | 146 | def __init__( 147 | self, 148 | unique_id: str, 149 | device_info: RoborockHassDeviceInfo, 150 | description: RoborockSwitchDescription, 151 | api: RoborockClient, 152 | ) -> None: 153 | """Initialize the entity.""" 154 | SwitchEntity.__init__(self) 155 | RoborockEntity.__init__(self, device_info, unique_id, api) 156 | self.entity_description = description 157 | 158 | async def async_turn_off(self, **kwargs: Any) -> None: 159 | """Turn off the switch.""" 160 | await self.entity_description.update_value(self.api.cache.get(self.entity_description.cache_key), False) 161 | 162 | async def async_turn_on(self, **kwargs: Any) -> None: 163 | """Turn on the switch.""" 164 | await self.entity_description.update_value(self.api.cache.get(self.entity_description.cache_key), True) 165 | 166 | @property 167 | def is_on(self) -> bool | None: 168 | """Return True if entity is on.""" 169 | return self.api.cache.get(self.entity_description.cache_key).value.get(self.entity_description.attribute) == 1 170 | -------------------------------------------------------------------------------- /custom_components/roborock/time.py: -------------------------------------------------------------------------------- 1 | """Support for Roborock time.""" 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import datetime 6 | import logging 7 | from collections.abc import Callable, Coroutine 8 | from dataclasses import dataclass 9 | from typing import Any 10 | 11 | 12 | from roborock.version_1_apis import AttributeCache 13 | from roborock.version_1_apis import RoborockClientV1 as RoborockClient 14 | from roborock.command_cache import CacheableAttribute 15 | 16 | from homeassistant.components.time import TimeEntity, TimeEntityDescription 17 | from homeassistant.config_entries import ConfigEntry 18 | from homeassistant.const import EntityCategory 19 | from homeassistant.core import HomeAssistant 20 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 21 | from homeassistant.util import slugify 22 | from . import EntryData, RoborockDataUpdateCoordinator 23 | from .const import DOMAIN 24 | from .device import RoborockEntity 25 | from .roborock_typing import RoborockHassDeviceInfo 26 | 27 | _LOGGER = logging.getLogger(__name__) 28 | 29 | 30 | @dataclass 31 | class RoborockTimeDescriptionMixin: 32 | """Define an entity description mixin for time entities.""" 33 | 34 | # Gets the status of the switch 35 | cache_key: CacheableAttribute 36 | # Sets the status of the switch 37 | update_value: Callable[[AttributeCache, datetime.time], Coroutine[Any, Any, dict]] 38 | # Attribute from cache 39 | get_value: Callable[[AttributeCache], datetime.time] 40 | 41 | 42 | @dataclass 43 | class RoborockTimeDescription(TimeEntityDescription, RoborockTimeDescriptionMixin): 44 | """Class to describe an Roborock time entity.""" 45 | 46 | 47 | TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [ 48 | RoborockTimeDescription( 49 | key="dnd_start", 50 | name="DnD start", 51 | translation_key="dnd_start", 52 | icon="mdi:bell-cancel", 53 | cache_key=CacheableAttribute.dnd_timer, 54 | update_value=lambda cache, desired_time: cache.update_value( 55 | [ 56 | desired_time.hour, 57 | desired_time.minute, 58 | cache.value.get("end_hour"), 59 | cache.value.get("end_minute"), 60 | ] 61 | ), 62 | get_value=lambda cache: datetime.time( 63 | hour=cache.value.get("start_hour"), 64 | minute=cache.value.get("start_minute") 65 | ), 66 | entity_category=EntityCategory.CONFIG, 67 | ), 68 | RoborockTimeDescription( 69 | key="dnd_end", 70 | name="DnD end", 71 | translation_key="dnd_end", 72 | icon="mdi:bell-ring", 73 | cache_key=CacheableAttribute.dnd_timer, 74 | update_value=lambda cache, desired_time: cache.update_value( 75 | [ 76 | cache.value.get("start_hour"), 77 | cache.value.get("start_minute"), 78 | desired_time.hour, 79 | desired_time.minute, 80 | ] 81 | ), 82 | get_value=lambda cache: datetime.time( 83 | hour=cache.value.get("end_hour"), 84 | minute=cache.value.get("end_minute") 85 | ), 86 | entity_category=EntityCategory.CONFIG, 87 | ), 88 | RoborockTimeDescription( 89 | key="valley_electricity_start", 90 | name="Off-Peak charging start", 91 | translation_key="valley_electricity_start", 92 | icon="mdi:bell-ring", 93 | cache_key=CacheableAttribute.valley_electricity_timer, 94 | update_value=lambda cache, desired_time: cache.update_value( 95 | [ 96 | desired_time.hour, 97 | desired_time.minute, 98 | cache.value.get("end_hour"), 99 | cache.value.get("end_minute"), 100 | ] 101 | ), 102 | get_value=lambda cache: datetime.time( 103 | hour=cache.value.get("start_hour"), 104 | minute=cache.value.get("start_minute") 105 | ), 106 | entity_category=EntityCategory.CONFIG, 107 | ), 108 | RoborockTimeDescription( 109 | key="valley_electricity_end", 110 | name="Off-Peak charging end", 111 | translation_key="valley_electricity_end", 112 | icon="mdi:bell-ring", 113 | cache_key=CacheableAttribute.valley_electricity_timer, 114 | update_value=lambda cache, desired_time: cache.update_value( 115 | [ 116 | cache.value.get("start_hour"), 117 | cache.value.get("start_minute"), 118 | desired_time.hour, 119 | desired_time.minute, 120 | ] 121 | ), 122 | get_value=lambda cache: datetime.time( 123 | hour=cache.value.get("end_hour"), 124 | minute=cache.value.get("end_minute") 125 | ), 126 | entity_category=EntityCategory.CONFIG, 127 | ), 128 | ] 129 | 130 | 131 | async def async_setup_entry( 132 | hass: HomeAssistant, 133 | config_entry: ConfigEntry, 134 | async_add_entities: AddEntitiesCallback, 135 | ) -> None: 136 | """Only vacuums with mop should have binary sensor registered.""" 137 | domain_data: EntryData = hass.data[DOMAIN][config_entry.entry_id] 138 | coordinators = [device_entry_data["coordinator"] for device_entry_data in domain_data.get("devices").values()] 139 | 140 | possible_entities: list[ 141 | tuple[RoborockDataUpdateCoordinator, RoborockTimeDescription] 142 | ] = [ 143 | (coordinator, description) 144 | for coordinator in coordinators 145 | for description in TIME_DESCRIPTIONS 146 | ] 147 | # We need to check if this function is supported by the device. 148 | results = await asyncio.gather( 149 | *(coordinator.api.cache.get(description.cache_key).async_value() 150 | for coordinator, description in possible_entities), 151 | return_exceptions=True 152 | ) 153 | valid_entities: list[RoborockTime] = [] 154 | for (coordinator, description), result in zip(possible_entities, results): 155 | device_info = coordinator.data 156 | if result is None or isinstance(result, Exception): 157 | _LOGGER.debug("Not adding entity because of %s", result) 158 | else: 159 | valid_entities.append( 160 | RoborockTime( 161 | f"{description.key}_{slugify(coordinator.data.device.duid)}", 162 | device_info, 163 | description, 164 | coordinator.api, 165 | ) 166 | ) 167 | async_add_entities(valid_entities) 168 | 169 | 170 | class RoborockTime(RoborockEntity, TimeEntity): 171 | """A class to let you set options on a Roborock vacuum where the potential options are fixed.""" 172 | 173 | entity_description: RoborockTimeDescription 174 | 175 | def __init__( 176 | self, 177 | unique_id: str, 178 | device_info: RoborockHassDeviceInfo, 179 | description: RoborockTimeDescription, 180 | api: RoborockClient, 181 | ) -> None: 182 | """Initialize the entity.""" 183 | TimeEntity.__init__(self) 184 | RoborockEntity.__init__(self, device_info, unique_id, api) 185 | self.entity_description = description 186 | 187 | @property 188 | def native_value(self) -> datetime.time | None: 189 | """Return the value reported by the time.""" 190 | return self.entity_description.get_value(self.api.cache.get(self.entity_description.cache_key)) 191 | 192 | async def async_set_value(self, value: datetime.time) -> None: 193 | """Set the time.""" 194 | await self.entity_description.update_value(self.api.cache.get(self.entity_description.cache_key), value) 195 | -------------------------------------------------------------------------------- /custom_components/roborock/translations/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Vyberte metodu ověření", 6 | "menu_options": { 7 | "code": "Přihlášení pomocí ověřovacího kódu", 8 | "password": "Přihlášení pomocí hesla" 9 | } 10 | }, 11 | "email": { 12 | "description": "Vaše údaje Roborock aplikace", 13 | "data": { 14 | "username": "Email" 15 | } 16 | }, 17 | "code": { 18 | "description": "Zadejte ověřovací kód zaslaný na váš email", 19 | "data": { 20 | "code": "Ověřovací kód" 21 | } 22 | }, 23 | "password": { 24 | "description": "Zadejte vaše heslo", 25 | "data": { 26 | "password": "Heslo" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Ve vašem Roborock účtu nebylo nalezeno žádné zařízení", 32 | "auth": "Přihlášení selhalo. Zkontrolujte své přihlašovací údaje" 33 | }, 34 | "abort": { 35 | "already_configured": "Účet je již nastaven", 36 | "already_in_progress": "Účet se již nastavuje" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Vyberte platformu, kterou chcete nakonfigurovat", 43 | "menu_options": { 44 | "vacuum": "Vysavač", 45 | "camera": "Kamera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Možnosti kamery", 51 | "data": { 52 | "map_transformation:scale": "Měřítko mapy", 53 | "map_transformation:rotate": "Rotace mapy", 54 | "map_transformation:trim:left": "Levé oříznutí mapy", 55 | "map_transformation:trim:right": "Pravé oříznutí mapy", 56 | "map_transformation:trim:top": "Horní oříznutí mapy", 57 | "map_transformation:trim:bottom": "Spodní oříznutí mapy", 58 | "include_ignored_obstacles": "Zobrazit ignorované překážky", 59 | "include_nogo": "Zobrazit no-go zóny" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Možnosti vysavače", 64 | "data": { 65 | "include_shared": "Zahrnout sdílená zařízení" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Obecná nastavení", 70 | "data": { 71 | "cloud_integration": "Použít cloudovou integraci" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Resetovat údržbu senzorů" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Resetovat údržbu filtru" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Resetovat údržbu bočního kartáče" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Resetovat údržbu hlavního kartáče" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Chyba vysavače", 94 | "state": { 95 | "none": "Žádná", 96 | "lidar_blocked": "LiDAR nebo laser jsou zablokovány", 97 | "bumper_stuck": "Zaseknutý nárazník", 98 | "wheels_suspended": "Chyba kol", 99 | "cliff_sensor_error": "Chyba senzoru pádu", 100 | "main_brush_jammed": "Zaseknutý hlavní kartáč", 101 | "side_brush_jammed": "Zaseknutý boční kartáč", 102 | "wheels_jammed": "Zaseknutá kola", 103 | "robot_trapped": "Robot je v pasti", 104 | "no_dustbin": "Není připojen zásobník prachu", 105 | "low_battery": "Nízká baterie", 106 | "charging_error": "Chyba nabíjení", 107 | "battery_error": "Chyba baterie", 108 | "wall_sensor_dirty": "Senzor zdí je znečištěný", 109 | "robot_tilted": "Robot je nakloněný", 110 | "side_brush_error": "Chyba bočního kartáče", 111 | "fan_error": "Chyba ventilátoru", 112 | "vertical_bumper_pressed": "Stlačen vertikální nárazník", 113 | "dock_locator_error": "Chyba lokátoru doku", 114 | "return_to_dock_fail": "Nelze se vrátit do doku", 115 | "nogo_zone_detected": "No-go zóna detekována", 116 | "vibrarise_jammed": "Zseknutý VibraRise systém", 117 | "robot_on_carpet": "Robot na koberci", 118 | "filter_blocked": "Filtr je ucpaný nebo mokrý", 119 | "invisible_wall_detected": "Neviditelná zeď detekována", 120 | "cannot_cross_carpet": "Nelze přejet přes koberec", 121 | "internal_error": "Interní chyba" 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Začátek úklidu" 126 | }, 127 | "last_clean_end": { 128 | "name": "Konec úklidu" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Doba úklidu" 132 | }, 133 | "last_clean_area": { 134 | "name": "Plocha úklidu" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Aktuální doba úklidu" 138 | }, 139 | "current_clean_area": { 140 | "name": "Aktuální plocha úklidu" 141 | }, 142 | "total_duration": { 143 | "name": "Celková doba úklidu" 144 | }, 145 | "total_clean_area": { 146 | "name": "Celková uklizená plocha" 147 | }, 148 | "total_clean_count": { 149 | "name": "Celkový počet úklidů" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Celkový počet sběrů prachu" 153 | }, 154 | "main_brush_left": { 155 | "name": "Zbývající čas hlavního kartáče" 156 | }, 157 | "side_brush_left": { 158 | "name": "Zbývající čas bočního kartáče" 159 | }, 160 | "filter_left": { 161 | "name": "Zbývající čas filtru" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Zbývající čas senzorů" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dok režim mytí" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dok řežim sběru prachu" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dok interval mytí mopu" 174 | }, 175 | "current_map_selected": { 176 | "name": "Aktuální mapa" 177 | }, 178 | "current_room": { 179 | "name": "Aktuální místnost" 180 | }, 181 | "dock_status": { 182 | "name": "Stav doku", 183 | "state": { 184 | "ok": "V pořádku", 185 | "water_empty": "Zásobník čisté vody je prázdný", 186 | "waste_water_tank_full": "Zásobník špinavé vody je plný/chybí", 187 | "dirty_tank_latch_open": "Zámek nádržky na špinavou vodu je odblokován", 188 | "no_dustbin": "Zásobník prachu není připojen", 189 | "duct_blockage": "Ucpaná sací hadice", 190 | "cleaning_tank_full_or_blocked": "Nádoba v doku je plná, sací hadice zablokovaná nebo je odblokován zámek nádržky na špinavou vodu" 191 | } 192 | }, 193 | "mop_drying_status": { 194 | "name": "Sušení mopu" 195 | }, 196 | "mop_drying_remaining_time": { 197 | "name": "Zbývající čas sušení mopu" 198 | }, 199 | "clean_percent": { 200 | "name": "Průběh úklidu" 201 | }, 202 | "battery": { 203 | "name": "Baterie" 204 | } 205 | }, 206 | "binary_sensor": { 207 | "mop_attached": { 208 | "name": "Mop připojen" 209 | }, 210 | "water_box_attached": { 211 | "name": "Zásobník na vodu připojen" 212 | }, 213 | "water_shortage": { 214 | "name": "Nedostatek vody" 215 | } 216 | }, 217 | "select": { 218 | "mop_mode": { 219 | "name": "Režim mopování", 220 | "state": { 221 | "fast": "Rychlý", 222 | "standard": "Standardní", 223 | "deep": "Deep", 224 | "deep_plus": "Deep+", 225 | "custom": "Vlastní" 226 | } 227 | }, 228 | "mop_intensity": { 229 | "name": "Intenzita mopování", 230 | "state": { 231 | "off": "Vypnuto", 232 | "low": "Mírná", 233 | "mild": "Mírná", 234 | "moderate": "Střední", 235 | "medium": "Střední", 236 | "intense": "Vysoká", 237 | "high": "Vysoká", 238 | "custom": "Vlastní", 239 | "custom_water_flow": "Vlastní" 240 | } 241 | } 242 | }, 243 | "number": { 244 | "sound_volume": { 245 | "name": "Hlasitost" 246 | } 247 | }, 248 | "vacuum": { 249 | "roborock": { 250 | "state_attributes": { 251 | "fan_speed": { 252 | "state": { 253 | "auto": "Auto", 254 | "balanced": "Vyvážený", 255 | "custom": "Vlastní", 256 | "gentle": "Jemný", 257 | "off": "Vypnuto", 258 | "max": "Max", 259 | "max_plus": "Max+", 260 | "medium": "Střední", 261 | "quiet": "Tichý", 262 | "silent": "Tichý", 263 | "standard": "Standard", 264 | "turbo": "Turbo" 265 | } 266 | }, 267 | "status": { 268 | "state": { 269 | "starting": "Spouštění", 270 | "charger_disconnected": "Nabíjení odpojeno", 271 | "idle": "Nečinný", 272 | "remote_control_active": "Ovládán dálkově", 273 | "cleaning": "Uklízení", 274 | "returning_home": "Návrat do doku", 275 | "manual_mode": "Manuální režim", 276 | "charging": "Nabíjení", 277 | "charging_problem": "Problém s nabíjením", 278 | "paused": "Pozastaveno", 279 | "spot_cleaning": "Bodové čištění", 280 | "error": "Chyba", 281 | "shutting_down": "Vypínání", 282 | "updating": "Aktualizování", 283 | "docking": "Dokování", 284 | "going_to_target": "Jede k cíli", 285 | "zoned_cleaning": "Zónový úklid", 286 | "segment_cleaning": "Segmentový úklid", 287 | "emptying_the_bin": "Vysypávání koše", 288 | "washing_the_mop": "Mytí mopu", 289 | "going_to_wash_the_mop": "Jede umýt mop", 290 | "charging_complete": "Nabíjení dokončeno", 291 | "device_offline": "Zařízení offline" 292 | } 293 | } 294 | } 295 | } 296 | }, 297 | "time": { 298 | "dnd_start": { 299 | "name": "Začátek režimu Nerušit" 300 | }, 301 | "dnd_end": { 302 | "name": "Konec režimu Nerušit" 303 | }, 304 | "valley_electricity_start": { 305 | "name": "Začátek režimu Nabíjení mimo špičku" 306 | }, 307 | "valley_electricity_end": { 308 | "name": "Konec režimu Nabíjení mimo špičku" 309 | } 310 | }, 311 | "switch": { 312 | "child_lock": { 313 | "name": "Dětský zámek" 314 | }, 315 | "flow_led_status": { 316 | "name": "Kontrolka stavu" 317 | }, 318 | "dnd_switch": { 319 | "name": "Režim Nerušit" 320 | }, 321 | "valley_electricity_switch": { 322 | "name": "Režim Nabíjení mimo špičku" 323 | } 324 | } 325 | } 326 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Vælg en godkendelsesmetode", 6 | "menu_options": { 7 | "code": "Login men godkendelseskode fra email", 8 | "password": "Login med password" 9 | } 10 | }, 11 | "email": { 12 | "description": "Login info til Roborock app", 13 | "data": { 14 | "username": "Brugernavn" 15 | } 16 | }, 17 | "code": { 18 | "description": "Indtast verifikationskoden sent til din email", 19 | "data": { 20 | "code": "Verifikationskode" 21 | } 22 | }, 23 | "password": { 24 | "description": "Indtast password", 25 | "data": { 26 | "password": "Password" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Der blev ikke fundet nogen enhed på din Roborock konto", 32 | "auth": "Login fejl. Kontroller login informationer" 33 | }, 34 | "abort": { 35 | "already_configured": "Enhed er allerede konfigureret", 36 | "already_in_progress": "Konfiguration af enhed er allerede igangsat" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Vælg platform der skal konfigures", 43 | "menu_options": { 44 | "vacuum": "Støvsuger", 45 | "camera": "Kamera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Kamera Indstillinger", 51 | "data": { 52 | "map_transformation:scale": "Kort skala", 53 | "map_transformation:rotate": "Kort rotation", 54 | "map_transformation:trim:left": "Kort venstre trim", 55 | "map_transformation:trim:right": "Kort højre trim", 56 | "map_transformation:trim:top": "Kort top trim", 57 | "map_transformation:trim:bottom": "Kort bund trim", 58 | "include_ignored_obstacles": "Vis ignorerede forhindringer", 59 | "include_nogo": "Vis no-go zoner" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Støvsuger Indstillinger", 64 | "data": { 65 | "include_shared": "Inkluder delte enheder" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Grundlæggende indstillinger", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Nulstil sensor forbrug" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Nulstil filter forbrug" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Nulstil sidebørste forbrug" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Nulstil hovedebørste forbrug" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Nuværende fejl", 94 | "state": { 95 | "none": "Ingen", 96 | "lidar_blocked": "LiDAR tårn eller laser blokeret. Kontroller for fremmedelementer og prøv igen.", 97 | "bumper_stuck": "Kofanger sidder fast. Rens den og tryk forsigtig på den for at frigør.", 98 | "wheels_suspended": "Hjul ophængt. Flyt robotten og genstart.", 99 | "cliff_sensor_error": "Faldsensor fejl. Rens faldsensorer, flyt robot væk fra fald fare og genstart.", 100 | "main_brush_jammed": "Hovedbørsten sidder fast. Rens hovedbørste og lejer.", 101 | "side_brush_jammed": "Sidebørsten sidder fast. Fjern og rens sidebørste.", 102 | "wheels_jammed": "Hjul sidder fast. Flyt robot og genstart.", 103 | "robot_trapped": "Robot fanget. Fjern forhindringer og genstart.", 104 | "no_dustbin": "Ingen støvbeholder. Indsæt støvbeholder og filter.", 105 | "low_battery": "Lavt batteri. Oplad og genstart.", 106 | "charging_error": "Lade fejl. Rens lade kontakter og på igen.", 107 | "battery_error": "Batteri fejl.", 108 | "wall_sensor_dirty": "Vægsensor beskidt. Rens vægsensor.", 109 | "robot_tilted": "Robot vippet. Flyt til jævnt underlag og genstart.", 110 | "side_brush_error": "Sidebørste fejl. Genstart robot.", 111 | "fan_error": "Blæser fejl. Genstart robot.", 112 | "vertical_bumper_pressed": "Lodret kofanger påvirket. Flyt robotten og genstart.", 113 | "dock_locator_error": "Dock lokations fejl. Rens dock og genstart.", 114 | "return_to_dock_fail": "Kunne ikke returnere til dock. Rens dock lokations beacon og genstart.", 115 | "nogo_zone_detected": "No-go zone eller usynligvæg detekteret. Flyt robotten væk fra dette område.", 116 | "vibrarise_jammed": "VibraRise system fastklemte. Kontroller for forhindringer.", 117 | "robot_on_carpet": "Robot på tæppe. Flyt robot til gulv og genstart.", 118 | "filter_blocked": "Filter blokeret eller vådt. Rens, tør og restart.", 119 | "invisible_wall_detected": "No-go zone eller usynligvæg detekteret. Flyt robotten væk fra dette område.", 120 | "cannot_cross_carpet": "Kan ikke køre på tæppe. Flyt robot over tæppet og genstart.", 121 | "internal_error": "Intern Fejl. Reset robotten." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Sidste rengøringsstart" 126 | }, 127 | "last_clean_end": { 128 | "name": "Sidste rengøringsafslutning" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Sidste rengøringsvarighed" 132 | }, 133 | "last_clean_area": { 134 | "name": "Sidste rengøringsområde" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Nuværende rengøringsvarighed" 138 | }, 139 | "current_clean_area": { 140 | "name": "Nuværende rengøringsområde" 141 | }, 142 | "total_duration": { 143 | "name": "Total varighed" 144 | }, 145 | "total_clean_area": { 146 | "name": "Totalt rengjort område" 147 | }, 148 | "total_clean_count": { 149 | "name": "Totalt antal rengøringer" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Total støve tømningsantal" 153 | }, 154 | "main_brush_left": { 155 | "name": "Hovedebørste tilbage" 156 | }, 157 | "side_brush_left": { 158 | "name": "Sidebørste tilbage" 159 | }, 160 | "filter_left": { 161 | "name": "Filter tilbage" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Sensor støv tilbage" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dock vaske indstilling" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dock støv tømnings indstilling" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dock moppe vaske interval indstilling" 174 | }, 175 | "current_map_selected": { 176 | "name": "Valgt kort" 177 | }, 178 | "current_room": { 179 | "name": "Nuværende rum" 180 | }, 181 | "dock_status": { 182 | "name": "Dock status" 183 | }, 184 | "battery": { 185 | "name": "Batteri" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Moppe monteret" 191 | }, 192 | "water_box_attached": { 193 | "name": "Vandbeholder monteret" 194 | }, 195 | "water_shortage": { 196 | "name": "Lav vandstand" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Mop mode", 202 | "state": { 203 | "standard": "Standard", 204 | "deep": "Dyb", 205 | "deep_plus": "Dyb+", 206 | "custom": "Brugerdefinerede" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Mop intensity", 211 | "state": { 212 | "off": "Off", 213 | "mild": "Mild", 214 | "moderate": "Moderat", 215 | "intense": "Intens", 216 | "custom": "Brugerdefinerede", 217 | "custom_water_flow": "Brugerdefinerede" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Automatisk", 227 | "balanced": "Balanceret", 228 | "custom": "Brugerdefinerede", 229 | "gentle": "Blidt", 230 | "off": "Off", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Stille", 235 | "silent": "Lydløs", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Starter", 243 | "charger_disconnected": "Oplader afbrudt", 244 | "idle": "Idle", 245 | "remote_control_active": "Fjernbetjening aktiveret", 246 | "cleaning": "Rengører", 247 | "returning_home": "Kører hjem", 248 | "manual_mode": "Manuel tilstand", 249 | "charging": "Oplader", 250 | "charging_problem": "Problem med opladning", 251 | "paused": "Pause", 252 | "spot_cleaning": "Punkt rengøring", 253 | "error": "Fejl", 254 | "shutting_down": "Slukker", 255 | "updating": "Updater", 256 | "docking": "Kører i dock", 257 | "going_to_target": "Kører til punkt", 258 | "zoned_cleaning": "Zone rengøring", 259 | "segment_cleaning": "Segment reggøring", 260 | "emptying_the_bin": "Tømmer støvebeholder", 261 | "washing_the_mop": "Renser moppe", 262 | "going_to_wash_the_mop": "Kører til rensning af moppe", 263 | "charging_complete": "Opladning afsluttet", 264 | "device_offline": "Enhed er offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "DnD start" 273 | }, 274 | "dnd_end": { 275 | "name": "DnD slut" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Off-Peak charging start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Off-Peak charging end" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Child lock" 287 | }, 288 | "flow_led_status": { 289 | "name": "Status Indicator Light" 290 | }, 291 | "dnd_switch": { 292 | "name": "DnD switch" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Valley electricity switch" 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /custom_components/roborock/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Select an authentication method", 6 | "menu_options": { 7 | "code": "Login with email authentication code", 8 | "password": "Login with password" 9 | } 10 | }, 11 | "email": { 12 | "description": "Your app credentials", 13 | "data": { 14 | "username": "Email" 15 | } 16 | }, 17 | "code": { 18 | "description": "Type the verification code sent to your email", 19 | "data": { 20 | "code": "Verification code" 21 | } 22 | }, 23 | "password": { 24 | "description": "Type your password", 25 | "data": { 26 | "password": "Password" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "No device found within your Roborock account", 32 | "auth": "Failed to login. Check your credentials" 33 | }, 34 | "abort": { 35 | "already_configured": "Account is already configured", 36 | "already_in_progress": "Account is already being configured" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Select a platform to configure", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Camera Options", 51 | "data": { 52 | "map_transformation:scale": "Map scale", 53 | "map_transformation:rotate": "Map rotation", 54 | "map_transformation:trim:left": "Map left trim", 55 | "map_transformation:trim:right": "Map right trim", 56 | "map_transformation:trim:top": "Map top trim", 57 | "map_transformation:trim:bottom": "Map bottom trim", 58 | "include_ignored_obstacles": "Show ignored obstacles", 59 | "include_nogo": "Show no-go zones" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Vacuum options", 64 | "data": { 65 | "include_shared": "Include shared devices" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "General settings", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reset sensor consumable" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reset filter consumable" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reset side brush consumable" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reset main brush consumable" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Current error", 94 | "state": { 95 | "none": "None", 96 | "lidar_blocked": "LiDAR turret or laser blocked. Check for obstruction and retry.", 97 | "bumper_stuck": "Bumper stuck. Clean it and lightly tap to release it.", 98 | "wheels_suspended": "Wheels suspended. Move robot and restart.", 99 | "cliff_sensor_error": "Cliff sensor error. Clean cliff sensors, move robot away from drops and restart.", 100 | "main_brush_jammed": "Main brush jammed. Clean main brush and bearings.", 101 | "side_brush_jammed": "Side brush jammed. Remove and clean side brush.", 102 | "wheels_jammed": "Wheels jammed. Move the robot and restart.", 103 | "robot_trapped": "Robot trapped. Clear obstacles surrounding robot.", 104 | "no_dustbin": "No dustbin. Install dustbin and filter.", 105 | "low_battery": "Low battery. Recharge and retry.", 106 | "charging_error": "Charging error. Clean charging contacts and retry.", 107 | "battery_error": "Battery error.", 108 | "wall_sensor_dirty": "Wall sensor dirty. Clean wall sensor.", 109 | "robot_tilted": "Robot tilted. Move to level ground and restart.", 110 | "side_brush_error": "Side brush error. Reset robot.", 111 | "fan_error": "Fan error. Reset robot.", 112 | "vertical_bumper_pressed": "Vertical bumper pressed. Move robot and retry.", 113 | "dock_locator_error": "Dock locator error. Clean and retry.", 114 | "return_to_dock_fail": "Could not return to dock. Clean dock location beacon and retry.", 115 | "nogo_zone_detected": "No-go zone or invisible wall detected. Move the robot.", 116 | "vibrarise_jammed": "VibraRise system jammed. Check for obstructions.", 117 | "robot_on_carpet": "Robot on carpet. Move robot to floor and retry.", 118 | "filter_blocked": "Filter blocked or wet. Clean, dry, and retry.", 119 | "invisible_wall_detected": "No-go zone or Invisible Wall detected. Move robot from this area.", 120 | "cannot_cross_carpet": "Cannot cross carpet. Move robot across carpet and restart.", 121 | "internal_error": "Internal error. Reset the robot." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Last clean start" 126 | }, 127 | "last_clean_end": { 128 | "name": "Last clean end" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Last clean duration" 132 | }, 133 | "last_clean_area": { 134 | "name": "Last clean area" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Current clean duration" 138 | }, 139 | "current_clean_area": { 140 | "name": "Current clean area" 141 | }, 142 | "total_duration": { 143 | "name": "Total duration" 144 | }, 145 | "total_clean_area": { 146 | "name": "Total clean area" 147 | }, 148 | "total_clean_count": { 149 | "name": "Total clean count" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Total dust collection count" 153 | }, 154 | "main_brush_left": { 155 | "name": "Main brush left" 156 | }, 157 | "side_brush_left": { 158 | "name": "Side brush left" 159 | }, 160 | "filter_left": { 161 | "name": "Filter left" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Sensor dirty left" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dock washing mode" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dock dust collection mode" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dock mop wash mode interval" 174 | }, 175 | "current_map_selected": { 176 | "name": "Current map selected" 177 | }, 178 | "current_room": { 179 | "name": "Current room" 180 | }, 181 | "dock_status": { 182 | "name": "Dock status" 183 | }, 184 | "mop_drying_status": { 185 | "name": "Mop drying" 186 | }, 187 | "mop_drying_remaining_time": { 188 | "name": "Mop drying remaining time" 189 | }, 190 | "clean_percent": { 191 | "name": "Cleaning progress" 192 | }, 193 | "battery": { 194 | "name": "Battery" 195 | } 196 | }, 197 | "binary_sensor": { 198 | "mop_attached": { 199 | "name": "Mop attached" 200 | }, 201 | "water_box_attached": { 202 | "name": "Water box attached" 203 | }, 204 | "water_shortage": { 205 | "name": "Water shortage" 206 | } 207 | }, 208 | "select": { 209 | "mop_mode": { 210 | "name": "Route", 211 | "state": { 212 | "fast": "Fast", 213 | "standard": "Standard", 214 | "deep": "Deep", 215 | "deep_plus": "Deep+", 216 | "custom": "Custom" 217 | } 218 | }, 219 | "mop_intensity": { 220 | "name": "Mop intensity", 221 | "state": { 222 | "off": "Off", 223 | "low": "Low", 224 | "mild": "Mild", 225 | "moderate": "Moderate", 226 | "medium": "Medium", 227 | "intense": "Intense", 228 | "high": "High", 229 | "custom": "Custom", 230 | "custom_water_flow": "Custom" 231 | } 232 | } 233 | }, 234 | "vacuum": { 235 | "roborock": { 236 | "state_attributes": { 237 | "fan_speed": { 238 | "state": { 239 | "auto": "Auto", 240 | "balanced": "Balanced", 241 | "custom": "Custom", 242 | "gentle": "Gentle", 243 | "off": "Off", 244 | "max": "Max", 245 | "max_plus": "Max+", 246 | "medium": "Medium", 247 | "quiet": "Quiet", 248 | "silent": "Silent", 249 | "standard": "Standard", 250 | "turbo": "Turbo" 251 | } 252 | }, 253 | "status": { 254 | "state": { 255 | "starting": "Starting", 256 | "charger_disconnected": "Charger disconnected", 257 | "idle": "Idle", 258 | "remote_control_active": "Remote control active", 259 | "cleaning": "Cleaning", 260 | "returning_home": "Returning home", 261 | "manual_mode": "Manual mode", 262 | "charging": "Charging", 263 | "charging_problem": "Charging problem", 264 | "paused": "Paused", 265 | "spot_cleaning": "Spot cleaning", 266 | "error": "Error", 267 | "shutting_down": "Shutting down", 268 | "updating": "Updating", 269 | "docking": "Docking", 270 | "going_to_target": "Going to target", 271 | "zoned_cleaning": "Zoned cleaning", 272 | "segment_cleaning": "Segment cleaning", 273 | "emptying_the_bin": "Emptying the bin", 274 | "washing_the_mop": "Washing the mop", 275 | "going_to_wash_the_mop": "Going to wash the mop", 276 | "charging_complete": "Charging complete", 277 | "device_offline": "Device offline" 278 | } 279 | } 280 | } 281 | } 282 | }, 283 | "time": { 284 | "dnd_start": { 285 | "name": "DnD start" 286 | }, 287 | "dnd_end": { 288 | "name": "DnD end" 289 | }, 290 | "valley_electricity_start": { 291 | "name": "Off-Peak charging start" 292 | }, 293 | "valley_electricity_end": { 294 | "name": "Off-Peak charging end" 295 | } 296 | }, 297 | "switch": { 298 | "child_lock": { 299 | "name": "Child lock" 300 | }, 301 | "flow_led_status": { 302 | "name": "Status Indicator Light" 303 | }, 304 | "dnd_switch": { 305 | "name": "DnD switch" 306 | }, 307 | "valley_electricity_switch": { 308 | "name": "Valley electricity switch" 309 | } 310 | } 311 | } 312 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "בחירת שיטת אימות", 6 | "menu_options": { 7 | "code": "התחברות עם קוד אימות דואר אלקטרוני", 8 | "password": "התחברות עם סיסמה" 9 | } 10 | }, 11 | "email": { 12 | "description": "אישורי היישום שלך", 13 | "data": { 14 | "username": "דואר אלקטרוני" 15 | } 16 | }, 17 | "code": { 18 | "description": "נא להקליד את קוד האימות שנשלח לדואר האלקטרוני שלך", 19 | "data": { 20 | "code": "קוד אימות" 21 | } 22 | }, 23 | "password": { 24 | "description": "הקלדת הסיסמה שלך", 25 | "data": { 26 | "password": "סיסמה" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "לא נמצא מכשיר בחשבון Roborock שלך", 32 | "auth": "הכניסה נכשלה. נא לבדוק את האישורים שלך" 33 | }, 34 | "abort": { 35 | "already_configured": "החשבון כבר מוגדר", 36 | "already_in_progress": "החשבון כבר בתהליך הגדרה" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "בחירת פלטפורמה להגדרה", 43 | "menu_options": { 44 | "vacuum": "שואב", 45 | "camera": "מצלמה", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "אפשרויות מצלמה", 51 | "data": { 52 | "map_transformation:scale": "קנה מידה מפה", 53 | "map_transformation:rotate": "סיבוב מפה", 54 | "map_transformation:trim:left": "מפה חיתוך שמאל", 55 | "map_transformation:trim:right": "מפה חיתוך ימין", 56 | "map_transformation:trim:top": "מפה חיתוך עליון", 57 | "map_transformation:trim:bottom": "מפה חיתוך תחתון", 58 | "include_ignored_obstacles": "הצגת מכשולים שהתעלמו מהם", 59 | "include_nogo": "הצגת אזורים ללא מעבר" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "אפשרויות שואב", 64 | "data": { 65 | "include_shared": "כלול התקנים משותפים" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "הגדרות כלליות", 70 | "data": { 71 | "cloud_integration": "שימוש בשילוב ענן" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "איפוס שימוש בחיישן" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "איפוס שימוש במסנן" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "איפוס שימוש במברשת צד" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "איפוס שימוש במברשת ראשית" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "שגיאה נוכחית", 94 | "state": { 95 | "none": "ללא", 96 | "lidar_blocked": "צריח LiDAR או לייזר חסומים. הא לבדוק אם יש חסימה ולנסות שוב.", 97 | "bumper_stuck": "הפגוש תקוע. יש לנקות אותו ולהקיש קלות כדי לשחרר אותו.", 98 | "wheels_suspended": "גלגלים מושעים. יש להזיז את הרובוט ולהפעיל מחדש.", 99 | "cliff_sensor_error": "שגיאת חיישן גובה. יש לנקות את חיישני הגובה, ולהחריק את הרובוט מנפילות ולהפעיל מחדש.", 100 | "main_brush_jammed": "המברשת הראשית נתקעה. יש לנקות את המברשת הראשית ואת המיסבים.", 101 | "side_brush_jammed": "מברשת צד נתקעה. יש להסיר ולנקות את מברשת צד.", 102 | "wheels_jammed": "גלגלים תקועים. יש להזיז את הרובוט ולהפעיל מחדש.", 103 | "robot_trapped": "רובוט לכוד. יש לנקות מכשולים סביב הרובוט.", 104 | "no_dustbin": "ללא פח אשפה. יש להתקין פח אשפה ומסנן.", 105 | "low_battery": "סוללה חלשה. יש לטעון מחדש ולנסות שוב.", 106 | "charging_error": "שגיאת טעינה. יש לנקות את מגעי הטעינה ולנסות שוב.", 107 | "battery_error": "שגיאת סוללה.", 108 | "wall_sensor_dirty": "חיישן קיר מלוכלך. יש לנקות את חיישן הקיר.", 109 | "robot_tilted": "הרובוט הוטה. יש להעמיד על הקרקע ולהפעיל מחדש.", 110 | "side_brush_error": "שגיאת מברשת צד. יש לאפס את הרובוט.", 111 | "fan_error": "שגיאת מאוורר. יש לאפס את הרובוט.", 112 | "vertical_bumper_pressed": "פגוש אנכי לחוץ. יש להזיז את הרובוט ולנסות שוב.", 113 | "dock_locator_error": "שגיאת איתור עגינה. יש לנקות ולנסות שוב.", 114 | "return_to_dock_fail": "לא ניתן לחזור למעגן. יש לנקות את משואה של מיקום המעגן ולנסות שוב.", 115 | "nogo_zone_detected": "זוהה אזור אסור למעבר או קיר בלתי נראה. נא להזיז את הרובוט.", 116 | "vibrarise_jammed": "מערכת VibraRise נתקעה. יש לבדוק אם יש חסימות.", 117 | "robot_on_carpet": "רובוט על השטיח. יש להעביר את הרובוט לרצפה ולנסות שוב.", 118 | "filter_blocked": "המסנן חסום או רטוב. יש לנקות, לייבש, ולנסות שוב.", 119 | "invisible_wall_detected": "זוהה אזור אסור או קיר בלתי נראה. יש להזיז את הרובוט מאזור זה.", 120 | "cannot_cross_carpet": "לא ניתן לחצות שטיח. יש להעביר את הרובוט על פני השטיח ולהפעיל מחדש.", 121 | "internal_error": "שגיאה פנימית. יש לאפס את הרובוט." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "התחלת ניקוי אחרון" 126 | }, 127 | "last_clean_end": { 128 | "name": "סיום ניקוי אחרון" 129 | }, 130 | "last_clean_duration": { 131 | "name": "משך הניקוי האחרון" 132 | }, 133 | "last_clean_area": { 134 | "name": "ניקוי אזור אחרון" 135 | }, 136 | "current_clean_duration": { 137 | "name": "משך הניקוי הנוכחי" 138 | }, 139 | "current_clean_area": { 140 | "name": "איזור ניקוי נוכחי" 141 | }, 142 | "total_duration": { 143 | "name": "משך כולל" 144 | }, 145 | "total_clean_area": { 146 | "name": "אזור ניקוי כולל" 147 | }, 148 | "total_clean_count": { 149 | "name": "סך ספירת ניקיונות" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "סך ספירת איסוף אבק" 153 | }, 154 | "main_brush_left": { 155 | "name": "נותר למברשת ראשית" 156 | }, 157 | "side_brush_left": { 158 | "name": "נותר למברשת צד" 159 | }, 160 | "filter_left": { 161 | "name": "נותר למסנן" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "נותר לחיישן לכלוך" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "מצב שטיפת מעגן" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "מצב איסוף אבק במעגן" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "מצב מחזוריות שטיפת מטלית במעגן" 174 | }, 175 | "current_map_selected": { 176 | "name": "בחירת מפה נוכחית" 177 | }, 178 | "current_room": { 179 | "name": "חדר נוכחי" 180 | }, 181 | "dock_status": { 182 | "name": "סטטוס עגינה", 183 | "state": { 184 | "ok": "תקין", 185 | "water_empty": "מיכל מים ריק", 186 | "waste_water_tank_full": "מיכל שפכים מלא", 187 | "dirty_tank_latch_open": "מיכל מלוכלך ופתוח", 188 | "no_dustbin": "אין מיכל אבק זמין", 189 | "duct_blockage": "צינור יניקה חסום", 190 | "cleaning_tank_full_or_blocked": "ניקוי מיכל מלא או חסום" 191 | } 192 | }, 193 | "mop_drying_status": { 194 | "name": "ייבוש מטלית" 195 | }, 196 | "mop_drying_remaining_time": { 197 | "name": "זמן שנותר לייבוש מטלית" 198 | }, 199 | "clean_percent": { 200 | "name": "התקדמות הניקוי" 201 | }, 202 | "battery": { 203 | "name": "סוֹלְלָה" 204 | } 205 | }, 206 | "binary_sensor": { 207 | "mop_attached": { 208 | "name": "מטלית מחוברת" 209 | }, 210 | "water_box_attached": { 211 | "name": "מיכל מים מחובר" 212 | }, 213 | "water_shortage": { 214 | "name": "מחסור במים" 215 | } 216 | }, 217 | "select": { 218 | "mop_mode": { 219 | "name": "מסלול", 220 | "state": { 221 | "fast": "מהיר", 222 | "standard": "רגיל", 223 | "deep": "עמוק", 224 | "deep_plus": "עמוק+", 225 | "custom": "מותאם אישית" 226 | } 227 | }, 228 | "mop_intensity": { 229 | "name": "עוצמת המטלית", 230 | "state": { 231 | "off": "כבוי", 232 | "low": "נמוך", 233 | "mild": "קל", 234 | "moderate": "מתון", 235 | "medium": "בינוני", 236 | "intense": "אינטנסיבי", 237 | "high": "גבוה", 238 | "custom": "מתאם אישית", 239 | "custom_water_flow": "מותאם אישית" 240 | } 241 | } 242 | }, 243 | "vacuum": { 244 | "roborock": { 245 | "state_attributes": { 246 | "fan_speed": { 247 | "state": { 248 | "auto": "אוטומטי", 249 | "balanced": "מאוזן", 250 | "custom": "מותאם אישית", 251 | "gentle": "עדין", 252 | "off": "כבוי", 253 | "max": "מקסימום", 254 | "max_plus": "מקסימום+", 255 | "medium": "בינוני", 256 | "quiet": "שקט", 257 | "silent": "חרישי", 258 | "standard": "רגיל", 259 | "turbo": "טורבו" 260 | } 261 | }, 262 | "status": { 263 | "state": { 264 | "starting": "מתחיל", 265 | "charger_disconnected": "המטען מנותק", 266 | "idle": "ממתין", 267 | "remote_control_active": "שלט רחוק פעיל", 268 | "cleaning": "מנקה", 269 | "returning_home": "חוזר הביתה", 270 | "manual_mode": "מצב ידני", 271 | "charging": "טוען", 272 | "charging_problem": "בעיית טעינה", 273 | "paused": "מושהה", 274 | "spot_cleaning": "ניקוי נקודתי", 275 | "error": "שגיאה", 276 | "shutting_down": "מתכבה", 277 | "updating": "מעדכן", 278 | "docking": "עוגן", 279 | "going_to_target": "בדרך למטרה", 280 | "zoned_cleaning": "ניקוי אזור", 281 | "segment_cleaning": "ניקוי מקטע", 282 | "emptying_the_bin": "קירון הפח", 283 | "washing_the_mop": "שטיפת המטלית", 284 | "going_to_wash_the_mop": "בדרך לשטיפת המטלית", 285 | "charging_complete": "הטעינה הושלמה", 286 | "device_offline": "ההתקן לא מקוון" 287 | } 288 | } 289 | } 290 | } 291 | }, 292 | "time": { 293 | "dnd_start": { 294 | "name": "התחלת לא להפריע" 295 | }, 296 | "dnd_end": { 297 | "name": "סיום לא להפריע" 298 | }, 299 | "valley_electricity_start": { 300 | "name": "התחלת טעינה מחוץ לשיא" 301 | }, 302 | "valley_electricity_end": { 303 | "name": "סיום טעינה בשיא" 304 | } 305 | }, 306 | "switch": { 307 | "child_lock": { 308 | "name": "נעילת ילדים" 309 | }, 310 | "flow_led_status": { 311 | "name": "נורית חיווי מצב" 312 | }, 313 | "dnd_switch": { 314 | "name": "שינוי לא להפריע" 315 | }, 316 | "valley_electricity_switch": { 317 | "name": "מתג חשמל בעמק" 318 | } 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /custom_components/roborock/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Select an authentication method", 6 | "menu_options": { 7 | "code": "Login with email authentication code", 8 | "password": "Login with password" 9 | } 10 | }, 11 | "email": { 12 | "description": "Jouw Roborock logingegevens", 13 | "data": { 14 | "username": "Gebruikersnaam" 15 | } 16 | }, 17 | "code": { 18 | "description": "Type the verification code sent to your email", 19 | "data": { 20 | "code": "Verification code" 21 | } 22 | }, 23 | "password": { 24 | "description": "Type your password", 25 | "data": { 26 | "password": "Password" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Geen apparaat gevonden in je Roborock account", 32 | "auth": "Inloggen mislukt. Controleer je logingegevens" 33 | }, 34 | "abort": { 35 | "already_configured": "Account is already configured", 36 | "already_in_progress": "Account is already being configured" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Select a platform to configure", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Camera Options", 51 | "data": { 52 | "map_transformation:scale": "Map scale", 53 | "map_transformation:rotate": "Map rotation", 54 | "map_transformation:trim:left": "Map left trim", 55 | "map_transformation:trim:right": "Map right trim", 56 | "map_transformation:trim:top": "Map top trim", 57 | "map_transformation:trim:bottom": "Map bottom trim", 58 | "include_ignored_obstacles": "Toon genegeerde obstakels", 59 | "include_nogo": "Toon no-go-zones" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Vacuum options", 64 | "data": { 65 | "include_shared": "Include shared devices" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "General settings", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reset sensor consumable" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reset filter consumable" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reset side brush consumable" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reset main brush consumable" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Current error", 94 | "state": { 95 | "none": "None", 96 | "lidar_blocked": "LiDAR turret or laser blocked. Check for obstruction and retry.", 97 | "bumper_stuck": "Bumper stuck. Clean it and lightly tap to release it.", 98 | "wheels_suspended": "Wheels suspended. Move robot and restart.", 99 | "cliff_sensor_error": "Cliff sensor error. Clean cliff sensors, move robot away from drops and restart.", 100 | "main_brush_jammed": "Main brush jammed. Clean main brush and bearings.", 101 | "side_brush_jammed": "Side brush jammed. Remove and clean side brush.", 102 | "wheels_jammed": "Wheels jammed. Move the robot and restart.", 103 | "robot_trapped": "Robot trapped. Clear obstacles surrounding robot.", 104 | "no_dustbin": "No dustbin. Install dustbin and filter.", 105 | "low_battery": "Low battery. Recharge and retry.", 106 | "charging_error": "Charging error. Clean charging contacts and retry.", 107 | "battery_error": "Battery error.", 108 | "wall_sensor_dirty": "Wall sensor dirty. Clean wall sensor.", 109 | "robot_tilted": "Robot tilted. Move to level ground and restart.", 110 | "side_brush_error": "Side brush error. Reset robot.", 111 | "fan_error": "Fan error. Reset robot.", 112 | "vertical_bumper_pressed": "Vertical bumper pressed. Move robot and retry.", 113 | "dock_locator_error": "Dock locator error. Clean and retry.", 114 | "return_to_dock_fail": "Could not return to dock. Clean dock location beacon and retry.", 115 | "nogo_zone_detected": "VibraRise system jammed. Check for obstructions.", 116 | "vibrarise_jammed": "Robot on carpet. Move robot to floor and retry.", 117 | "robot_on_carpet": "Filter blocked or wet. Clean, dry, and retry.", 118 | "filter_blocked": "No-go zone or Invisible Wall detected. Move robot from this area.", 119 | "invisible_wall_detected": "Cannot cross carpet. Move robot across carpet and restart.", 120 | "cannot_cross_carpet": "Internal error. Reset the robot.", 121 | "internal_error": "Interne fout. Stel de robot opnieuw in." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Last clean start" 126 | }, 127 | "last_clean_end": { 128 | "name": "Last clean end" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Last clean duration" 132 | }, 133 | "last_clean_area": { 134 | "name": "Last clean area" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Current clean duration" 138 | }, 139 | "current_clean_area": { 140 | "name": "Current clean area" 141 | }, 142 | "total_duration": { 143 | "name": "Total duration" 144 | }, 145 | "total_clean_area": { 146 | "name": "Total clean area" 147 | }, 148 | "total_clean_count": { 149 | "name": "Total clean count" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Total dust collection count" 153 | }, 154 | "main_brush_left": { 155 | "name": "Main brush left" 156 | }, 157 | "side_brush_left": { 158 | "name": "Side brush left" 159 | }, 160 | "filter_left": { 161 | "name": "Filter left" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Sensor dirty left" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dock washing mode" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dock dust collection mode" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dock mop wash mode interval" 174 | }, 175 | "current_map_selected": { 176 | "name": "Current map selected" 177 | }, 178 | "current_room": { 179 | "name": "Current room" 180 | }, 181 | "dock_status": { 182 | "name": "Dock status" 183 | }, 184 | "battery": { 185 | "name": "Accu" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Mop attached" 191 | }, 192 | "water_box_attached": { 193 | "name": "Water box attached" 194 | }, 195 | "water_shortage": { 196 | "name": "Water shortage" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Mop mode", 202 | "state": { 203 | "standard": "Standard", 204 | "deep": "Deep", 205 | "deep_plus": "Deep+", 206 | "custom": "Custom" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Mop intensity", 211 | "state": { 212 | "off": "Off", 213 | "mild": "Mild", 214 | "moderate": "Moderate", 215 | "intense": "Intense", 216 | "custom": "Custom", 217 | "custom_water_flow": "Custom" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balanced", 228 | "custom": "Custom", 229 | "gentle": "Gentle", 230 | "off": "Off", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Quiet", 235 | "silent": "Silent", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Starting", 243 | "charger_disconnected": "Charger disconnected", 244 | "idle": "Idle", 245 | "remote_control_active": "Remote control active", 246 | "cleaning": "Cleaning", 247 | "returning_home": "Returning home", 248 | "manual_mode": "Manual mode", 249 | "charging": "Charging", 250 | "charging_problem": "Charging problem", 251 | "paused": "Paused", 252 | "spot_cleaning": "Spot cleaning", 253 | "error": "Error", 254 | "shutting_down": "Shutting down", 255 | "updating": "Updating", 256 | "docking": "Docking", 257 | "going_to_target": "Going to target", 258 | "zoned_cleaning": "Zoned cleaning", 259 | "segment_cleaning": "Segment cleaning", 260 | "emptying_the_bin": "Emptying the bin", 261 | "washing_the_mop": "Washing the mop", 262 | "going_to_wash_the_mop": "Going to wash the mop", 263 | "charging_complete": "Charging complete", 264 | "device_offline": "Device offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "DnD start" 273 | }, 274 | "dnd_end": { 275 | "name": "DnD end" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Off-Peak charging start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Off-Peak charging end" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Child lock" 287 | }, 288 | "flow_led_status": { 289 | "name": "Status Indicator Light" 290 | }, 291 | "dnd_switch": { 292 | "name": "DnD switch" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Valley electricity switch" 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/no.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Select an authentication method", 6 | "menu_options": { 7 | "code": "Login with email authentication code", 8 | "password": "Login with password" 9 | } 10 | }, 11 | "email": { 12 | "description": "Innlogging fra app", 13 | "data": { 14 | "username": "Brukernavn" 15 | } 16 | }, 17 | "code": { 18 | "description": "Type the verification code sent to your email", 19 | "data": { 20 | "code": "Verification code" 21 | } 22 | }, 23 | "password": { 24 | "description": "Type your password", 25 | "data": { 26 | "password": "Password" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Ingen enhet funnet i din Roborock konto", 32 | "auth": "Innlogging feilet, sjekk brukernavn og passord" 33 | }, 34 | "abort": { 35 | "already_configured": "Account is already configured", 36 | "already_in_progress": "Account is already being configured" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Select a platform to configure", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Camera Options", 51 | "data": { 52 | "map_transformation:scale": "Map scale", 53 | "map_transformation:rotate": "Map rotation", 54 | "map_transformation:trim:left": "Map left trim", 55 | "map_transformation:trim:right": "Map right trim", 56 | "map_transformation:trim:top": "Map top trim", 57 | "map_transformation:trim:bottom": "Map bottom trim", 58 | "include_ignored_obstacles": "Vis ignorerte hindringer", 59 | "include_nogo": "Vis forbudssoner" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Vacuum options", 64 | "data": { 65 | "include_shared": "Include shared devices" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "General settings", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reset sensor consumable" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reset filter consumable" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reset side brush consumable" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reset main brush consumable" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Current error", 94 | "state": { 95 | "none": "None", 96 | "lidar_blocked": "LiDAR turret or laser blocked. Check for obstruction and retry.", 97 | "bumper_stuck": "Bumper stuck. Clean it and lightly tap to release it.", 98 | "wheels_suspended": "Wheels suspended. Move robot and restart.", 99 | "cliff_sensor_error": "Cliff sensor error. Clean cliff sensors, move robot away from drops and restart.", 100 | "main_brush_jammed": "Main brush jammed. Clean main brush and bearings.", 101 | "side_brush_jammed": "Side brush jammed. Remove and clean side brush.", 102 | "wheels_jammed": "Wheels jammed. Move the robot and restart.", 103 | "robot_trapped": "Robot trapped. Clear obstacles surrounding robot.", 104 | "no_dustbin": "No dustbin. Install dustbin and filter.", 105 | "low_battery": "Low battery. Recharge and retry.", 106 | "charging_error": "Charging error. Clean charging contacts and retry.", 107 | "battery_error": "Battery error.", 108 | "wall_sensor_dirty": "Wall sensor dirty. Clean wall sensor.", 109 | "robot_tilted": "Robot tilted. Move to level ground and restart.", 110 | "side_brush_error": "Side brush error. Reset robot.", 111 | "fan_error": "Fan error. Reset robot.", 112 | "vertical_bumper_pressed": "Vertical bumper pressed. Move robot and retry.", 113 | "dock_locator_error": "Dock locator error. Clean and retry.", 114 | "return_to_dock_fail": "Could not return to dock. Clean dock location beacon and retry.", 115 | "nogo_zone_detected": "VibraRise system jammed. Check for obstructions.", 116 | "vibrarise_jammed": "Robot on carpet. Move robot to floor and retry.", 117 | "robot_on_carpet": "Filter blocked or wet. Clean, dry, and retry.", 118 | "filter_blocked": "No-go zone or Invisible Wall detected. Move robot from this area.", 119 | "invisible_wall_detected": "Cannot cross carpet. Move robot across carpet and restart.", 120 | "cannot_cross_carpet": "Internal error. Reset the robot.", 121 | "internal_error": "Intern feil. Tilbakestill roboten." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Last clean start" 126 | }, 127 | "last_clean_end": { 128 | "name": "Last clean end" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Last clean duration" 132 | }, 133 | "last_clean_area": { 134 | "name": "Last clean area" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Current clean duration" 138 | }, 139 | "current_clean_area": { 140 | "name": "Current clean area" 141 | }, 142 | "total_duration": { 143 | "name": "Total duration" 144 | }, 145 | "total_clean_area": { 146 | "name": "Total clean area" 147 | }, 148 | "total_clean_count": { 149 | "name": "Total clean count" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Total dust collection count" 153 | }, 154 | "main_brush_left": { 155 | "name": "Main brush left" 156 | }, 157 | "side_brush_left": { 158 | "name": "Side brush left" 159 | }, 160 | "filter_left": { 161 | "name": "Filter left" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Sensor dirty left" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dock washing mode" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dock dust collection mode" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dock mop wash mode interval" 174 | }, 175 | "current_map_selected": { 176 | "name": "Current map selected" 177 | }, 178 | "current_room": { 179 | "name": "Current room" 180 | }, 181 | "dock_status": { 182 | "name": "Dock status" 183 | }, 184 | "battery": { 185 | "name": "Batteri" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Mop attached" 191 | }, 192 | "water_box_attached": { 193 | "name": "Water box attached" 194 | }, 195 | "water_shortage": { 196 | "name": "Water shortage" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Mop mode", 202 | "state": { 203 | "standard": "Standard", 204 | "deep": "Deep", 205 | "deep_plus": "Deep+", 206 | "custom": "Custom" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Mop intensity", 211 | "state": { 212 | "off": "Off", 213 | "mild": "Mild", 214 | "moderate": "Moderate", 215 | "intense": "Intense", 216 | "custom": "Custom", 217 | "custom_water_flow": "Custom" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balanced", 228 | "custom": "Custom", 229 | "gentle": "Gentle", 230 | "off": "Off", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Quiet", 235 | "silent": "Silent", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Starting", 243 | "charger_disconnected": "Charger disconnected", 244 | "idle": "Idle", 245 | "remote_control_active": "Remote control active", 246 | "cleaning": "Cleaning", 247 | "returning_home": "Returning home", 248 | "manual_mode": "Manual mode", 249 | "charging": "Charging", 250 | "charging_problem": "Charging problem", 251 | "paused": "Paused", 252 | "spot_cleaning": "Spot cleaning", 253 | "error": "Error", 254 | "shutting_down": "Shutting down", 255 | "updating": "Updating", 256 | "docking": "Docking", 257 | "going_to_target": "Going to target", 258 | "zoned_cleaning": "Zoned cleaning", 259 | "segment_cleaning": "Segment cleaning", 260 | "emptying_the_bin": "Emptying the bin", 261 | "washing_the_mop": "Washing the mop", 262 | "going_to_wash_the_mop": "Going to wash the mop", 263 | "charging_complete": "Charging complete", 264 | "device_offline": "Device offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "DnD start" 273 | }, 274 | "dnd_end": { 275 | "name": "DnD end" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Off-Peak charging start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Off-Peak charging end" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Child lock" 287 | }, 288 | "flow_led_status": { 289 | "name": "Status Indicator Light" 290 | }, 291 | "dnd_switch": { 292 | "name": "DnD switch" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Valley electricity switch" 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Wybierz metodę uwierzytelniania", 6 | "menu_options": { 7 | "code": "Zaloguj się za pomocą kodu uwierzytelniającego z e-maila", 8 | "password": "Zaloguj się za pomocą hasła" 9 | } 10 | }, 11 | "email": { 12 | "description": "Dane logowania (Roborock)", 13 | "data": { 14 | "username": "Nazwa użytkownika" 15 | } 16 | }, 17 | "code": { 18 | "description": "Wpisz kod weryfikacyjny wysłany na Twój e-mail", 19 | "data": { 20 | "code": "Kod" 21 | } 22 | }, 23 | "password": { 24 | "description": "Podaj hasło", 25 | "data": { 26 | "password": "Hasło" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Nie znaleziono urządzenia na koncie Roborock", 32 | "auth": "Logowanie nieudane. Sprawdź nazwę użytkownika lub hasło" 33 | }, 34 | "abort": { 35 | "already_configured": "Konto jest już skonfigurowane", 36 | "already_in_progress": "Konto jest już w trakcie konfiguracji" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Wybierz platformę do skonfigurowania", 43 | "menu_options": { 44 | "vacuum": "Odkurzacz", 45 | "camera": "Kamera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Opcje kamery", 51 | "data": { 52 | "map_transformation:scale": "Skala mapy", 53 | "map_transformation:rotate": "Obrót mapy", 54 | "map_transformation:trim:left": "Przycięcie mapy z lewej", 55 | "map_transformation:trim:right": "Przycięcie mapy z prawej", 56 | "map_transformation:trim:top": "Przycięcie mapy z góry", 57 | "map_transformation:trim:bottom": "Przycięcie mapy od dołu", 58 | "include_ignored_obstacles": "Pokaż zignorowane przeszkody", 59 | "include_nogo": "Pokaż strefy zakazu ruchu" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "IOpcje odkurzacza", 64 | "data": { 65 | "include_shared": "Uwzględnij urządzenia współdzielone" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "General settings", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reset sensor consumable" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reset filter consumable" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reset side brush consumable" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reset main brush consumable" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Current error", 94 | "state": { 95 | "none": "zadne", 96 | "lidar_blocked": "LiDAR lub laser zablokowany. Sprawdź, czy nie ma przeszkód i ponów próbę.", 97 | "bumper_stuck": "Zderzak utknął. Oczyść go i lekko stuknij, aby go uwolnić.", 98 | "wheels_suspended": "Koła zawieszone. Przesuń robota i uruchom ponownie.", 99 | "cliff_sensor_error": "Błąd czujnika klifu. Wyczyść czujniki klifu, odsuń robota od spadków i uruchom ponownie.", 100 | "main_brush_jammed": "Szczotka główna zakleszczona. Oczyścić szczotkę główną i łożyska.", 101 | "side_brush_jammed": "Szczotka boczna zacięła się. Wyjmij i wyczyścić szczotkę boczną.", 102 | "wheels_jammed": "Koła się zacięły. Przesuń robota i uruchom ponownie.", 103 | "robot_trapped": "Robot uwięziony. Usuń przeszkody otaczające robota.", 104 | "no_dustbin": "Brak pojemnika na śmieci. Zainstaluj kosz na śmieci i filtr.", 105 | "low_battery": "Niski poziom baterii. Naładuj i ponów próbę.", 106 | "charging_error": "Błąd ładowania. Oczyść styki ładowania i spróbuj ponownie.", 107 | "battery_error": "Błąd baterii.", 108 | "wall_sensor_dirty": "Zabrudzony czujnik ścian. Wyczyść czujnik ścian.", 109 | "robot_tilted": "Robot jest przechylony. Przenieś na równy grunt i uruchom ponownie.", 110 | "side_brush_error": "Błąd szczotki bocznej. Zresetuj robota.", 111 | "fan_error": "Błąd wentylatora. Zresetuj robota.", 112 | "vertical_bumper_pressed": "Pionowy zderzak wciśnięty. Przesuń robota i spróbuj ponownie.", 113 | "dock_locator_error": "Błąd lokalizatora bazy. Wyczyść i spróbuj ponownie.", 114 | "return_to_dock_fail": "Nie można wrócić do bazy. Usuń lokalizację bazy i ponów próbę.", 115 | "nogo_zone_detected": "System VibraRise zaciął się. Sprawdź czy nie ma przeszkód.", 116 | "vibrarise_jammed": "Robot na dywanie. Przenieś robota na podłogę i ponów próbę.", 117 | "robot_on_carpet": "Filtr zablokowany lub mokry. Wyczyść, wysusz i spróbuj ponownie.", 118 | "filter_blocked": "Wykryto strefę No-go lub niewidzialną ścianę. Przenieś robota z tego obszaru.", 119 | "invisible_wall_detected": "Nie można przejść przez dywan. Przenieś robota przez dywan i uruchom ponownie.", 120 | "cannot_cross_carpet": "Błąd wewnętrzny. Zresetuj robota.", 121 | "internal_error": "Błąd wewnętrzny. Zresetuj robota." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Last clean start" 126 | }, 127 | "last_clean_end": { 128 | "name": "Last clean end" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Last clean duration" 132 | }, 133 | "last_clean_area": { 134 | "name": "Last clean area" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Current clean duration" 138 | }, 139 | "current_clean_area": { 140 | "name": "Current clean area" 141 | }, 142 | "total_duration": { 143 | "name": "Total duration" 144 | }, 145 | "total_clean_area": { 146 | "name": "Total clean area" 147 | }, 148 | "total_clean_count": { 149 | "name": "Total clean count" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Total dust collection count" 153 | }, 154 | "main_brush_left": { 155 | "name": "Main brush left" 156 | }, 157 | "side_brush_left": { 158 | "name": "Side brush left" 159 | }, 160 | "filter_left": { 161 | "name": "Filter left" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Sensor dirty left" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Dock washing mode" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Dock dust collection mode" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Dock mop wash mode interval" 174 | }, 175 | "current_map_selected": { 176 | "name": "Current map selected" 177 | }, 178 | "current_room": { 179 | "name": "Current room" 180 | }, 181 | "dock_status": { 182 | "name": "Dock status" 183 | }, 184 | "battery": { 185 | "name": "Bateria" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Mop attached" 191 | }, 192 | "water_box_attached": { 193 | "name": "Water box attached" 194 | }, 195 | "water_shortage": { 196 | "name": "Water shortage" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Mop mode", 202 | "state": { 203 | "standard": "Standard", 204 | "deep": "Deep", 205 | "deep_plus": "Deep+", 206 | "custom": "Custom" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Mop intensity", 211 | "state": { 212 | "off": "Off", 213 | "mild": "Mild", 214 | "moderate": "Moderate", 215 | "intense": "Intense", 216 | "custom": "Custom", 217 | "custom_water_flow": "Custom" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balanced", 228 | "custom": "Custom", 229 | "gentle": "Gentle", 230 | "off": "Off", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Quiet", 235 | "silent": "Silent", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Starting", 243 | "charger_disconnected": "Charger disconnected", 244 | "idle": "Idle", 245 | "remote_control_active": "Remote control active", 246 | "cleaning": "Cleaning", 247 | "returning_home": "Returning home", 248 | "manual_mode": "Manual mode", 249 | "charging": "Charging", 250 | "charging_problem": "Charging problem", 251 | "paused": "Paused", 252 | "spot_cleaning": "Spot cleaning", 253 | "error": "Error", 254 | "shutting_down": "Shutting down", 255 | "updating": "Updating", 256 | "docking": "Docking", 257 | "going_to_target": "Going to target", 258 | "zoned_cleaning": "Zoned cleaning", 259 | "segment_cleaning": "Segment cleaning", 260 | "emptying_the_bin": "Emptying the bin", 261 | "washing_the_mop": "Washing the mop", 262 | "going_to_wash_the_mop": "Going to wash the mop", 263 | "charging_complete": "Charging complete", 264 | "device_offline": "Device offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "DnD start" 273 | }, 274 | "dnd_end": { 275 | "name": "DnD end" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Off-Peak charging start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Off-Peak charging end" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Child lock" 287 | }, 288 | "flow_led_status": { 289 | "name": "Status Indicator Light" 290 | }, 291 | "dnd_switch": { 292 | "name": "DnD switch" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Valley electricity switch" 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Selecione um método de autenticação", 6 | "menu_options": { 7 | "code": "Login com codigo de autenticação por email", 8 | "password": "Login com senha" 9 | } 10 | }, 11 | "email": { 12 | "description": "Suas credenciais do app", 13 | "data": { 14 | "username": "Email" 15 | } 16 | }, 17 | "code": { 18 | "description": "Digite o codigo de verificação enviado para o seu email", 19 | "data": { 20 | "code": "Codigo de verificação" 21 | } 22 | }, 23 | "password": { 24 | "description": "Digite sua senha", 25 | "data": { 26 | "password": "Senha" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Nenhum dispositivo encontrado na sua conta Roborock", 32 | "auth": "Falha no login. Verifique suas credenciais" 33 | }, 34 | "abort": { 35 | "already_configured": "Conta já configurada", 36 | "already_in_progress": "Conta já esta sendo configurada" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Selecione uma plataforma para configurar", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Opções de camera", 51 | "data": { 52 | "map_transformation:scale": "Escala do mapa", 53 | "map_transformation:rotate": "Rotação do mapa", 54 | "map_transformation:trim:left": "Guarnição esquerda do mapa", 55 | "map_transformation:trim:right": "Guarnição direita do mapa", 56 | "map_transformation:trim:top": "Guarnição superior do mapa", 57 | "map_transformation:trim:bottom": "Guarnição inferior do mapa", 58 | "include_ignored_obstacles": "Mostrar obstáculos ignorados", 59 | "include_nogo": "Mostrar zonas proibidas" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Opções do aspirador", 64 | "data": { 65 | "include_shared": "Incluir dispositivos compartilhados" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Configurações gerais", 70 | "data": { 71 | "cloud_integration": "Usar integração na nuvem" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reiniciar manutenção sensores" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reiniciar manutenção filtro" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reiniciar manutenção escova lateral" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reiniciar manutenção escova principal" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Erro atual", 94 | "state": { 95 | "none": "Nenhum", 96 | "lidar_blocked": "Torre LiDAR ou laser bloqueado. Verifique se há obstrução e tente novamente.", 97 | "bumper_stuck": "Pára-choques preso. Limpe-o e bata levemente para soltá-lo.", 98 | "wheels_suspended": "Rodas suspensas. Mova o robô e reinicie.", 99 | "cliff_sensor_error": "Erro do sensor de queda. Limpe os sensores de queda, afaste o robô de quedas e reinicie.", 100 | "main_brush_jammed": "Escova principal encravada. Limpe a escova principal e os rolamentos.", 101 | "side_brush_jammed": "Escova lateral encravada. Remova e limpe a escova lateral.", 102 | "wheels_jammed": "Rodas emperradas. Mova o robô e reinicie.", 103 | "robot_trapped": "Robô preso. Remova os obstáculos ao redor do robô.", 104 | "no_dustbin": "Sem caixote do lixo. Instale a lixeira e o filtro.", 105 | "low_battery": "Bateria Fraca. Recarregue e tente novamente.", 106 | "charging_error": "Erro de carregamento. Limpe os contatos de carregamento e tente novamente.", 107 | "battery_error": "Erro de bateria.", 108 | "wall_sensor_dirty": "Sensor de parede sujo. Limpe o sensor de parede.", 109 | "robot_tilted": "Robô inclinado. Mova-o para o nível do solo e reinicie.", 110 | "side_brush_error": "Erro da escova lateral. Reiniciar robô.", 111 | "fan_error": "Erro do ventilador. Reiniciar robô.", 112 | "vertical_bumper_pressed": "Amortecedor vertical pressionado. Mova o robô e tente novamente.", 113 | "dock_locator_error": "Erro no localizador de docas. Limpe e tente novamente.", 114 | "return_to_dock_fail": "Não foi possível retornar a base. Limpe o sinalizador de localização da base e tente novamente.", 115 | "nogo_zone_detected": "Zona interditada ou parede invisivel detectada. Mover o robô.", 116 | "vibrarise_jammed": "Sistema VibraRise emperrado. Verifique se há obstruções.", 117 | "robot_on_carpet": "Robô no tapete. Mova o robô para o chão e tente novamente.", 118 | "filter_blocked": "Filtro bloqueado ou molhado. Limpe, seque e tente novamente.", 119 | "invisible_wall_detected": "Zona proibida ou parede invisível detectada. Mova o robô desta área.", 120 | "cannot_cross_carpet": "Não pode cruzar carpete. Mova o robô pelo carpete e reinicie.", 121 | "internal_error": "Erro interno. Reinicialize o robô." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Inicio última limpeza" 126 | }, 127 | "last_clean_end": { 128 | "name": "Fim última limpeza" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Duração última limpeza" 132 | }, 133 | "last_clean_area": { 134 | "name": "Are última limpeza" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Duração limpeza atual" 138 | }, 139 | "current_clean_area": { 140 | "name": "Area limpeza atual" 141 | }, 142 | "total_duration": { 143 | "name": "Duração total" 144 | }, 145 | "total_clean_area": { 146 | "name": "Area total limpeza" 147 | }, 148 | "total_clean_count": { 149 | "name": "Contagem total limpezas" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Contagem total coleta de poeira" 153 | }, 154 | "main_brush_left": { 155 | "name": "Restante escova principal" 156 | }, 157 | "side_brush_left": { 158 | "name": "Restante escova lateiral" 159 | }, 160 | "filter_left": { 161 | "name": "Restante filtro" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Restante sensores" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Modo de limpeza da dock" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Modo de coleta de poeira da dock" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Intervalo de lavagem do esfregão na dock" 174 | }, 175 | "current_map_selected": { 176 | "name": "Mapa selecionado atual" 177 | }, 178 | "current_room": { 179 | "name": "Quarto atual" 180 | }, 181 | "dock_status": { 182 | "name": "Estado da dock" 183 | } 184 | }, 185 | "binary_sensor": { 186 | "mop_attached": { 187 | "name": "Esfragão conectado" 188 | }, 189 | "water_box_attached": { 190 | "name": "Caixa de água conectada" 191 | }, 192 | "water_shortage": { 193 | "name": "Falta de água" 194 | } 195 | }, 196 | "select": { 197 | "mop_mode": { 198 | "name": "Modo do esfregão", 199 | "state": { 200 | "standard": "Padrão", 201 | "deep": "Profundo", 202 | "deep_plus": "Mais profundo", 203 | "custom": "Customizado" 204 | } 205 | }, 206 | "mop_intensity": { 207 | "name": "Intensidade do esfregão", 208 | "state": { 209 | "off": "Desligado", 210 | "mild": "Leve", 211 | "moderate": "Moderado", 212 | "intense": "Intenso", 213 | "custom": "Customizado", 214 | "custom_water_flow": "Customizado" 215 | } 216 | } 217 | }, 218 | "vacuum": { 219 | "roborock": { 220 | "state_attributes": { 221 | "fan_speed": { 222 | "state": { 223 | "auto": "Auto", 224 | "balanced": "Balanceado", 225 | "custom": "Customizado", 226 | "gentle": "Gentil", 227 | "off": "Desligado", 228 | "max": "Max", 229 | "max_plus": "Max+", 230 | "medium": "Medio", 231 | "quiet": "Quieto", 232 | "silent": "Silencioso", 233 | "standard": "Padrão", 234 | "turbo": "Turbo" 235 | } 236 | }, 237 | "status": { 238 | "state": { 239 | "starting": "Iniciando", 240 | "charger_disconnected": "Corregador desconectado", 241 | "idle": "Ocioso", 242 | "remote_control_active": "Controle remoto ativo", 243 | "cleaning": "Limpando", 244 | "returning_home": "Retornando para base", 245 | "manual_mode": "Modo manual", 246 | "charging": "Carregando", 247 | "charging_problem": "Problema ao carregar", 248 | "paused": "Pausado", 249 | "spot_cleaning": "Limpeza local", 250 | "error": "Erro", 251 | "shutting_down": "Desligando", 252 | "updating": "Atualizando", 253 | "docking": "Conectando a base", 254 | "going_to_target": "Indo para alvo", 255 | "zoned_cleaning": "Limpeza de zona", 256 | "segment_cleaning": "Limpeza de segmento", 257 | "emptying_the_bin": "Emptying the bin", 258 | "washing_the_mop": "Lavando o esfregão", 259 | "going_to_wash_the_mop": "Indo lavar o esfregão", 260 | "charging_complete": "Carregamento completo", 261 | "device_offline": "Dispositivo offline" 262 | } 263 | } 264 | } 265 | } 266 | }, 267 | "time": { 268 | "dnd_start": { 269 | "name": "Não perturbe começo" 270 | }, 271 | "dnd_end": { 272 | "name": "Não perturbe fim" 273 | }, 274 | "valley_electricity_start": { 275 | "name": "Carregamento fora de pico começo" 276 | }, 277 | "valley_electricity_end": { 278 | "name": "Carregamento fora de pico fim" 279 | } 280 | }, 281 | "switch": { 282 | "child_lock": { 283 | "name": "Trava de criança" 284 | }, 285 | "flow_led_status": { 286 | "name": "Led de fluxo de estado" 287 | }, 288 | "dnd_switch": { 289 | "name": "Não perturbe" 290 | }, 291 | "valley_electricity_switch": { 292 | "name": "Preço energia reduzido" 293 | } 294 | } 295 | } 296 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Selecione um método de autenticação", 6 | "menu_options": { 7 | "code": "Login com codigo de autenticação por email", 8 | "password": "Login com senha" 9 | } 10 | }, 11 | "email": { 12 | "description": "Suas credenciais do app", 13 | "data": { 14 | "username": "Email" 15 | } 16 | }, 17 | "code": { 18 | "description": "Digite o codigo de verificação enviado para o seu email", 19 | "data": { 20 | "code": "Codigo de verificação" 21 | } 22 | }, 23 | "password": { 24 | "description": "Digite sua senha", 25 | "data": { 26 | "password": "Senha" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Nenhum dispositivo encontrado na sua conta Roborock", 32 | "auth": "Falha no login. Verifique suas credenciais" 33 | }, 34 | "abort": { 35 | "already_configured": "Conta já configurada", 36 | "already_in_progress": "Conta já esta sendo configurada" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Selecione uma plataforma para configurar", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Opções de camera", 51 | "data": { 52 | "map_transformation:scale": "Escala do mapa", 53 | "map_transformation:rotate": "Rotação do mapa", 54 | "map_transformation:trim:left": "Guarnição esquerda do mapa", 55 | "map_transformation:trim:right": "Guarnição direita do mapa", 56 | "map_transformation:trim:top": "Guarnição superior do mapa", 57 | "map_transformation:trim:bottom": "Guarnição inferior do mapa", 58 | "include_ignored_obstacles": "Mostrar obstáculos ignorados", 59 | "include_nogo": "Mostrar zonas proibidas" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Opções do aspirador", 64 | "data": { 65 | "include_shared": "Incluir dispositivos compartilhados" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Configurações gerais", 70 | "data": { 71 | "cloud_integration": "Usar integração na nuvem" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Reiniciar manutenção sensores" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Reiniciar manutenção filtro" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Reiniciar manutenção escova lateral" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Reiniciar manutenção escova principal" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Erro atual", 94 | "state": { 95 | "none": "Nenhum", 96 | "lidar_blocked": "Torre LiDAR ou laser bloqueado. Verifique se há obstrução e tente novamente.", 97 | "bumper_stuck": "Pára-choques preso. Limpe-o e bata levemente para soltá-lo.", 98 | "wheels_suspended": "Rodas suspensas. Mova o robô e reinicie.", 99 | "cliff_sensor_error": "Erro do sensor de queda. Limpe os sensores de queda, afaste o robô de quedas e reinicie.", 100 | "main_brush_jammed": "Escova principal encravada. Limpe a escova principal e os rolamentos.", 101 | "side_brush_jammed": "Escova lateral encravada. Remova e limpe a escova lateral.", 102 | "wheels_jammed": "Rodas emperradas. Mova o robô e reinicie.", 103 | "robot_trapped": "Robô preso. Remova os obstáculos ao redor do robô.", 104 | "no_dustbin": "Sem caixote do lixo. Instale a lixeira e o filtro.", 105 | "low_battery": "Bateria Fraca. Recarregue e tente novamente.", 106 | "charging_error": "Erro de carregamento. Limpe os contatos de carregamento e tente novamente.", 107 | "battery_error": "Erro de bateria.", 108 | "wall_sensor_dirty": "Sensor de parede sujo. Limpe o sensor de parede.", 109 | "robot_tilted": "Robô inclinado. Mova-o para o nível do solo e reinicie.", 110 | "side_brush_error": "Erro da escova lateral. Reiniciar robô.", 111 | "fan_error": "Erro do ventilador. Reiniciar robô.", 112 | "vertical_bumper_pressed": "Amortecedor vertical pressionado. Mova o robô e tente novamente.", 113 | "dock_locator_error": "Erro no localizador de docas. Limpe e tente novamente.", 114 | "return_to_dock_fail": "Não foi possível retornar a base. Limpe o sinalizador de localização da base e tente novamente.", 115 | "nogo_zone_detected": "Zona interditada ou parede invisivel detectada. Mover o robô.", 116 | "vibrarise_jammed": "Sistema VibraRise emperrado. Verifique se há obstruções.", 117 | "robot_on_carpet": "Robô no tapete. Mova o robô para o chão e tente novamente.", 118 | "filter_blocked": "Filtro bloqueado ou molhado. Limpe, seque e tente novamente.", 119 | "invisible_wall_detected": "Zona proibida ou parede invisível detectada. Mova o robô desta área.", 120 | "cannot_cross_carpet": "Não pode cruzar carpete. Mova o robô pelo carpete e reinicie.", 121 | "internal_error": "Erro interno. Reinicialize o robô." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Inicio última limpeza" 126 | }, 127 | "last_clean_end": { 128 | "name": "Fim última limpeza" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Duração última limpeza" 132 | }, 133 | "last_clean_area": { 134 | "name": "Are última limpeza" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Duração limpeza atual" 138 | }, 139 | "current_clean_area": { 140 | "name": "Area limpeza atual" 141 | }, 142 | "total_duration": { 143 | "name": "Duração total" 144 | }, 145 | "total_clean_area": { 146 | "name": "Area total limpeza" 147 | }, 148 | "total_clean_count": { 149 | "name": "Contagem total limpezas" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Contagem total coleta de poeira" 153 | }, 154 | "main_brush_left": { 155 | "name": "Restante escova principal" 156 | }, 157 | "side_brush_left": { 158 | "name": "Restante escova lateiral" 159 | }, 160 | "filter_left": { 161 | "name": "Restante filtro" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Restante sensores" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Modo de limpeza da dock" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Modo de coleta de poeira da dock" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Intervalo de lavagem do esfregão na dock" 174 | }, 175 | "current_map_selected": { 176 | "name": "Mapa selecionado atual" 177 | }, 178 | "current_room": { 179 | "name": "Quarto atual" 180 | }, 181 | "dock_status": { 182 | "name": "Estado da dock" 183 | }, 184 | "battery": { 185 | "name": "Batteria" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Esfragão conectado" 191 | }, 192 | "water_box_attached": { 193 | "name": "Caixa de água conectada" 194 | }, 195 | "water_shortage": { 196 | "name": "Falta de água" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Modo do esfregão", 202 | "state": { 203 | "standard": "Padrão", 204 | "deep": "Profundo", 205 | "deep_plus": "Mais profundo", 206 | "custom": "Customizado" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Intensidade do esfregão", 211 | "state": { 212 | "off": "Desligado", 213 | "mild": "Leve", 214 | "moderate": "Moderado", 215 | "intense": "Intenso", 216 | "custom": "Customizado", 217 | "custom_water_flow": "Customizado" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balanceado", 228 | "custom": "Customizado", 229 | "gentle": "Gentil", 230 | "off": "Desligado", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medio", 234 | "quiet": "Quieto", 235 | "silent": "Silencioso", 236 | "standard": "Padrão", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Iniciando", 243 | "charger_disconnected": "Corregador desconectado", 244 | "idle": "Ocioso", 245 | "remote_control_active": "Controle remoto ativo", 246 | "cleaning": "Limpando", 247 | "returning_home": "Retornando para base", 248 | "manual_mode": "Modo manual", 249 | "charging": "Carregando", 250 | "charging_problem": "Problema ao carregar", 251 | "paused": "Pausado", 252 | "spot_cleaning": "Limpeza local", 253 | "error": "Erro", 254 | "shutting_down": "Desligando", 255 | "updating": "Atualizando", 256 | "docking": "Conectando a base", 257 | "going_to_target": "Indo para alvo", 258 | "zoned_cleaning": "Limpeza de zona", 259 | "segment_cleaning": "Limpeza de segmento", 260 | "emptying_the_bin": "Emptying the bin", 261 | "washing_the_mop": "Lavando o esfregão", 262 | "going_to_wash_the_mop": "Indo lavar o esfregão", 263 | "charging_complete": "Carregamento completo", 264 | "device_offline": "Dispositivo offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "Não perturbe começo" 273 | }, 274 | "dnd_end": { 275 | "name": "Não perturbe fim" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Carregamento fora de pico começo" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Carregamento fora de pico fim" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Trava de criança" 287 | }, 288 | "flow_led_status": { 289 | "name": "Led de fluxo de estado" 290 | }, 291 | "dnd_switch": { 292 | "name": "Não perturbe" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Preço energia reduzido" 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Выберите метод аутентификации", 6 | "menu_options": { 7 | "code": "Войти с помощью кода на почту", 8 | "password": "Войти с помощью пароля" 9 | } 10 | }, 11 | "email": { 12 | "description": "Ваши данные из приложения", 13 | "data": { 14 | "username": "Почта" 15 | } 16 | }, 17 | "code": { 18 | "description": "Введите код отправленный вам на почту", 19 | "data": { 20 | "code": "Код подтверждения" 21 | } 22 | }, 23 | "password": { 24 | "description": "Введите свой пароль", 25 | "data": { 26 | "password": "Пароль" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "В вашей учетной записи Roborock не найдено ни одного устройства", 32 | "auth": "Не удалось войти. Проверьте данные." 33 | }, 34 | "abort": { 35 | "already_configured": "Аккаунт уже настроен", 36 | "already_in_progress": "Аккаунт уже настраивается" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Выберите платформу для настройки", 43 | "menu_options": { 44 | "vacuum": "Пылесос", 45 | "camera": "Камера", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Параметры камеры", 51 | "data": { 52 | "map_transformation:scale": "Масштаб карты", 53 | "map_transformation:rotate": "Вращение карты", 54 | "map_transformation:trim:left": "Выравнивание карты слева", 55 | "map_transformation:trim:right": "Выравнивание карты справа", 56 | "map_transformation:trim:top": "Выравнивание карты сверху", 57 | "map_transformation:trim:bottom": "Выравнивание карты снизу", 58 | "include_ignored_obstacles": "Показать игнорируемые препятствия", 59 | "include_nogo": "Показать запретные зоны" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Параметры пылесоса", 64 | "data": { 65 | "include_shared": "Добавить общие устройства" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Общие настройки", 70 | "data": { 71 | "cloud_integration": "Use cloud integration" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Сброс данных очистки сенсоров" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Сброс данных очистки фильтра" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Сброс данных очистки передней щётки" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Сброс данных очистки основной щётки" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Текущая ошибка", 94 | "state": { 95 | "none": "Нет", 96 | "lidar_blocked": "Башня LiDAR или лазер заблокированы. Проверьте наличие препятствий и повторите попытку.", 97 | "bumper_stuck": "Бампер застрял. Очистите его и слегка коснитесь, чтобы освободить.", 98 | "wheels_suspended": "Колеса подвешены. Переместите робота и перезапустите.", 99 | "cliff_sensor_error": "Ошибка датчика перепада высоты. Очистите датчики , отодвиньте робота и перезапустите.", 100 | "main_brush_jammed": "Заклинило основную щетку. Очистите основную щетку и подшипники.", 101 | "side_brush_jammed": "Заклинило боковую щетку. Снимите и очистите боковую щетку.", 102 | "wheels_jammed": "Колеса заклинило. Переместите робота и перезапустите.", 103 | "robot_trapped": "Робот в ловушке. Уберите препятствия вокруг робота.", 104 | "no_dustbin": "Отсутсвует мусоросборник. Установите мусоросборник и фильтр.", 105 | "low_battery": "Низкий заряд батареи. Зарядите и повторите попытку.", 106 | "charging_error": "Ошибка зарядки. Очистите контакты для зарядки и повторите попытку.", 107 | "battery_error": "Ошибка батареи.", 108 | "wall_sensor_dirty": "Датчик препятствий загрязнен. Очистите датчик препятствий.", 109 | "robot_tilted": "Робот подвис. Переместите на ровную поверхность и перезапустите.", 110 | "side_brush_error": "Ошибка боковой щетки. Перезапустите робота.", 111 | "fan_error": "Ошибка лопостей. Перезапустите робота.", 112 | "vertical_bumper_pressed": "Вертикальный бампер вдавлен. Переместите робота и повторите попытку.", 113 | "dock_locator_error": "Ошибка локации станции. Очистите и повторите попытку.", 114 | "return_to_dock_fail": "Не удалось вернуться к станции. Очистите маяк местоположения станции и повторите попытку.", 115 | "nogo_zone_detected": "Система VibraRise заблокирована. Проверьте на наличие препятствий.", 116 | "vibrarise_jammed": "Робот на ковре. Переместите робота на пол и повторите попытку.", 117 | "robot_on_carpet": "Фильтр забит или намок. Очистите, высушите и повторите попытку.", 118 | "filter_blocked": "Обнаружена запретная зона или невидимая стена. Переместите робота из этой области.", 119 | "invisible_wall_detected": "Не удалось пересечь ковер. Переместите робота с ковра и перезапустите.", 120 | "cannot_cross_carpet": "Внутренняя ошибка. Сбросьте робота.", 121 | "internal_error": "Внутренняя ошибка. Переустановите робота." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Время начала последней уборки" 126 | }, 127 | "last_clean_end": { 128 | "name": "Время окончания последней уборки" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Время последней уборки" 132 | }, 133 | "last_clean_area": { 134 | "name": "Площадь последней уборки" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Время текущей уборки" 138 | }, 139 | "current_clean_area": { 140 | "name": "Площадь текущей уборки" 141 | }, 142 | "total_duration": { 143 | "name": "Общее время уборки" 144 | }, 145 | "total_clean_area": { 146 | "name": "Площадь уборки за всё время" 147 | }, 148 | "total_clean_count": { 149 | "name": "Убрано комнат за всё время" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Собрано пыли за всё время" 153 | }, 154 | "main_brush_left": { 155 | "name": "Остаток до очистки основной щетки" 156 | }, 157 | "side_brush_left": { 158 | "name": "Остаток до очистки передней щетки" 159 | }, 160 | "filter_left": { 161 | "name": "Остаток до очистки фильтра" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Остаток до очистки сенсоров" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Режим очистки док-станции" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Режим сбора пыли в док-станции" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Интервал очистки тряпки" 174 | }, 175 | "current_map_selected": { 176 | "name": "Выбрана карта" 177 | }, 178 | "current_room": { 179 | "name": "Текущая комната" 180 | }, 181 | "dock_status": { 182 | "name": "Состояние док-станции" 183 | }, 184 | "battery": { 185 | "name": "Батарея" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Моющая тряпка" 191 | }, 192 | "water_box_attached": { 193 | "name": "Ёмкость для воды" 194 | }, 195 | "water_shortage": { 196 | "name": "Наличие воды в ёмкости" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Интенсивность мытья", 202 | "state": { 203 | "standard": "Обычная", 204 | "deep": "Интенсивная", 205 | "deep_plus": "Интенсивная+", 206 | "custom": "Настраиваемая" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Подача воды", 211 | "state": { 212 | "off": "Отключено", 213 | "mild": "Минимальная", 214 | "moderate": "Средняя", 215 | "intense": "Максимальная", 216 | "custom": "Настраиваемая", 217 | "custom_water_flow": "Настраиваемая" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balanced", 228 | "custom": "Custom", 229 | "gentle": "Gentle", 230 | "off": "Off", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Quiet", 235 | "silent": "Silent", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Starting", 243 | "charger_disconnected": "Charger disconnected", 244 | "idle": "Idle", 245 | "remote_control_active": "Remote control active", 246 | "cleaning": "Cleaning", 247 | "returning_home": "Returning home", 248 | "manual_mode": "Manual mode", 249 | "charging": "Charging", 250 | "charging_problem": "Charging problem", 251 | "paused": "Paused", 252 | "spot_cleaning": "Spot cleaning", 253 | "error": "Error", 254 | "shutting_down": "Shutting down", 255 | "updating": "Updating", 256 | "docking": "Docking", 257 | "going_to_target": "Going to target", 258 | "zoned_cleaning": "Zoned cleaning", 259 | "segment_cleaning": "Segment cleaning", 260 | "emptying_the_bin": "Emptying the bin", 261 | "washing_the_mop": "Washing the mop", 262 | "going_to_wash_the_mop": "Going to wash the mop", 263 | "charging_complete": "Charging complete", 264 | "device_offline": "Device offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "Не беспокоить начало" 273 | }, 274 | "dnd_end": { 275 | "name": "Не беспокоить конец" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Off-Peak charging start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Off-Peak charging end" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Child lock" 287 | }, 288 | "flow_led_status": { 289 | "name": "Status Indicator Light" 290 | }, 291 | "dnd_switch": { 292 | "name": "DnD switch" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Valley electricity switch" 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /custom_components/roborock/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Ange en autentiseringsmetod ", 6 | "menu_options": { 7 | "code": "Logga in med verifikationskoden via e-post", 8 | "password": "Logga in med lösenord" 9 | } 10 | }, 11 | "email": { 12 | "description": "Dina inloggningsuppgifter till appen", 13 | "data": { 14 | "username": "E-post/Användarnamn" 15 | } 16 | }, 17 | "code": { 18 | "description": "Ange verifikationskoden som skickades till din e-post", 19 | "data": { 20 | "code": "Verifikationskod" 21 | } 22 | }, 23 | "password": { 24 | "description": "Ange ditt lösenord", 25 | "data": { 26 | "password": "Lösenord" 27 | } 28 | } 29 | }, 30 | "error": { 31 | "no_device": "Inga enheter hittades på ditt Roborock konto", 32 | "auth": "Inloggning misslyckades. Kontrollera dina inloggningsuppgifter" 33 | }, 34 | "abort": { 35 | "already_configured": "Enheten redan konfigurerad", 36 | "already_in_progress": "Kontot är redan konfigurerat" 37 | } 38 | }, 39 | "options": { 40 | "step": { 41 | "user": { 42 | "description": "Välj en plattform att konfigurera", 43 | "menu_options": { 44 | "vacuum": "Vacuum", 45 | "camera": "Camera", 46 | "roborock": "Roborock" 47 | } 48 | }, 49 | "camera": { 50 | "description": "Kartalternativ (Camera)", 51 | "data": { 52 | "map_transformation:scale": "Kartskala", 53 | "map_transformation:rotate": "Rotera kartan", 54 | "map_transformation:trim:left": "Trimma vänster kant", 55 | "map_transformation:trim:right": "Trimma höger kant", 56 | "map_transformation:trim:top": "Trimma övere kant", 57 | "map_transformation:trim:bottom": "Trimma undre kant", 58 | "include_ignored_obstacles": "Visa ignorerade hinder", 59 | "include_nogo": "Visa no go-zoner" 60 | } 61 | }, 62 | "vacuum": { 63 | "description": "Dammsugaralternativ (Vacuum)", 64 | "data": { 65 | "include_shared": "Inkludera delade enheter" 66 | } 67 | }, 68 | "roborock": { 69 | "description": "Allmänna inställningar", 70 | "data": { 71 | "cloud_integration": "Använd molnintegrationen" 72 | } 73 | } 74 | } 75 | }, 76 | "entity": { 77 | "button": { 78 | "reset_sensor_consumable": { 79 | "name": "Återställ sensorerna" 80 | }, 81 | "reset_filter_consumable": { 82 | "name": "Återställ filtret" 83 | }, 84 | "reset_side_brush_consumable": { 85 | "name": "Återställ sidoborsten" 86 | }, 87 | "reset_main_brush_consumable": { 88 | "name": "Återställ huvudborsten" 89 | } 90 | }, 91 | "sensor": { 92 | "current_error": { 93 | "name": "Pågående fel", 94 | "state": { 95 | "none": "Inget", 96 | "lidar_blocked": "LiDAR torn eller lasern är blockerad. Kontrollera hinder och försök igen.", 97 | "bumper_stuck": "Stötfångaren har fastnat. Rengör och tryck försiktigt för att frigöra den.", 98 | "wheels_suspended": "Hjulen upphängda. Flytta roboten och starta om.", 99 | "cliff_sensor_error": "Fel på fallsensorn. Rengör fallsensorerna, flytta roboten från trappor etc. och starta om.", 100 | "main_brush_jammed": "Huvudborsten har fastnat. Rengör huvudborsten och dess kugghjul.", 101 | "side_brush_jammed": "Sidoborsten har fastnat. Montera av och rengör sidoborsten.", 102 | "wheels_jammed": "Hjulen har fastnat. Flytta roboten och starta om.", 103 | "robot_trapped": "Roboten har fastnat. Ta bort hinder runtomkring roboten och starta om.", 104 | "no_dustbin": "Ingen smutsbehållare. Installera smutbehållare och filter.", 105 | "low_battery": "Lågt batteri. Ludda upp och försök igen.", 106 | "charging_error": "Uppladdningsfel. Rengör uppladdningskontakterna och försök igen.", 107 | "battery_error": "Batterifel.", 108 | "wall_sensor_dirty": "Smutsiga väggsensorer. Rengör väggsensorerna.", 109 | "robot_tilted": "Roboten lutar. Flytta till jämnt underlag och starta om.", 110 | "side_brush_error": "Fel på sidoborsten. Återställ roboten.", 111 | "fan_error": "Fel på fläkten. Återställ roboten.", 112 | "vertical_bumper_pressed": "Vertikal stötfångare intryckt. Flytta roboten och starta om.", 113 | "dock_locator_error": "Fel på laddstationssensorn. Rengör och försök igen.", 114 | "return_to_dock_fail": "Kunde inte går tillbaka till laddstationen. Rengör laddstationssensorn och försök igen.", 115 | "nogo_zone_detected": "No-go zon eller osynlig vägg upptäckt. Flytta roboten.", 116 | "vibrarise_jammed": "VibraRise systemet har fastnat. Kontrollera hinder.", 117 | "robot_on_carpet": "Roboten på matta. Flytta roboten till golv och försök igen.", 118 | "filter_blocked": "Blockerat eller blött filter. Rengör, torka och försök igen.", 119 | "invisible_wall_detected": "No-go zon eller osynlig vägg upptäckt. Flytta bort roboten från detta område.", 120 | "cannot_cross_carpet": "Kan inte köra över matta. Flytta över roboten och starta om.", 121 | "internal_error": "Internt fel. Återställ roboten." 122 | } 123 | }, 124 | "last_clean_start": { 125 | "name": "Start senaste rengöringen" 126 | }, 127 | "last_clean_end": { 128 | "name": "Slut senaste rengöringen" 129 | }, 130 | "last_clean_duration": { 131 | "name": "Senaste rengöringstid" 132 | }, 133 | "last_clean_area": { 134 | "name": "Senaste rengjorda område" 135 | }, 136 | "current_clean_duration": { 137 | "name": "Pågående rengöringtid" 138 | }, 139 | "current_clean_area": { 140 | "name": "Pågående område" 141 | }, 142 | "total_duration": { 143 | "name": "Total regörningstid" 144 | }, 145 | "total_clean_area": { 146 | "name": "Totalt rengöringssarea" 147 | }, 148 | "total_clean_count": { 149 | "name": "Totala antal rengörningar" 150 | }, 151 | "total_dust_collection_count": { 152 | "name": "Totalt antal uppsamlingar" 153 | }, 154 | "main_brush_left": { 155 | "name": "Kvar av huvudborsten" 156 | }, 157 | "side_brush_left": { 158 | "name": "Kvar av sidoborsten" 159 | }, 160 | "filter_left": { 161 | "name": "Kvar av filtret" 162 | }, 163 | "sensor_dirty_left": { 164 | "name": "Kvar av nedsmutsade sensorer" 165 | }, 166 | "dock_washing_mode": { 167 | "name": "Rengöringsläge docka" 168 | }, 169 | "dock_dust_collection_mode": { 170 | "name": "Uppsamlingsläge docka" 171 | }, 172 | "dock_mop_wash_mode_interval": { 173 | "name": "Intervall dockans moppläge" 174 | }, 175 | "current_map_selected": { 176 | "name": "Nuvarande karta vald" 177 | }, 178 | "current_room": { 179 | "name": "Nuvarande rum" 180 | }, 181 | "dock_status": { 182 | "name": "Dockningsstatus" 183 | }, 184 | "battery": { 185 | "name": "Batteri" 186 | } 187 | }, 188 | "binary_sensor": { 189 | "mop_attached": { 190 | "name": "Mopp monterad" 191 | }, 192 | "water_box_attached": { 193 | "name": "Vattentank monterad" 194 | }, 195 | "water_shortage": { 196 | "name": "Låg vattennivå" 197 | } 198 | }, 199 | "select": { 200 | "mop_mode": { 201 | "name": "Moppläge", 202 | "state": { 203 | "standard": "Standard", 204 | "deep": "Djup", 205 | "deep_plus": "Djup+", 206 | "custom": "Anpassad" 207 | } 208 | }, 209 | "mop_intensity": { 210 | "name": "Moppintensitet", 211 | "state": { 212 | "off": "Av", 213 | "mild": "Mild", 214 | "moderate": "Lagom", 215 | "intense": "Hård", 216 | "custom": "Anpassad", 217 | "custom_water_flow": "Anpassad" 218 | } 219 | } 220 | }, 221 | "vacuum": { 222 | "roborock": { 223 | "state_attributes": { 224 | "fan_speed": { 225 | "state": { 226 | "auto": "Auto", 227 | "balanced": "Balancerad", 228 | "custom": "Anpassad", 229 | "gentle": "Mild", 230 | "off": "Av", 231 | "max": "Max", 232 | "max_plus": "Max+", 233 | "medium": "Medium", 234 | "quiet": "Tyst", 235 | "silent": "Ljudlös", 236 | "standard": "Standard", 237 | "turbo": "Turbo" 238 | } 239 | }, 240 | "status": { 241 | "state": { 242 | "starting": "Startar", 243 | "charger_disconnected": "Laddare frånkopplad", 244 | "idle": "Inaktiv", 245 | "remote_control_active": "Fjärrstyrning aktiverad", 246 | "cleaning": "Rengör", 247 | "returning_home": "Återvänder hem", 248 | "manual_mode": "Manuellt läge", 249 | "charging": "Laddar", 250 | "charging_problem": "Laddningsproblem", 251 | "paused": "Pausad", 252 | "spot_cleaning": "Punktrengöring", 253 | "error": "Fel", 254 | "shutting_down": "Stänger av", 255 | "updating": "Uppdaterar", 256 | "docking": "Dockar", 257 | "going_to_target": "På väg till målepunkt", 258 | "zoned_cleaning": "Zonrengöring", 259 | "segment_cleaning": "Segmentrengöring", 260 | "emptying_the_bin": "Tömmer uppsamlaren", 261 | "washing_the_mop": "Tvättar moppen", 262 | "going_to_wash_the_mop": "På väg för att tvätta moppen", 263 | "charging_complete": "Uppladning klar", 264 | "device_offline": "Enheten offline" 265 | } 266 | } 267 | } 268 | } 269 | }, 270 | "time": { 271 | "dnd_start": { 272 | "name": "Stör ej start" 273 | }, 274 | "dnd_end": { 275 | "name": "Stör ej slut" 276 | }, 277 | "valley_electricity_start": { 278 | "name": "Lågprisladdning start" 279 | }, 280 | "valley_electricity_end": { 281 | "name": "Lågprisladdning slut" 282 | } 283 | }, 284 | "switch": { 285 | "child_lock": { 286 | "name": "Barnlås" 287 | }, 288 | "flow_led_status": { 289 | "name": "Statusindikator" 290 | }, 291 | "dnd_switch": { 292 | "name": "Växla stör ej" 293 | }, 294 | "valley_electricity_switch": { 295 | "name": "Växla lågprisladdning" 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /custom_components/roborock/utils.py: -------------------------------------------------------------------------------- 1 | """Utilities for roborock.""" 2 | 3 | 4 | def set_nested_dict(data: dict, key_string: str, value): 5 | """Set nested dict.""" 6 | here = data 7 | keys = key_string.split(":") 8 | for key in keys[:-1]: 9 | here = here.setdefault(key, {}) 10 | here[keys[-1]] = value 11 | 12 | 13 | def get_nested_dict(data: dict, key_string: str, default=None) -> dict: 14 | """Get nested dict.""" 15 | here = data 16 | keys = key_string.split(":") 17 | for key in keys: 18 | here = here.get(key) 19 | if here is None: 20 | return default 21 | return here 22 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Roborock", 3 | "homeassistant": "2023.10.0" 4 | } -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # Roborock integration for HomeAsssistant 2 | 3 | 4 | ## About this repo 5 | I've bought a Roborock S7 Maxv and hated the fact that I had to use the Roborock App or the previous existing HomeAssistant integration. But not both. 6 | 7 | Using the Xiaomi integration there is also a limit to the number of map request which this integration don't have 8 | 9 | Hope everyone can enjoy this integration along side the Roborock App 10 | 11 | [![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/humbertogontijo) 12 | 13 | --- 14 | 15 | ## Installation 16 | 17 | I recommend installing it through [HACS](https://github.com/hacs/integration) 18 | 19 | ### Installing via HACS 20 | 21 | 1. Go to HACS->Integrations 22 | 2. Add this repo into your HACS custom repositories 23 | 3. Search for Roborock and Download it 24 | 4. Restart your HomeAssistant 25 | 5. Go to Settings->Devices & Services 26 | 6. Shift reload your browser 27 | 7. Click Add Integration 28 | 8. Search for Roborock 29 | 9. Type your username used in the Roborock App and hit submit 30 | 10. You will receive an Email with a verification code 31 | 11. Type the verification code and hit submit 32 | 12. You're all set 33 | 34 | 35 | --- 36 | 37 | ## Special thanks 38 | 39 | Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 40 | And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor 41 | 42 | --- -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | author: 2 | email: humbertogontijo@users.noreply.github.com 3 | homepage: https://github.com/humbertogontijo/homeassistant-roborock 4 | name: humbertogontijo 5 | description: Roborock 6 | files: 7 | - custom_components/roborock/__init__.py 8 | - custom_components/roborock/vacuum.py 9 | keywords: 10 | - vacuum 11 | - robot 12 | - roborock 13 | - homeassistant 14 | - home-assistant 15 | - custom-component 16 | - python 17 | license: MIT License 18 | name: roborock 19 | type: component -------------------------------------------------------------------------------- /project.inlang.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema":"https://inlang.com/schema/project-settings", 3 | "sourceLanguageTag": "en", 4 | "languageTags": ["da", "de", "en", "fr", "it", "nl", "no", "pl", "pt-BR", "pt", "ru", "sv"], 5 | "modules": [ 6 | "https://cdn.jsdelivr.net/npm/@inlang/plugin-json@4/dist/index.js", 7 | "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@1/dist/index.js", 8 | "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@1/dist/index.js", 9 | "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@1/dist/index.js", 10 | "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@1/dist/index.js" 11 | ], 12 | "plugin.inlang.json": { 13 | "pathPattern": "custom_components/roborock/translations/{languageTag}.json" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r ./requirements.txt 2 | pytest==7.4.0 3 | pytest_homeassistant_custom_component==0.13.46 4 | aiohttp_cors==0.7.0 5 | aiodiscover==1.4.16 6 | scapy==2.5.0 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | homeassistant==2023.7.3 2 | voluptuous==0.13.1 3 | python_roborock==0.35.3 4 | Pillow==10.0.0 5 | ruff==0.0.282 6 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml 2 | 3 | target-version = "py310" 4 | 5 | select = [ 6 | "B007", # Loop control variable {name} not used within loop body 7 | "B014", # Exception handler with duplicate exception 8 | "C", # complexity 9 | "D", # docstrings 10 | "E", # pycodestyle 11 | "F", # pyflakes/autoflake 12 | "ICN001", # import concentions; {name} should be imported as {asname} 13 | "PGH004", # Use specific rule codes when using noqa 14 | "PLC0414", # Useless import alias. Import alias does not rename original package. 15 | "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass 16 | "SIM117", # Merge with-statements that use the same scope 17 | "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() 18 | "SIM201", # Use {left} != {right} instead of not {left} == {right} 19 | "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a} 20 | "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'. 21 | "SIM401", # Use get from dict with default instead of an if block 22 | "T20", # flake8-print 23 | "TRY004", # Prefer TypeError exception for invalid type 24 | "RUF006", # Store a reference to the return value of asyncio.create_task 25 | "UP", # pyupgrade 26 | "W", # pycodestyle 27 | ] 28 | 29 | ignore = [ 30 | "D202", # No blank lines allowed after function docstring 31 | "D203", # 1 blank line required before class docstring 32 | "D213", # Multi-line docstring summary should start at the second line 33 | "D404", # First word of the docstring should not be This 34 | "D406", # Section name should end with a newline 35 | "D407", # Section name underlining 36 | "D411", # Missing blank line before section 37 | "E501", # line too long 38 | "E731", # do not assign a lambda expression, use a def 39 | "UP006", # keep-runtime-typing 40 | "UP007", # keep-runtime-typing 41 | ] 42 | 43 | exclude = [ 44 | "custom_components/roborock/common", 45 | "custom_components/roborock/camera.py" 46 | ] 47 | 48 | [flake8-pytest-style] 49 | fixture-parentheses = false 50 | 51 | [mccabe] 52 | max-complexity = 25 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for Roborock integration.""" 2 | -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | """Common methods used across tests for Roborock.""" 2 | from unittest.mock import patch 3 | 4 | from homeassistant.core import HomeAssistant 5 | from homeassistant.setup import async_setup_component 6 | from pytest_homeassistant_custom_component.common import MockConfigEntry 7 | 8 | from custom_components.roborock.const import ( 9 | CAMERA, 10 | CONF_BASE_URL, 11 | CONF_BOTTOM, 12 | CONF_ENTRY_USERNAME, 13 | CONF_INCLUDE_SHARED, 14 | CONF_LEFT, 15 | CONF_MAP_TRANSFORM, 16 | CONF_RIGHT, 17 | CONF_ROTATE, 18 | CONF_SCALE, 19 | CONF_TOP, 20 | CONF_TRIM, 21 | CONF_USER_DATA, 22 | DOMAIN, 23 | VACUUM 24 | ) 25 | from .mock_data import BASE_URL, HOME_DATA, HOME_DATA_SHARED, USER_DATA_RAW, USER_EMAIL 26 | 27 | 28 | async def setup_platform( 29 | hass: HomeAssistant, platform: str, include_shared: bool = True 30 | ) -> MockConfigEntry: 31 | """Set up the Roborock platform.""" 32 | mock_entry = MockConfigEntry( 33 | domain=DOMAIN, 34 | title=USER_EMAIL, 35 | data={ 36 | CONF_ENTRY_USERNAME: USER_EMAIL, 37 | CONF_USER_DATA: USER_DATA_RAW, 38 | CONF_BASE_URL: BASE_URL, 39 | }, 40 | options={ 41 | CAMERA: { 42 | f"{CONF_MAP_TRANSFORM}.{CONF_SCALE}": 1.0, 43 | f"{CONF_MAP_TRANSFORM}.{CONF_ROTATE}": "90", 44 | f"{CONF_MAP_TRANSFORM}.{CONF_TRIM}.{CONF_LEFT}": 5.0, 45 | f"{CONF_MAP_TRANSFORM}.{CONF_TRIM}.{CONF_RIGHT}": 5.0, 46 | f"{CONF_MAP_TRANSFORM}.{CONF_TRIM}.{CONF_TOP}": 5.0, 47 | f"{CONF_MAP_TRANSFORM}.{CONF_TRIM}.{CONF_BOTTOM}": 5.0, 48 | }, 49 | VACUUM: { 50 | CONF_INCLUDE_SHARED: include_shared, 51 | } 52 | }, 53 | ) 54 | mock_entry.add_to_hass(hass) 55 | 56 | home_data = HOME_DATA_SHARED if include_shared else HOME_DATA 57 | 58 | with patch("custom_components.roborock.PLATFORMS", [platform]), patch( 59 | "roborock.api.RoborockApiClient.get_home_data", 60 | return_value=home_data, 61 | ), patch( 62 | "custom_components.roborock.get_local_devices_info", 63 | side_effect=lambda: { 64 | device.duid: {"ip": "127.0.0.1"} 65 | for device in home_data.devices + home_data.received_devices 66 | } 67 | ): 68 | assert await async_setup_component(hass, DOMAIN, {}) 69 | await hass.async_block_till_done() 70 | return mock_entry 71 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Global fixtures for Roborock integration.""" 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | from .mock_data import PROP 7 | 8 | 9 | # This fixture enables loading custom integrations in all tests. 10 | # Remove to enable selective use of this fixture 11 | @pytest.fixture(autouse=True) 12 | def auto_enable_custom_integrations(enable_custom_integrations): 13 | """Enable custom integration loading.""" 14 | yield 15 | 16 | 17 | @pytest.fixture(name="bypass_api_fixture") 18 | def bypass_api_fixture(): 19 | """Skip calls to the API.""" 20 | with patch( 21 | "roborock.cloud_api.mqtt" 22 | ), patch( 23 | "roborock.cloud_api.RoborockMqttClient.async_connect" 24 | ), patch( 25 | "roborock.cloud_api.RoborockMqttClient.async_disconnect", return_value=PROP 26 | ), patch( 27 | "roborock.cloud_api.RoborockMqttClient.sync_disconnect", return_value=PROP 28 | ), patch( 29 | "roborock.cloud_api.RoborockMqttClient.send_command" 30 | ), patch( 31 | "roborock.cloud_api.RoborockMqttClient.get_prop", return_value=PROP 32 | ), patch( 33 | "roborock.local_api.RoborockLocalClient.async_connect" 34 | ), patch( 35 | "roborock.local_api.RoborockLocalClient.async_disconnect" 36 | ), patch( 37 | "roborock.local_api.RoborockLocalClient.sync_disconnect" 38 | ), patch( 39 | "roborock.local_api.RoborockLocalClient.send_command" 40 | ), patch( 41 | "roborock.local_api.RoborockLocalClient.get_prop", return_value=PROP 42 | ): 43 | yield 44 | -------------------------------------------------------------------------------- /tests/test_binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Tests for Roborock binary sensors.""" 2 | import pytest 3 | from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN 4 | from homeassistant.components.binary_sensor import BinarySensorDeviceClass 5 | from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON 6 | from homeassistant.core import HomeAssistant 7 | from homeassistant.helpers import entity_registry as er 8 | 9 | from custom_components.roborock.binary_sensor import ( 10 | ATTR_MOP_ATTACHED, 11 | ATTR_WATER_BOX_ATTACHED, 12 | ATTR_WATER_SHORTAGE, 13 | ) 14 | 15 | from .common import setup_platform 16 | from .mock_data import HOME_DATA 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_registry_entries(hass: HomeAssistant, bypass_api_fixture) -> None: 21 | """Tests devices are registered in the entity registry.""" 22 | mock_config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN) 23 | entity_registry = er.async_get(hass) 24 | 25 | duid = HOME_DATA.devices[0].duid 26 | 27 | entry = entity_registry.async_get("binary_sensor.roborock_s7_maxv_mop_attached") 28 | assert entry.unique_id == f"{ATTR_MOP_ATTACHED}_{duid}" 29 | 30 | entry = entity_registry.async_get( 31 | "binary_sensor.roborock_s7_maxv_water_box_attached" 32 | ) 33 | assert entry.unique_id == f"{ATTR_WATER_BOX_ATTACHED}_{duid}" 34 | 35 | entry = entity_registry.async_get("binary_sensor.roborock_s7_maxv_water_shortage") 36 | assert entry.unique_id == f"{ATTR_WATER_SHORTAGE}_{duid}" 37 | await mock_config_entry.async_unload(hass) 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_mop_attached(hass: HomeAssistant, bypass_api_fixture) -> None: 42 | """Tests mop_attached is getting the correct values.""" 43 | mock_config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN) 44 | state = hass.states.get("binary_sensor.roborock_s7_maxv_mop_attached") 45 | 46 | assert state.state == STATE_ON 47 | assert ( 48 | state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.CONNECTIVITY 49 | ) 50 | await mock_config_entry.async_unload(hass) 51 | 52 | 53 | @pytest.mark.asyncio 54 | async def test_water_box_attached(hass: HomeAssistant, bypass_api_fixture) -> None: 55 | """Tests water_box_attached is getting the correct values.""" 56 | mock_config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN) 57 | state = hass.states.get("binary_sensor.roborock_s7_maxv_water_box_attached") 58 | 59 | assert state.state == STATE_ON 60 | assert ( 61 | state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.CONNECTIVITY 62 | ) 63 | await mock_config_entry.async_unload(hass) 64 | 65 | 66 | @pytest.mark.asyncio 67 | async def test_water_shortage(hass: HomeAssistant, bypass_api_fixture) -> None: 68 | """Tests water_shortage is getting the correct values.""" 69 | mock_config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN) 70 | state = hass.states.get("binary_sensor.roborock_s7_maxv_water_shortage") 71 | 72 | assert state.state == STATE_OFF 73 | assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.PROBLEM 74 | await mock_config_entry.async_unload(hass) 75 | -------------------------------------------------------------------------------- /tests/test_camera.py: -------------------------------------------------------------------------------- 1 | """Tests for Roborock cameras.""" 2 | import pytest 3 | from homeassistant.const import STATE_IDLE 4 | from homeassistant.core import HomeAssistant 5 | from homeassistant.helpers import entity_registry as er 6 | 7 | from custom_components.roborock.const import CAMERA 8 | from .common import setup_platform 9 | from .mock_data import HOME_DATA 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_registry_entries(hass: HomeAssistant, bypass_api_fixture) -> None: 14 | """Tests devices are registered in the entity registry.""" 15 | mock_config_entry = await setup_platform(hass, CAMERA) 16 | entity_registry = er.async_get(hass) 17 | 18 | duid = HOME_DATA.devices[0].duid 19 | 20 | entry = entity_registry.async_get("camera.roborock_s7_maxv_map") 21 | assert entry.unique_id == f"{duid}" 22 | await mock_config_entry.async_unload(hass) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_camera_attributes(hass: HomeAssistant, bypass_api_fixture) -> None: 27 | """Tests camera attributes.""" 28 | mock_config_entry = await setup_platform(hass, CAMERA) 29 | state = hass.states.get("camera.roborock_s7_maxv_map") 30 | 31 | assert state.state == STATE_IDLE 32 | await mock_config_entry.async_unload(hass) 33 | -------------------------------------------------------------------------------- /tests/test_platform.py: -------------------------------------------------------------------------------- 1 | """Test Roborock platform.""" 2 | 3 | import pytest 4 | from homeassistant.core import HomeAssistant 5 | from homeassistant.helpers import entity_registry as er 6 | 7 | from custom_components.roborock import VACUUM 8 | from tests.common import setup_platform 9 | from tests.mock_data import HOME_DATA 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_disable_include_shared(hass: HomeAssistant, bypass_api_fixture) -> None: 14 | """Tests devices are registered in the entity registry.""" 15 | mock_config_entry = await setup_platform(hass, VACUUM, include_shared=False) 16 | entity_registry = er.async_get(hass) 17 | entry = entity_registry.async_get("vacuum.roborock_s7_maxv") 18 | assert entry.unique_id == HOME_DATA.devices[0].duid 19 | 20 | entry = entity_registry.async_get("vacuum.roborock_s7_maxv_shared") 21 | assert entry is None 22 | await mock_config_entry.async_unload(hass) 23 | -------------------------------------------------------------------------------- /tests/test_select.py: -------------------------------------------------------------------------------- 1 | """Test Roborock Select platform.""" 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | from homeassistant.components.select import DOMAIN as SELECT_DOMAIN 6 | from homeassistant.const import SERVICE_SELECT_OPTION 7 | from homeassistant.core import HomeAssistant 8 | from homeassistant.exceptions import HomeAssistantError 9 | from roborock.exceptions import RoborockException 10 | 11 | from tests.common import setup_platform 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_disable_include_shared(hass: HomeAssistant, bypass_api_fixture) -> None: 16 | """Test allowed changing values for select entities.""" 17 | mock_config_entry = await setup_platform(hass, SELECT_DOMAIN) 18 | with patch( 19 | "roborock.local_api.RoborockLocalClient.send_command", 20 | ) as mock_send_message: 21 | await hass.services.async_call( 22 | "select", 23 | SERVICE_SELECT_OPTION, 24 | service_data={"option": "deep"}, 25 | blocking=True, 26 | target={"entity_id": "select.roborock_s7_maxv_mop_mode"}, 27 | ) 28 | # Test mop mode 29 | await hass.services.async_call( 30 | "select", 31 | SERVICE_SELECT_OPTION, 32 | service_data={"option": "deep"}, 33 | blocking=True, 34 | target={"entity_id": "select.roborock_s7_maxv_mop_mode"}, 35 | ) 36 | assert mock_send_message.assert_called_once 37 | 38 | with patch( 39 | "roborock.local_api.RoborockLocalClient.send_command", 40 | ) as mock_send_message: 41 | # Test intensity mode 42 | await hass.services.async_call( 43 | "select", 44 | SERVICE_SELECT_OPTION, 45 | service_data={"option": "mild"}, 46 | blocking=True, 47 | target={"entity_id": "select.roborock_s7_maxv_mop_intensity"}, 48 | ) 49 | assert mock_send_message.assert_called_once 50 | await mock_config_entry.async_unload(hass) 51 | 52 | 53 | @pytest.mark.asyncio 54 | async def test_update_failure( 55 | hass: HomeAssistant, 56 | bypass_api_fixture, 57 | ) -> None: 58 | """Test that changing a value will raise a homeassistanterror when it fails.""" 59 | mock_config_entry = await setup_platform(hass, SELECT_DOMAIN) 60 | with patch( 61 | "roborock.local_api.RoborockLocalClient.send_command", 62 | side_effect=RoborockException(), 63 | ), pytest.raises(HomeAssistantError): 64 | await hass.services.async_call( 65 | "select", 66 | SERVICE_SELECT_OPTION, 67 | service_data={"option": "deep"}, 68 | blocking=True, 69 | target={"entity_id": "select.roborock_s7_maxv_mop_mode"}, 70 | ) 71 | await mock_config_entry.async_unload(hass) 72 | -------------------------------------------------------------------------------- /tests/test_vacuum.py: -------------------------------------------------------------------------------- 1 | """Tests for Roborock vacuums.""" 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | from homeassistant.components.vacuum import ( 6 | ATTR_FAN_SPEED, 7 | ATTR_FAN_SPEED_LIST, 8 | DOMAIN as VACUUM_DOMAIN, 9 | SERVICE_CLEAN_SPOT, 10 | SERVICE_LOCATE, 11 | SERVICE_PAUSE, 12 | SERVICE_RETURN_TO_BASE, 13 | SERVICE_SET_FAN_SPEED, 14 | SERVICE_START, 15 | SERVICE_STOP, 16 | ) 17 | from homeassistant.core import HomeAssistant 18 | from homeassistant.helpers import entity_registry as er 19 | from roborock import RoborockFanSpeedS7MaxV 20 | from roborock.roborock_typing import RoborockCommand 21 | 22 | from .common import setup_platform 23 | from .mock_data import HOME_DATA 24 | 25 | ENTITY_ID = "vacuum.roborock_s7_maxv" 26 | DEVICE_ID = HOME_DATA.devices[0].duid 27 | 28 | 29 | @pytest.mark.asyncio 30 | async def test_registry_entries(hass: HomeAssistant, bypass_api_fixture) -> None: 31 | """Tests devices are registered in the entity registry.""" 32 | mock_config_entry = await setup_platform(hass, VACUUM_DOMAIN) 33 | entity_registry = er.async_get(hass) 34 | entry = entity_registry.async_get(ENTITY_ID) 35 | assert entry.unique_id == DEVICE_ID 36 | await mock_config_entry.async_unload(hass) 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_vacuum_services(hass: HomeAssistant, bypass_api_fixture) -> None: 41 | """Test vacuum services.""" 42 | mock_config_entry = await setup_platform(hass, VACUUM_DOMAIN) 43 | entity_registry = er.async_get(hass) 44 | entity_registry.async_get(ENTITY_ID) 45 | with patch( 46 | "roborock.local_api.RoborockLocalClient.send_command" 47 | ) as mock_local_api_command: 48 | calls = 0 49 | # Test starting 50 | await hass.services.async_call( 51 | VACUUM_DOMAIN, SERVICE_START, {"entity_id": ENTITY_ID}, blocking=True 52 | ) 53 | calls += 1 54 | assert mock_local_api_command.call_count == calls 55 | 56 | # Test stopping 57 | await hass.services.async_call( 58 | VACUUM_DOMAIN, SERVICE_STOP, {"entity_id": ENTITY_ID}, blocking=True 59 | ) 60 | calls += 1 61 | assert mock_local_api_command.call_count == calls 62 | 63 | # Test pausing 64 | await hass.services.async_call( 65 | VACUUM_DOMAIN, SERVICE_PAUSE, {"entity_id": ENTITY_ID}, blocking=True 66 | ) 67 | calls += 1 68 | assert mock_local_api_command.call_count == calls 69 | 70 | # Test return to base 71 | await hass.services.async_call( 72 | VACUUM_DOMAIN, 73 | SERVICE_RETURN_TO_BASE, 74 | {"entity_id": ENTITY_ID}, 75 | blocking=True, 76 | ) 77 | calls += 1 78 | assert mock_local_api_command.call_count == calls 79 | 80 | # Test clean spot 81 | await hass.services.async_call( 82 | VACUUM_DOMAIN, SERVICE_CLEAN_SPOT, {"entity_id": ENTITY_ID}, blocking=True 83 | ) 84 | calls += 1 85 | assert mock_local_api_command.call_count == calls 86 | 87 | # Test locate 88 | await hass.services.async_call( 89 | VACUUM_DOMAIN, SERVICE_LOCATE, {"entity_id": ENTITY_ID}, blocking=True 90 | ) 91 | calls += 1 92 | assert mock_local_api_command.call_count == calls 93 | await mock_config_entry.async_unload(hass) 94 | 95 | 96 | @pytest.mark.asyncio 97 | async def test_vacuum_fan_speeds(hass: HomeAssistant, bypass_api_fixture) -> None: 98 | """Test vacuum fan speeds.""" 99 | mock_config_entry = await setup_platform(hass, VACUUM_DOMAIN) 100 | entity_registry = er.async_get(hass) 101 | entity_registry.async_get(ENTITY_ID) 102 | 103 | state = hass.states.get(ENTITY_ID) 104 | assert state.attributes.get(ATTR_FAN_SPEED) == "balanced" 105 | 106 | fanspeeds = state.attributes.get(ATTR_FAN_SPEED_LIST) 107 | 108 | for speed in RoborockFanSpeedS7MaxV: 109 | assert speed.name in fanspeeds 110 | # Test setting fan speed to "Turbo" 111 | with patch("custom_components.roborock.vacuum.RoborockVacuum.send") as mock_send: 112 | await hass.services.async_call( 113 | VACUUM_DOMAIN, 114 | SERVICE_SET_FAN_SPEED, 115 | {"entity_id": ENTITY_ID, "fan_speed": "Turbo"}, 116 | blocking=True, 117 | ) 118 | mock_send.assert_called_once_with(RoborockCommand.SET_CUSTOM_MODE, []) 119 | await mock_config_entry.async_unload(hass) 120 | --------------------------------------------------------------------------------