initial commit
This commit is contained in:
parent
5266322dfa
commit
24e7b0e228
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Contributions
|
||||||
|
|
||||||
|
Contributions are welcome. If you use HA and a Eufy Vacuum and want to make improvmenets, get in touch.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2022 Brendan McCluskey
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
266
README.md
266
README.md
|
|
@ -1,2 +1,266 @@
|
||||||
# eufy-robovac-hass
|
[](https://github.com/custom-components/hacs)
|
||||||
|
|
||||||
|
[](https://github.com/sponsors/CodeFoodPixels)
|
||||||
|
[](https://ko-fi.com/O5O3O08PA)
|
||||||
|
[](https://paypal.me/CodeFoodPixels)
|
||||||
|
[](https://monzo.me/codefoodpixels)
|
||||||
|
|
||||||
|
# Eufy RobovVac control for Home Assistant
|
||||||
|
|
||||||
|
A brand new version Eufy RoboVac integration for Home Assistant that includes a Config Flow to add your RoboVac(s) and the local key and ID required. All you need to do is enter your Eufy app credentials and the Config Flow will look up the details for you. After the initial config use the configuration button on the Integration to enter the RoboVac IP address when prompted.
|
||||||
|
|
||||||
|
This work has evovled from the original work by Richard Mitchell https://github.com/mitchellrj and the countless others who have contributed over the last couple of years. It also builds on the work done by Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber to get the required local ID and key.
|
||||||
|
|
||||||
|
This project has been forked many times since the I am building upon the original work done by Richard and attempting to simplfy the operation and number of files involved.
|
||||||
|
|
||||||
|
## Installation ##
|
||||||
|
Couple of Pre-reqs
|
||||||
|
1. Make sure your Home Assistant Core is up to date
|
||||||
|
2. Remove any previous Eufy or RoboVac installation including entries in the configuration.yaml
|
||||||
|
|
||||||
|
|
||||||
|
If you want you can clone this repo manually, oterwise use HACS (Recommended).
|
||||||
|
|
||||||
|
### Using HACS
|
||||||
|
1. In HACS add this repo as an integration additional repository.
|
||||||
|
2. Then install it.
|
||||||
|
3. Restart Home Assistant
|
||||||
|
4. Go to the Integrations Page and Click +Add Integration button
|
||||||
|
5. Search for Eufy Robovac and select it
|
||||||
|
6. Enter your Eufy username and password (The ones you use to login to the add with) and submit
|
||||||
|
7. If youve done it correctly you should get a success dialoge and option to enter an Area for each RoboVac you have
|
||||||
|
8. Click Finish
|
||||||
|
9. On the Integrations Screen Locate your Eufy Robovac card and click the configure button
|
||||||
|
10. Select the Radio button beside the Vacuum name and type its IP addess in the box and press Submit
|
||||||
|
(You need to repeat steps 9 and 10 for each RoboVac you have)
|
||||||
|
11. Enjoy
|
||||||
|
|
||||||
|
Please note: You may have to get a new version of the access key for your vacuum from time to time if Eufy change it. Worst case you have to Delete the integration and re add it to get the new key.
|
||||||
|
|
||||||
|
### Optional 1: Scripts
|
||||||
|
|
||||||
|
The integration is designed to work with the standard Home Assistant Lovelace card but that doesnt support all the options of your Robovac. I have created some scripts to send the relevant commands to the Robovac.
|
||||||
|
|
||||||
|
Add the below text to your scripts.yaml file for a xxC RoboVAC. It should be in the same folder as your configuration.yaml
|
||||||
|
```
|
||||||
|
15c_smallroomclean:
|
||||||
|
alias: 15C_smallRoomClean
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: smallRoomClean
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.15c
|
||||||
|
mode: single
|
||||||
|
15c_edgeclean:
|
||||||
|
alias: 15C_edgeClean
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: edgeClean
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.15c
|
||||||
|
mode: single
|
||||||
|
15c_dock:
|
||||||
|
alias: 15C_dock
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.return_to_base
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.15c
|
||||||
|
mode: single
|
||||||
|
```
|
||||||
|
If you have a Gxx add this to your scripts.yaml
|
||||||
|
```
|
||||||
|
g30_autoclean:
|
||||||
|
alias: G30_autoClean
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: autoClean
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.g30
|
||||||
|
mode: single
|
||||||
|
g30_autoreturn:
|
||||||
|
alias: G30_autoReturn
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: autoReturn
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.g30
|
||||||
|
mode: single
|
||||||
|
g30_donotdisturb:
|
||||||
|
alias: G30_do_Not_Disturb
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: doNotDisturb
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.g30
|
||||||
|
mode: single
|
||||||
|
g30_dock:
|
||||||
|
alias: G30_dock
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.return_to_base
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.g30
|
||||||
|
mode: single
|
||||||
|
```
|
||||||
|
If you have an X8 add this to your scripts.yaml
|
||||||
|
```
|
||||||
|
x8_boostiq:
|
||||||
|
alias: x8_boostIQ
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: boostIQ
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.x8
|
||||||
|
mode: single
|
||||||
|
x8_autoclean:
|
||||||
|
alias: x8_autoClean
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: autoClean
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.x8
|
||||||
|
mode: single
|
||||||
|
x8_autoreturn:
|
||||||
|
alias: X8_autoReturn
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: autoReturn
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.x8
|
||||||
|
mode: single
|
||||||
|
x8_donotdisturb:
|
||||||
|
alias: X8_do_Not_Disturb
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.send_command
|
||||||
|
data:
|
||||||
|
command: doNotDisturb
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.x8
|
||||||
|
mode: single
|
||||||
|
x8_dock:
|
||||||
|
alias: X8_dock
|
||||||
|
sequence:
|
||||||
|
- service: vacuum.return_to_base
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.x8
|
||||||
|
mode: single
|
||||||
|
```
|
||||||
|
The facilities in the script options above only work on the those model series. i.e. You cant do edge cleaning on the G30 and you cant do the autoreturn on the 15C.
|
||||||
|
|
||||||
|
### Optional 2 : Lovelace Card
|
||||||
|
|
||||||
|
Search in HACS for the Vacuum Card by Denys Dovhan and install it and configure it in lovelace to use you vacuum. Note there is a minor "feature" in the vacuum card where it doesnt show the correct values in toolbar when they update and there is a template adjusting what is being displayed. A screen refresh shows the correct vaules. Hopefully this will be fixed soon.
|
||||||
|
|
||||||
|
Edit the lovelace vaccum card and add the following to the cards yaml if you have a xxC.
|
||||||
|
```
|
||||||
|
type: custom:vacuum-card
|
||||||
|
entity: vacuum.15c
|
||||||
|
image: default
|
||||||
|
show_name: true
|
||||||
|
show_status: true
|
||||||
|
show_toolbar: true
|
||||||
|
shortcuts:
|
||||||
|
- name: Dock
|
||||||
|
service: script.15c_dock
|
||||||
|
icon: mdi:home-map-marker
|
||||||
|
- name: Edge Cleaning
|
||||||
|
service: script.15c_edgeclean
|
||||||
|
icon: mdi:square-outline
|
||||||
|
- name: Small Room
|
||||||
|
service: script.15c_smallroomclean
|
||||||
|
icon: mdi:timer-cog-outline
|
||||||
|
```
|
||||||
|
Again if you have the Gxx you will add these lines to the cards yaml.
|
||||||
|
```
|
||||||
|
type: custom:vacuum-card
|
||||||
|
entity: vacuum.g30
|
||||||
|
image: default
|
||||||
|
shortcuts:
|
||||||
|
- name: Dock
|
||||||
|
service: script.g30_dock
|
||||||
|
icon: mdi:home-map-marker
|
||||||
|
- name: Auto Clean
|
||||||
|
service: script.g30_autoclean
|
||||||
|
icon: mdi:caps-lock
|
||||||
|
- name: Auto Return
|
||||||
|
service: script.g30_autoreturn
|
||||||
|
icon: mdi:arrow-u-down-left-bold
|
||||||
|
- name: Do Not Disturb
|
||||||
|
service: script.g30_donotdisturb
|
||||||
|
icon: mdi:volume-off
|
||||||
|
stats:
|
||||||
|
default:
|
||||||
|
- attribute: cleaning_area
|
||||||
|
unit: sq meters
|
||||||
|
subtitle: Cleaning Area
|
||||||
|
- attribute: cleaning_time
|
||||||
|
value_template: '{{ (value | float(0) / 60) | round(1) }}'
|
||||||
|
unit: minutes
|
||||||
|
subtitle: Cleaning time
|
||||||
|
- attribute: auto_return
|
||||||
|
subtitle: Auto Ret
|
||||||
|
value_template: '{% if (value == true) %}On{% else %}Off{% endif %}'
|
||||||
|
- attribute: do_not_disturb
|
||||||
|
subtitle: Dnd
|
||||||
|
value_template: '{% if (value == true) %}On{% else %}Off{% endif %}'
|
||||||
|
```
|
||||||
|
Again if you have the X8 you will add these lines to the cards yaml.
|
||||||
|
```
|
||||||
|
type: custom:vacuum-card
|
||||||
|
entity: vacuum.x8
|
||||||
|
image: default
|
||||||
|
stats:
|
||||||
|
default:
|
||||||
|
- attribute: cleaning_area
|
||||||
|
unit: sq meters
|
||||||
|
subtitle: Cleaning Area
|
||||||
|
- attribute: cleaning_time
|
||||||
|
value_template: '{{ (value | float(0) / 60) | round(1) }}'
|
||||||
|
unit: minutes
|
||||||
|
subtitle: Cleaning time
|
||||||
|
- attribute: boost_iq
|
||||||
|
subtitle: Boost IQ
|
||||||
|
value_template: '{% if (value == true) %}On{% else %}Off{% endif %}'
|
||||||
|
- attribute: auto_return
|
||||||
|
subtitle: Auto Ret
|
||||||
|
value_template: '{% if (value == true) %}On{% else %}Off{% endif %}'
|
||||||
|
- attribute: do_not_disturb
|
||||||
|
subtitle: Dnd
|
||||||
|
value_template: '{% if (value == true) %}On{% else %}Off{% endif %}'
|
||||||
|
shortcuts:
|
||||||
|
- name: Dock
|
||||||
|
service: script.x8_dock
|
||||||
|
icon: mdi:home-map-marker
|
||||||
|
- name: Auto Clean
|
||||||
|
service: script.x8_autoclean
|
||||||
|
icon: mdi:caps-lock
|
||||||
|
- name: Boost IQ
|
||||||
|
service: script.x8_boostiq
|
||||||
|
icon: mdi:bootstrap
|
||||||
|
- name: Auto Return
|
||||||
|
service: script.x8_autoreturn
|
||||||
|
icon: mdi:arrow-u-down-left-bold
|
||||||
|
- name: Do Not Disturb
|
||||||
|
service: script.x8_donotdisturb
|
||||||
|
icon: mdi:volume-off
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging ##
|
||||||
|
I have left quite a few debug statements in the code and they may be useful to see whats happening by looking in the System Log files. The Log Viewer Addon available in the Home Assistance store can be very useful to watch the logs being updated in real time. To get the debugging to add to the logs you need to add the below text to your configuration.yaml
|
||||||
|
```
|
||||||
|
logger:
|
||||||
|
default: warning
|
||||||
|
logs:
|
||||||
|
custom_components.robovac.vacuum: debug
|
||||||
|
custom_components.robovac.tuyalocalapi: debug
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright 2022 Brendan McCluskey
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""The Eufy Robovac integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform, CONF_IP_ADDRESS
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from .const import CONF_VACS, DOMAIN
|
||||||
|
|
||||||
|
from .tuyalocaldiscovery import TuyaLocalDiscovery
|
||||||
|
|
||||||
|
PLATFORMS = [Platform.VACUUM, Platform.SENSOR]
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, entry) -> bool:
|
||||||
|
hass.data.setdefault(DOMAIN, {CONF_VACS:{}})
|
||||||
|
|
||||||
|
async def update_device(device):
|
||||||
|
entry = async_get_config_entry_for_device(hass, device["gwId"])
|
||||||
|
|
||||||
|
if entry == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not entry.state.recoverable:
|
||||||
|
return
|
||||||
|
|
||||||
|
hass_data = entry.data.copy()
|
||||||
|
if (
|
||||||
|
device["gwId"] in hass_data[CONF_VACS]
|
||||||
|
and device.get("ip") is not None
|
||||||
|
and hass_data[CONF_VACS][device["gwId"]].get("autodiscovery", True)
|
||||||
|
):
|
||||||
|
if hass_data[CONF_VACS][device["gwId"]][CONF_IP_ADDRESS] != device["ip"]:
|
||||||
|
hass_data[CONF_VACS][device["gwId"]][CONF_IP_ADDRESS] = device["ip"]
|
||||||
|
hass.config_entries.async_update_entry(entry, data=hass_data)
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Updated ip address of {} to {}".format(
|
||||||
|
device["gwId"], device["ip"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tuyalocaldiscovery = TuyaLocalDiscovery(update_device)
|
||||||
|
try:
|
||||||
|
await tuyalocaldiscovery.start()
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, tuyalocaldiscovery.close)
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("failed to set up discovery")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Eufy Robovac from a config entry."""
|
||||||
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||||
|
entry, PLATFORMS
|
||||||
|
):
|
||||||
|
"""Nothing"""
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def update_listener(hass, entry):
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
def async_get_config_entry_for_device(hass, device_id):
|
||||||
|
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
for entry in current_entries:
|
||||||
|
if device_id in entry.data[CONF_VACS]:
|
||||||
|
return entry
|
||||||
|
return None
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
# Copyright 2022 Brendan McCluskey
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Config flow for Eufy Robovac integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any, Optional
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.selector import selector
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
|
CONF_DESCRIPTION,
|
||||||
|
CONF_MAC,
|
||||||
|
CONF_CLIENT_ID,
|
||||||
|
CONF_REGION,
|
||||||
|
CONF_TIME_ZONE,
|
||||||
|
CONF_COUNTRY_CODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .countries import (
|
||||||
|
get_phone_code_by_country_code,
|
||||||
|
get_phone_code_by_region,
|
||||||
|
get_region_by_country_code,
|
||||||
|
get_region_by_phone_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import CONF_AUTODISCOVERY, DOMAIN, CONF_VACS
|
||||||
|
|
||||||
|
from .tuyawebapi import TuyaAPISession
|
||||||
|
from .eufywebapi import EufyLogon
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
USER_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_eufy_vacuums(self):
|
||||||
|
"""Login to Eufy and get the vacuum details"""
|
||||||
|
|
||||||
|
eufy_session = EufyLogon(self["username"], self["password"])
|
||||||
|
response = eufy_session.get_user_info()
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise CannotConnect
|
||||||
|
|
||||||
|
user_response = response.json()
|
||||||
|
if user_response["res_code"] != 1:
|
||||||
|
raise InvalidAuth
|
||||||
|
|
||||||
|
response = eufy_session.get_device_info(
|
||||||
|
user_response["user_info"]["request_host"],
|
||||||
|
user_response["user_info"]["id"],
|
||||||
|
user_response["access_token"],
|
||||||
|
)
|
||||||
|
|
||||||
|
device_response = response.json()
|
||||||
|
|
||||||
|
response = eufy_session.get_user_settings(
|
||||||
|
user_response["user_info"]["request_host"],
|
||||||
|
user_response["user_info"]["id"],
|
||||||
|
user_response["access_token"],
|
||||||
|
)
|
||||||
|
settings_response = response.json()
|
||||||
|
|
||||||
|
self[CONF_CLIENT_ID] = user_response["user_info"]["id"]
|
||||||
|
if (
|
||||||
|
"tuya_home" in settings_response["setting"]["home_setting"]
|
||||||
|
and "tuya_region_code"
|
||||||
|
in settings_response["setting"]["home_setting"]["tuya_home"]
|
||||||
|
):
|
||||||
|
self[CONF_REGION] = settings_response["setting"]["home_setting"]["tuya_home"][
|
||||||
|
"tuya_region_code"
|
||||||
|
]
|
||||||
|
if user_response["user_info"]["phone_code"]:
|
||||||
|
self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"]
|
||||||
|
else:
|
||||||
|
self[CONF_COUNTRY_CODE] = get_phone_code_by_region(self[CONF_REGION])
|
||||||
|
elif user_response["user_info"]["phone_code"]:
|
||||||
|
self[CONF_REGION] = get_region_by_phone_code(
|
||||||
|
user_response["user_info"]["phone_code"]
|
||||||
|
)
|
||||||
|
self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"]
|
||||||
|
elif user_response["user_info"]["country"]:
|
||||||
|
self[CONF_REGION] = get_region_by_country_code(
|
||||||
|
user_response["user_info"]["country"]
|
||||||
|
)
|
||||||
|
self[CONF_COUNTRY_CODE] = get_phone_code_by_country_code(
|
||||||
|
user_response["user_info"]["country"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self[CONF_REGION] = "EU"
|
||||||
|
self[CONF_COUNTRY_CODE] = "44"
|
||||||
|
|
||||||
|
self[CONF_TIME_ZONE] = user_response["user_info"]["timezone"]
|
||||||
|
|
||||||
|
tuya_client = TuyaAPISession(
|
||||||
|
username="eh-" + self[CONF_CLIENT_ID],
|
||||||
|
region=self[CONF_REGION],
|
||||||
|
timezone=self[CONF_TIME_ZONE],
|
||||||
|
phone_code=self[CONF_COUNTRY_CODE],
|
||||||
|
)
|
||||||
|
|
||||||
|
items = device_response["devices"]
|
||||||
|
self[CONF_VACS] = {}
|
||||||
|
for item in items:
|
||||||
|
if item["product"]["appliance"] == "Cleaning":
|
||||||
|
try:
|
||||||
|
device = tuya_client.get_device(item["id"])
|
||||||
|
_LOGGER.debug("Robovac schema: {}".format(device["schema"]))
|
||||||
|
|
||||||
|
vac_details = {
|
||||||
|
CONF_ID: item["id"],
|
||||||
|
CONF_MODEL: item["product"]["product_code"],
|
||||||
|
CONF_NAME: item["alias_name"],
|
||||||
|
CONF_DESCRIPTION: item["name"],
|
||||||
|
CONF_MAC: item["wifi"]["mac"],
|
||||||
|
CONF_IP_ADDRESS: "",
|
||||||
|
CONF_AUTODISCOVERY: True,
|
||||||
|
CONF_ACCESS_TOKEN: device["localKey"],
|
||||||
|
}
|
||||||
|
self[CONF_VACS][item["id"]] = vac_details
|
||||||
|
except:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Vacuum {} found on Eufy, but not on Tuya. Skipping.".format(
|
||||||
|
item["id"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Validate the user input allows us to connect."""
|
||||||
|
await hass.async_add_executor_job(get_eufy_vacuums, data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Eufy Robovac."""
|
||||||
|
|
||||||
|
data: Optional[dict[str, Any]]
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(step_id="user", data_schema=USER_SCHEMA)
|
||||||
|
errors = {}
|
||||||
|
try:
|
||||||
|
unique_id = user_input[CONF_USERNAME]
|
||||||
|
valid_data = await validate_input(self.hass, user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception as e: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception: {}".format(e))
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
# return await self.async_step_repo(valid_data)
|
||||||
|
return self.async_create_entry(title=unique_id, data=valid_data)
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=USER_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Get the options flow for this handler."""
|
||||||
|
return OptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handles options flow for the component."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.selected_vacuum = None
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
self.selected_vacuum = user_input["selected_vacuum"]
|
||||||
|
return await self.async_step_edit()
|
||||||
|
|
||||||
|
vacuums_config = self.config_entry.data[CONF_VACS]
|
||||||
|
vacuum_list = {}
|
||||||
|
for vacuum_id in vacuums_config:
|
||||||
|
vacuum_list[vacuum_id] = vacuums_config[vacuum_id]["name"]
|
||||||
|
|
||||||
|
devices_schema = vol.Schema(
|
||||||
|
{vol.Required("selected_vacuum"): vol.In(vacuum_list)}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init", data_schema=devices_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_edit(self, user_input=None):
|
||||||
|
"""Manage the options for the custom component."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
vacuums = self.config_entry.data[CONF_VACS]
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
updated_vacuums = deepcopy(vacuums)
|
||||||
|
updated_vacuums[self.selected_vacuum][CONF_AUTODISCOVERY] = user_input[
|
||||||
|
CONF_AUTODISCOVERY
|
||||||
|
]
|
||||||
|
if user_input[CONF_IP_ADDRESS]:
|
||||||
|
updated_vacuums[self.selected_vacuum][CONF_IP_ADDRESS] = user_input[
|
||||||
|
CONF_IP_ADDRESS
|
||||||
|
]
|
||||||
|
|
||||||
|
self.hass.config_entries.async_update_entry(
|
||||||
|
self.config_entry,
|
||||||
|
data={CONF_VACS: updated_vacuums},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_create_entry(title="", data={})
|
||||||
|
|
||||||
|
options_schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_AUTODISCOVERY,
|
||||||
|
default=vacuums[self.selected_vacuum].get(CONF_AUTODISCOVERY, True),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
|
default=vacuums[self.selected_vacuum].get(CONF_IP_ADDRESS),
|
||||||
|
): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="edit", data_schema=options_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""Constants for the Eufy Robovac integration."""
|
||||||
|
|
||||||
|
DOMAIN = "robovac"
|
||||||
|
CONF_VACS = "vacuums"
|
||||||
|
CONF_AUTODISCOVERY = "autodiscovery"
|
||||||
|
REFRESH_RATE = 60
|
||||||
|
PING_RATE = 10
|
||||||
|
TIMEOUT = 5
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
COUNTRIES = [
|
||||||
|
{"country_code": "AF", "phone_code": "93", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AL", "phone_code": "355", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DZ", "phone_code": "213", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AO", "phone_code": "244", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AR", "phone_code": "54", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "AM", "phone_code": "374", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AU", "phone_code": "61", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "AT", "phone_code": "43", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AZ", "phone_code": "994", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BH", "phone_code": "973", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BD", "phone_code": "880", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BY", "phone_code": "375", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BE", "phone_code": "32", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BZ", "phone_code": "501", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BJ", "phone_code": "229", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BT", "phone_code": "975", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BO", "phone_code": "591", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "BA", "phone_code": "387", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BW", "phone_code": "267", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BR", "phone_code": "55", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "VG", "phone_code": "1284", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BN", "phone_code": "673", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BG", "phone_code": "359", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BF", "phone_code": "226", "tuya_region": "EU"},
|
||||||
|
{"country_code": "BI", "phone_code": "257", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KH", "phone_code": "855", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CM", "phone_code": "237", "tuya_region": "EU"},
|
||||||
|
{"country_code": "US", "phone_code": "1", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "CA", "phone_code": "1", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "CV", "phone_code": "238", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KY", "phone_code": "1345", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CF", "phone_code": "236", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TD", "phone_code": "235", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CL", "phone_code": "56", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "CN", "phone_code": "86", "tuya_region": "AY"},
|
||||||
|
{"country_code": "CO", "phone_code": "57", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "KM", "phone_code": "269", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CG", "phone_code": "242", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CD", "phone_code": "243", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CR", "phone_code": "506", "tuya_region": "EU"},
|
||||||
|
{"country_code": "HR", "phone_code": "385", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CY", "phone_code": "357", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CZ", "phone_code": "420", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DK", "phone_code": "45", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DJ", "phone_code": "253", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DO", "phone_code": "1809", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DO", "phone_code": "1829", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DO", "phone_code": "1849", "tuya_region": "EU"},
|
||||||
|
{"country_code": "EC", "phone_code": "593", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "EG", "phone_code": "20", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SV", "phone_code": "503", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GQ", "phone_code": "240", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ER", "phone_code": "291", "tuya_region": "EU"},
|
||||||
|
{"country_code": "EE", "phone_code": "372", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ET", "phone_code": "251", "tuya_region": "EU"},
|
||||||
|
{"country_code": "FJ", "phone_code": "679", "tuya_region": "EU"},
|
||||||
|
{"country_code": "FI", "phone_code": "358", "tuya_region": "EU"},
|
||||||
|
{"country_code": "FR", "phone_code": "33", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GA", "phone_code": "241", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GM", "phone_code": "220", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GE", "phone_code": "995", "tuya_region": "EU"},
|
||||||
|
{"country_code": "DE", "phone_code": "49", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GH", "phone_code": "233", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GR", "phone_code": "30", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GL", "phone_code": "299", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GT", "phone_code": "502", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "GN", "phone_code": "224", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GY", "phone_code": "592", "tuya_region": "EU"},
|
||||||
|
{"country_code": "HT", "phone_code": "509", "tuya_region": "EU"},
|
||||||
|
{"country_code": "HN", "phone_code": "504", "tuya_region": "EU"},
|
||||||
|
{"country_code": "HK", "phone_code": "852", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "HU", "phone_code": "36", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IS", "phone_code": "354", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IN", "phone_code": "91", "tuya_region": "IN"},
|
||||||
|
{"country_code": "ID", "phone_code": "62", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "IR", "phone_code": "98", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IQ", "phone_code": "964", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IE", "phone_code": "353", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IM", "phone_code": "44", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IL", "phone_code": "972", "tuya_region": "EU"},
|
||||||
|
{"country_code": "IT", "phone_code": "39", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "CI", "phone_code": "225", "tuya_region": "EU"},
|
||||||
|
{"country_code": "JM", "phone_code": "1876", "tuya_region": "EU"},
|
||||||
|
{"country_code": "JP", "phone_code": "81", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "JO", "phone_code": "962", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KZ", "phone_code": "7", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KE", "phone_code": "254", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KR", "phone_code": "82", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "KW", "phone_code": "965", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KG", "phone_code": "996", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LA", "phone_code": "856", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LV", "phone_code": "371", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LB", "phone_code": "961", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LS", "phone_code": "266", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LR", "phone_code": "231", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LY", "phone_code": "218", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LT", "phone_code": "370", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LU", "phone_code": "352", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MO", "phone_code": "853", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "MK", "phone_code": "389", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MG", "phone_code": "261", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MW", "phone_code": "265", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MY", "phone_code": "60", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "MV", "phone_code": "960", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ML", "phone_code": "223", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MT", "phone_code": "356", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MR", "phone_code": "222", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MU", "phone_code": "230", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MX", "phone_code": "52", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "MD", "phone_code": "373", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MC", "phone_code": "377", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MN", "phone_code": "976", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ME", "phone_code": "382", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MA", "phone_code": "212", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MZ", "phone_code": "258", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MM", "phone_code": "95", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "NA", "phone_code": "264", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NP", "phone_code": "977", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NL", "phone_code": "31", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NZ", "phone_code": "64", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "NI", "phone_code": "505", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "NE", "phone_code": "227", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NG", "phone_code": "234", "tuya_region": "EU"},
|
||||||
|
{"country_code": "KP", "phone_code": "850", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NO", "phone_code": "47", "tuya_region": "EU"},
|
||||||
|
{"country_code": "OM", "phone_code": "968", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PK", "phone_code": "92", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PA", "phone_code": "507", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PY", "phone_code": "595", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "PE", "phone_code": "51", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "PH", "phone_code": "63", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "PL", "phone_code": "48", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PF", "phone_code": "689", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PT", "phone_code": "351", "tuya_region": "EU"},
|
||||||
|
{"country_code": "PR", "phone_code": "1787", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "QA", "phone_code": "974", "tuya_region": "EU"},
|
||||||
|
{"country_code": "RE", "phone_code": "262", "tuya_region": "EU"},
|
||||||
|
{"country_code": "RO", "phone_code": "40", "tuya_region": "EU"},
|
||||||
|
{"country_code": "RU", "phone_code": "7", "tuya_region": "EU"},
|
||||||
|
{"country_code": "RW", "phone_code": "250", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SM", "phone_code": "378", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SA", "phone_code": "966", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SN", "phone_code": "221", "tuya_region": "EU"},
|
||||||
|
{"country_code": "RS", "phone_code": "381", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SL", "phone_code": "232", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SG", "phone_code": "65", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SK", "phone_code": "421", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SI", "phone_code": "386", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SO", "phone_code": "252", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ZA", "phone_code": "27", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ES", "phone_code": "34", "tuya_region": "EU"},
|
||||||
|
{"country_code": "LK", "phone_code": "94", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SD", "phone_code": "249", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SR", "phone_code": "597", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "SZ", "phone_code": "268", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SE", "phone_code": "46", "tuya_region": "EU"},
|
||||||
|
{"country_code": "CH", "phone_code": "41", "tuya_region": "EU"},
|
||||||
|
{"country_code": "SY", "phone_code": "963", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TW", "phone_code": "886", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "TJ", "phone_code": "992", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TZ", "phone_code": "255", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TH", "phone_code": "66", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "TG", "phone_code": "228", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TO", "phone_code": "676", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TT", "phone_code": "1868", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TN", "phone_code": "216", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TR", "phone_code": "90", "tuya_region": "EU"},
|
||||||
|
{"country_code": "TM", "phone_code": "993", "tuya_region": "EU"},
|
||||||
|
{"country_code": "VI", "phone_code": "1340", "tuya_region": "EU"},
|
||||||
|
{"country_code": "UG", "phone_code": "256", "tuya_region": "EU"},
|
||||||
|
{"country_code": "UA", "phone_code": "380", "tuya_region": "EU"},
|
||||||
|
{"country_code": "AE", "phone_code": "971", "tuya_region": "EU"},
|
||||||
|
{"country_code": "GB", "phone_code": "44", "tuya_region": "EU"},
|
||||||
|
{"country_code": "UY", "phone_code": "598", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "UZ", "phone_code": "998", "tuya_region": "EU"},
|
||||||
|
{"country_code": "VA", "phone_code": "379", "tuya_region": "EU"},
|
||||||
|
{"country_code": "VE", "phone_code": "58", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "VN", "phone_code": "84", "tuya_region": "AZ"},
|
||||||
|
{"country_code": "YE", "phone_code": "967", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ZR", "phone_code": "243", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ZM", "phone_code": "260", "tuya_region": "EU"},
|
||||||
|
{"country_code": "ZW", "phone_code": "263", "tuya_region": "EU"},
|
||||||
|
{"country_code": "NCL", "phone_code": "687", "tuya_region": "EU"},
|
||||||
|
{"country_code": "MQ", "phone_code": "596", "tuya_region": "EU"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_region_by_country_code(country_code):
|
||||||
|
country = next(
|
||||||
|
(item for item in COUNTRIES if item["country_code"] == country_code), None
|
||||||
|
)
|
||||||
|
|
||||||
|
if country is None:
|
||||||
|
return "EU"
|
||||||
|
|
||||||
|
return country["tuya_region"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_region_by_phone_code(phone_code):
|
||||||
|
country = next(
|
||||||
|
(item for item in COUNTRIES if item["phone_code"] == phone_code), None
|
||||||
|
)
|
||||||
|
|
||||||
|
if country is None:
|
||||||
|
return "EU"
|
||||||
|
|
||||||
|
return country["tuya_region"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_phone_code_by_region(region):
|
||||||
|
country = next((item for item in COUNTRIES if item["tuya_region"] == region), None)
|
||||||
|
|
||||||
|
if country is None:
|
||||||
|
return "44"
|
||||||
|
|
||||||
|
return country["phone_code"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_phone_code_by_country_code(country_code):
|
||||||
|
country = next(
|
||||||
|
(item for item in COUNTRIES if item["country_code"] == country_code), None
|
||||||
|
)
|
||||||
|
|
||||||
|
if country is None:
|
||||||
|
return "44"
|
||||||
|
|
||||||
|
return country["phone_code"]
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
ERROR_MESSAGES = {
|
||||||
|
"IP_ADDRESS": "IP Address not set",
|
||||||
|
"CONNECTION_FAILED": "Connection to the vacuum failed",
|
||||||
|
"UNSUPPORTED_MODEL": "This model is not supported",
|
||||||
|
"no_error": "None",
|
||||||
|
1:"Front bumper stuck",
|
||||||
|
2:"Wheel stuck",
|
||||||
|
3:"Side brush",
|
||||||
|
4:"Rolling brush bar stuck",
|
||||||
|
5:"Device trapped",
|
||||||
|
6:"Device trapped",
|
||||||
|
7:"Wheel suspended",
|
||||||
|
8:"Low battery",
|
||||||
|
9:"Magnetic boundary",
|
||||||
|
12:"Right wall sensor",
|
||||||
|
13:"Device tilted",
|
||||||
|
14:"Insert dust collector",
|
||||||
|
17:"Restricted area detected",
|
||||||
|
18:"Laser cover stuck",
|
||||||
|
19:"Laser sesor stuck",
|
||||||
|
20:"Laser sensor blocked",
|
||||||
|
21:"Base blocked",
|
||||||
|
"S1":"Battery",
|
||||||
|
"S2":"Wheel Module",
|
||||||
|
"S3":"Side Brush",
|
||||||
|
"S4":"Suction Fan",
|
||||||
|
"S5":"Rolling Brush",
|
||||||
|
"S8":"Path Tracking Sensor",
|
||||||
|
"Wheel_stuck":"Wheel stuck",
|
||||||
|
"R_brush_stuck":"Rolling brush stuck",
|
||||||
|
"Crash_bar_stuck":"Front bumper stuck",
|
||||||
|
"sensor_dirty":"Sensor dirty",
|
||||||
|
"N_enough_pow":"Low battery",
|
||||||
|
"Stuck_5_min":"Device trapped",
|
||||||
|
"Fan_stuck":"Fan stuck",
|
||||||
|
"S_brush_stuck":"Side brush stuck",
|
||||||
|
}
|
||||||
|
|
||||||
|
def getErrorMessage(code):
|
||||||
|
return ERROR_MESSAGES.get(code, code)
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""Original Work from here: Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
eufyheaders = {
|
||||||
|
"User-Agent": "EufyHome-Android-2.4.0",
|
||||||
|
"timezone": "Europe/London",
|
||||||
|
"category": "Home",
|
||||||
|
"token": "",
|
||||||
|
"uid": "",
|
||||||
|
"openudid": "sdk_gphone64_arm64",
|
||||||
|
"clientType": "2",
|
||||||
|
"language": "en",
|
||||||
|
"country": "US",
|
||||||
|
"Accept-Encoding": "gzip",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EufyLogon:
|
||||||
|
def __init__(self, username, password):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def get_user_info(self):
|
||||||
|
login_url = "https://home-api.eufylife.com/v1/user/email/login"
|
||||||
|
login_auth = {
|
||||||
|
"client_Secret": "GQCpr9dSp3uQpsOMgJ4xQ",
|
||||||
|
"client_id": "eufyhome-app",
|
||||||
|
"email": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
return requests.post(login_url, json=login_auth, headers=eufyheaders)
|
||||||
|
|
||||||
|
def get_user_settings(self, url, userid, token):
|
||||||
|
setting_url = url + "/v1/user/setting"
|
||||||
|
eufyheaders["token"] = token
|
||||||
|
eufyheaders["id"] = userid
|
||||||
|
return requests.request("GET", setting_url, headers=eufyheaders, timeout=1.5)
|
||||||
|
|
||||||
|
def get_device_info(self, url, userid, token):
|
||||||
|
device_url = url + "/v1/device/v2"
|
||||||
|
eufyheaders["token"] = token
|
||||||
|
eufyheaders["id"] = userid
|
||||||
|
return requests.request("GET", device_url, headers=eufyheaders)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"domain": "robovac",
|
||||||
|
"name": "Eufy Robovac",
|
||||||
|
"codeowners": ["@codefoodpixels"],
|
||||||
|
"config_flow": true,
|
||||||
|
"dependencies": [],
|
||||||
|
"documentation": "https://github.com/codefoodpixels/robovac",
|
||||||
|
"integration_type": "device",
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"issue_tracker": "https://github.com/codefoodpixels/robovac/issues",
|
||||||
|
"requirements": [],
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from .vacuums.base import RobovacCommand
|
||||||
|
from .tuyalocalapi import TuyaDevice
|
||||||
|
from .vacuums import ROBOVAC_MODELS
|
||||||
|
|
||||||
|
|
||||||
|
class ModelNotSupportedException(Exception):
|
||||||
|
"""This model is not supported"""
|
||||||
|
|
||||||
|
|
||||||
|
class RoboVac(TuyaDevice):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, model_code, *args, **kwargs):
|
||||||
|
if model_code not in ROBOVAC_MODELS:
|
||||||
|
raise ModelNotSupportedException(
|
||||||
|
"Model {} is not supported".format(model_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.model_details = ROBOVAC_MODELS[model_code]
|
||||||
|
super().__init__(self.model_details, *args, **kwargs)
|
||||||
|
|
||||||
|
def getHomeAssistantFeatures(self):
|
||||||
|
return self.model_details.homeassistant_features
|
||||||
|
|
||||||
|
def getRoboVacFeatures(self):
|
||||||
|
return self.model_details.robovac_features
|
||||||
|
|
||||||
|
def getFanSpeeds(self):
|
||||||
|
return self.model_details.commands[RobovacCommand.FAN_SPEED]["values"]
|
||||||
|
|
||||||
|
def getModes(self):
|
||||||
|
return self.model_details.commands[RobovacCommand.MODE]["values"]
|
||||||
|
|
||||||
|
def getSupportedCommands(self):
|
||||||
|
return list(self.model_details.commands.keys())
|
||||||
|
|
||||||
|
def getCommandCodes(self):
|
||||||
|
command_codes = {}
|
||||||
|
for key, value in self.model_details.commands.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
command_codes[key] = str(value["code"])
|
||||||
|
else:
|
||||||
|
command_codes[key] = str(value)
|
||||||
|
|
||||||
|
return command_codes
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import PERCENTAGE, EntityCategory, CONF_NAME, CONF_ID
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
|
||||||
|
from .const import CONF_VACS, DOMAIN, REFRESH_RATE
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BATTERY = "Battery"
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=REFRESH_RATE)
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize my test integration 2 config entry."""
|
||||||
|
vacuums = config_entry.data[CONF_VACS]
|
||||||
|
for item in vacuums:
|
||||||
|
item = vacuums[item]
|
||||||
|
entity = RobovacSensorEntity(item)
|
||||||
|
async_add_entities([entity])
|
||||||
|
|
||||||
|
class RobovacSensorEntity(SensorEntity):
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_device_class = SensorDeviceClass.BATTERY
|
||||||
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
|
_attr_native_unit_of_measurement = PERCENTAGE
|
||||||
|
_attr_available = False
|
||||||
|
|
||||||
|
def __init__(self, item):
|
||||||
|
self.robovac = item
|
||||||
|
self.robovac_id = item[CONF_ID]
|
||||||
|
self._attr_unique_id = item[CONF_ID]
|
||||||
|
self._battery_level = None
|
||||||
|
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, item[CONF_ID])},
|
||||||
|
name=item[CONF_NAME]
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
try:
|
||||||
|
self._battery_level = self.hass.data[DOMAIN][CONF_VACS][self.robovac_id].battery_level
|
||||||
|
self._attr_available = True
|
||||||
|
except:
|
||||||
|
_LOGGER.debug("Failed to get battery level for {}".format(self.robovac_id))
|
||||||
|
self._battery_level = None
|
||||||
|
self._attr_available = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> str | None:
|
||||||
|
"""Return the state."""
|
||||||
|
if self._battery_level is not None:
|
||||||
|
return self._battery_level
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"description": "Enter your Eufy account details"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Manage vacuums",
|
||||||
|
"data": {
|
||||||
|
"selected_vacuum": "Select the Vacuum to edit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit vacuum",
|
||||||
|
"data": {
|
||||||
|
"autodiscovery": "Enable autodiscovery",
|
||||||
|
"ip_address": "IP Address"
|
||||||
|
},
|
||||||
|
"description": "Autodiscovery will automatically update the IP address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"description": "Enter your Eufy account details"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Manage vacuums",
|
||||||
|
"data": {
|
||||||
|
"selected_vacuum": "Select the Vacuum to edit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit vacuum",
|
||||||
|
"data": {
|
||||||
|
"autodiscovery": "Enable autodiscovery",
|
||||||
|
"ip_address": "IP Address"
|
||||||
|
},
|
||||||
|
"description": "Autodiscovery will automatically update the IP address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,929 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2019 Richard Mitchell
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Based on portions of https://github.com/codetheweb/tuyapi/
|
||||||
|
#
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2017 Max Isom
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from typing import Callable, Coroutine
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends.openssl import backend as openssl_backend
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.primitives.hashes import Hash, MD5
|
||||||
|
from cryptography.hazmat.primitives.padding import PKCS7
|
||||||
|
|
||||||
|
from .vacuums.base import RobovacCommand
|
||||||
|
|
||||||
|
INITIAL_BACKOFF = 5
|
||||||
|
INITIAL_QUEUE_TIME = 0.1
|
||||||
|
BACKOFF_MULTIPLIER = 1.70224
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
MESSAGE_PREFIX_FORMAT = ">IIII"
|
||||||
|
MESSAGE_SUFFIX_FORMAT = ">II"
|
||||||
|
MAGIC_PREFIX = 0x000055AA
|
||||||
|
MAGIC_SUFFIX = 0x0000AA55
|
||||||
|
MAGIC_SUFFIX_BYTES = struct.pack(">I", MAGIC_SUFFIX)
|
||||||
|
CRC_32_TABLE = [
|
||||||
|
0x00000000,
|
||||||
|
0x77073096,
|
||||||
|
0xEE0E612C,
|
||||||
|
0x990951BA,
|
||||||
|
0x076DC419,
|
||||||
|
0x706AF48F,
|
||||||
|
0xE963A535,
|
||||||
|
0x9E6495A3,
|
||||||
|
0x0EDB8832,
|
||||||
|
0x79DCB8A4,
|
||||||
|
0xE0D5E91E,
|
||||||
|
0x97D2D988,
|
||||||
|
0x09B64C2B,
|
||||||
|
0x7EB17CBD,
|
||||||
|
0xE7B82D07,
|
||||||
|
0x90BF1D91,
|
||||||
|
0x1DB71064,
|
||||||
|
0x6AB020F2,
|
||||||
|
0xF3B97148,
|
||||||
|
0x84BE41DE,
|
||||||
|
0x1ADAD47D,
|
||||||
|
0x6DDDE4EB,
|
||||||
|
0xF4D4B551,
|
||||||
|
0x83D385C7,
|
||||||
|
0x136C9856,
|
||||||
|
0x646BA8C0,
|
||||||
|
0xFD62F97A,
|
||||||
|
0x8A65C9EC,
|
||||||
|
0x14015C4F,
|
||||||
|
0x63066CD9,
|
||||||
|
0xFA0F3D63,
|
||||||
|
0x8D080DF5,
|
||||||
|
0x3B6E20C8,
|
||||||
|
0x4C69105E,
|
||||||
|
0xD56041E4,
|
||||||
|
0xA2677172,
|
||||||
|
0x3C03E4D1,
|
||||||
|
0x4B04D447,
|
||||||
|
0xD20D85FD,
|
||||||
|
0xA50AB56B,
|
||||||
|
0x35B5A8FA,
|
||||||
|
0x42B2986C,
|
||||||
|
0xDBBBC9D6,
|
||||||
|
0xACBCF940,
|
||||||
|
0x32D86CE3,
|
||||||
|
0x45DF5C75,
|
||||||
|
0xDCD60DCF,
|
||||||
|
0xABD13D59,
|
||||||
|
0x26D930AC,
|
||||||
|
0x51DE003A,
|
||||||
|
0xC8D75180,
|
||||||
|
0xBFD06116,
|
||||||
|
0x21B4F4B5,
|
||||||
|
0x56B3C423,
|
||||||
|
0xCFBA9599,
|
||||||
|
0xB8BDA50F,
|
||||||
|
0x2802B89E,
|
||||||
|
0x5F058808,
|
||||||
|
0xC60CD9B2,
|
||||||
|
0xB10BE924,
|
||||||
|
0x2F6F7C87,
|
||||||
|
0x58684C11,
|
||||||
|
0xC1611DAB,
|
||||||
|
0xB6662D3D,
|
||||||
|
0x76DC4190,
|
||||||
|
0x01DB7106,
|
||||||
|
0x98D220BC,
|
||||||
|
0xEFD5102A,
|
||||||
|
0x71B18589,
|
||||||
|
0x06B6B51F,
|
||||||
|
0x9FBFE4A5,
|
||||||
|
0xE8B8D433,
|
||||||
|
0x7807C9A2,
|
||||||
|
0x0F00F934,
|
||||||
|
0x9609A88E,
|
||||||
|
0xE10E9818,
|
||||||
|
0x7F6A0DBB,
|
||||||
|
0x086D3D2D,
|
||||||
|
0x91646C97,
|
||||||
|
0xE6635C01,
|
||||||
|
0x6B6B51F4,
|
||||||
|
0x1C6C6162,
|
||||||
|
0x856530D8,
|
||||||
|
0xF262004E,
|
||||||
|
0x6C0695ED,
|
||||||
|
0x1B01A57B,
|
||||||
|
0x8208F4C1,
|
||||||
|
0xF50FC457,
|
||||||
|
0x65B0D9C6,
|
||||||
|
0x12B7E950,
|
||||||
|
0x8BBEB8EA,
|
||||||
|
0xFCB9887C,
|
||||||
|
0x62DD1DDF,
|
||||||
|
0x15DA2D49,
|
||||||
|
0x8CD37CF3,
|
||||||
|
0xFBD44C65,
|
||||||
|
0x4DB26158,
|
||||||
|
0x3AB551CE,
|
||||||
|
0xA3BC0074,
|
||||||
|
0xD4BB30E2,
|
||||||
|
0x4ADFA541,
|
||||||
|
0x3DD895D7,
|
||||||
|
0xA4D1C46D,
|
||||||
|
0xD3D6F4FB,
|
||||||
|
0x4369E96A,
|
||||||
|
0x346ED9FC,
|
||||||
|
0xAD678846,
|
||||||
|
0xDA60B8D0,
|
||||||
|
0x44042D73,
|
||||||
|
0x33031DE5,
|
||||||
|
0xAA0A4C5F,
|
||||||
|
0xDD0D7CC9,
|
||||||
|
0x5005713C,
|
||||||
|
0x270241AA,
|
||||||
|
0xBE0B1010,
|
||||||
|
0xC90C2086,
|
||||||
|
0x5768B525,
|
||||||
|
0x206F85B3,
|
||||||
|
0xB966D409,
|
||||||
|
0xCE61E49F,
|
||||||
|
0x5EDEF90E,
|
||||||
|
0x29D9C998,
|
||||||
|
0xB0D09822,
|
||||||
|
0xC7D7A8B4,
|
||||||
|
0x59B33D17,
|
||||||
|
0x2EB40D81,
|
||||||
|
0xB7BD5C3B,
|
||||||
|
0xC0BA6CAD,
|
||||||
|
0xEDB88320,
|
||||||
|
0x9ABFB3B6,
|
||||||
|
0x03B6E20C,
|
||||||
|
0x74B1D29A,
|
||||||
|
0xEAD54739,
|
||||||
|
0x9DD277AF,
|
||||||
|
0x04DB2615,
|
||||||
|
0x73DC1683,
|
||||||
|
0xE3630B12,
|
||||||
|
0x94643B84,
|
||||||
|
0x0D6D6A3E,
|
||||||
|
0x7A6A5AA8,
|
||||||
|
0xE40ECF0B,
|
||||||
|
0x9309FF9D,
|
||||||
|
0x0A00AE27,
|
||||||
|
0x7D079EB1,
|
||||||
|
0xF00F9344,
|
||||||
|
0x8708A3D2,
|
||||||
|
0x1E01F268,
|
||||||
|
0x6906C2FE,
|
||||||
|
0xF762575D,
|
||||||
|
0x806567CB,
|
||||||
|
0x196C3671,
|
||||||
|
0x6E6B06E7,
|
||||||
|
0xFED41B76,
|
||||||
|
0x89D32BE0,
|
||||||
|
0x10DA7A5A,
|
||||||
|
0x67DD4ACC,
|
||||||
|
0xF9B9DF6F,
|
||||||
|
0x8EBEEFF9,
|
||||||
|
0x17B7BE43,
|
||||||
|
0x60B08ED5,
|
||||||
|
0xD6D6A3E8,
|
||||||
|
0xA1D1937E,
|
||||||
|
0x38D8C2C4,
|
||||||
|
0x4FDFF252,
|
||||||
|
0xD1BB67F1,
|
||||||
|
0xA6BC5767,
|
||||||
|
0x3FB506DD,
|
||||||
|
0x48B2364B,
|
||||||
|
0xD80D2BDA,
|
||||||
|
0xAF0A1B4C,
|
||||||
|
0x36034AF6,
|
||||||
|
0x41047A60,
|
||||||
|
0xDF60EFC3,
|
||||||
|
0xA867DF55,
|
||||||
|
0x316E8EEF,
|
||||||
|
0x4669BE79,
|
||||||
|
0xCB61B38C,
|
||||||
|
0xBC66831A,
|
||||||
|
0x256FD2A0,
|
||||||
|
0x5268E236,
|
||||||
|
0xCC0C7795,
|
||||||
|
0xBB0B4703,
|
||||||
|
0x220216B9,
|
||||||
|
0x5505262F,
|
||||||
|
0xC5BA3BBE,
|
||||||
|
0xB2BD0B28,
|
||||||
|
0x2BB45A92,
|
||||||
|
0x5CB36A04,
|
||||||
|
0xC2D7FFA7,
|
||||||
|
0xB5D0CF31,
|
||||||
|
0x2CD99E8B,
|
||||||
|
0x5BDEAE1D,
|
||||||
|
0x9B64C2B0,
|
||||||
|
0xEC63F226,
|
||||||
|
0x756AA39C,
|
||||||
|
0x026D930A,
|
||||||
|
0x9C0906A9,
|
||||||
|
0xEB0E363F,
|
||||||
|
0x72076785,
|
||||||
|
0x05005713,
|
||||||
|
0x95BF4A82,
|
||||||
|
0xE2B87A14,
|
||||||
|
0x7BB12BAE,
|
||||||
|
0x0CB61B38,
|
||||||
|
0x92D28E9B,
|
||||||
|
0xE5D5BE0D,
|
||||||
|
0x7CDCEFB7,
|
||||||
|
0x0BDBDF21,
|
||||||
|
0x86D3D2D4,
|
||||||
|
0xF1D4E242,
|
||||||
|
0x68DDB3F8,
|
||||||
|
0x1FDA836E,
|
||||||
|
0x81BE16CD,
|
||||||
|
0xF6B9265B,
|
||||||
|
0x6FB077E1,
|
||||||
|
0x18B74777,
|
||||||
|
0x88085AE6,
|
||||||
|
0xFF0F6A70,
|
||||||
|
0x66063BCA,
|
||||||
|
0x11010B5C,
|
||||||
|
0x8F659EFF,
|
||||||
|
0xF862AE69,
|
||||||
|
0x616BFFD3,
|
||||||
|
0x166CCF45,
|
||||||
|
0xA00AE278,
|
||||||
|
0xD70DD2EE,
|
||||||
|
0x4E048354,
|
||||||
|
0x3903B3C2,
|
||||||
|
0xA7672661,
|
||||||
|
0xD06016F7,
|
||||||
|
0x4969474D,
|
||||||
|
0x3E6E77DB,
|
||||||
|
0xAED16A4A,
|
||||||
|
0xD9D65ADC,
|
||||||
|
0x40DF0B66,
|
||||||
|
0x37D83BF0,
|
||||||
|
0xA9BCAE53,
|
||||||
|
0xDEBB9EC5,
|
||||||
|
0x47B2CF7F,
|
||||||
|
0x30B5FFE9,
|
||||||
|
0xBDBDF21C,
|
||||||
|
0xCABAC28A,
|
||||||
|
0x53B39330,
|
||||||
|
0x24B4A3A6,
|
||||||
|
0xBAD03605,
|
||||||
|
0xCDD70693,
|
||||||
|
0x54DE5729,
|
||||||
|
0x23D967BF,
|
||||||
|
0xB3667A2E,
|
||||||
|
0xC4614AB8,
|
||||||
|
0x5D681B02,
|
||||||
|
0x2A6F2B94,
|
||||||
|
0xB40BBE37,
|
||||||
|
0xC30C8EA1,
|
||||||
|
0x5A05DF1B,
|
||||||
|
0x2D02EF8D,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TuyaException(Exception):
|
||||||
|
"""Base for Tuya exceptions."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidKey(TuyaException):
|
||||||
|
"""The local key is invalid."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidMessage(TuyaException):
|
||||||
|
"""The message received is invalid."""
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDecodeFailed(TuyaException):
|
||||||
|
"""The message received cannot be decoded as JSON."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionException(TuyaException):
|
||||||
|
"""The socket connection failed."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTimeoutException(ConnectionException):
|
||||||
|
"""The socket connection timed out."""
|
||||||
|
|
||||||
|
|
||||||
|
class RequestResponseCommandMismatch(TuyaException):
|
||||||
|
"""The command in the response didn't match the one from the request."""
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseTimeoutException(TuyaException):
|
||||||
|
"""Did not recieve a response to the request within the timeout"""
|
||||||
|
|
||||||
|
|
||||||
|
class BackoffException(TuyaException):
|
||||||
|
"""Backoff time not reached"""
|
||||||
|
|
||||||
|
|
||||||
|
class TuyaCipher:
|
||||||
|
"""Tuya cryptographic helpers."""
|
||||||
|
|
||||||
|
def __init__(self, key, version):
|
||||||
|
"""Initialize the cipher."""
|
||||||
|
self.version = version
|
||||||
|
self.key = key
|
||||||
|
self.cipher = Cipher(
|
||||||
|
algorithms.AES(key.encode("ascii")), modes.ECB(), backend=openssl_backend
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_prefix_size_and_validate(self, command, encrypted_data):
|
||||||
|
try:
|
||||||
|
version = tuple(map(int, encrypted_data[:3].decode("utf8").split(".")))
|
||||||
|
except ValueError:
|
||||||
|
version = (0, 0)
|
||||||
|
if version != self.version:
|
||||||
|
return 0
|
||||||
|
if version < (3, 3):
|
||||||
|
hash = encrypted_data[3:19].decode("ascii")
|
||||||
|
expected_hash = self.hash(encrypted_data[19:])
|
||||||
|
if hash != expected_hash:
|
||||||
|
return 0
|
||||||
|
return 19
|
||||||
|
else:
|
||||||
|
if command in (Message.SET_COMMAND, Message.GRATUITOUS_UPDATE):
|
||||||
|
_, sequence, __, ___ = struct.unpack_from(">IIIH", encrypted_data, 3)
|
||||||
|
return 15
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def decrypt(self, command, data):
|
||||||
|
prefix_size = self.get_prefix_size_and_validate(command, data)
|
||||||
|
data = data[prefix_size:]
|
||||||
|
decryptor = self.cipher.decryptor()
|
||||||
|
if self.version < (3, 3):
|
||||||
|
data = base64.b64decode(data)
|
||||||
|
decrypted_data = decryptor.update(data)
|
||||||
|
decrypted_data += decryptor.finalize()
|
||||||
|
unpadder = PKCS7(128).unpadder()
|
||||||
|
unpadded_data = unpadder.update(decrypted_data)
|
||||||
|
unpadded_data += unpadder.finalize()
|
||||||
|
|
||||||
|
return unpadded_data
|
||||||
|
|
||||||
|
def encrypt(self, command, data):
|
||||||
|
encrypted_data = b""
|
||||||
|
if data:
|
||||||
|
padder = PKCS7(128).padder()
|
||||||
|
padded_data = padder.update(data)
|
||||||
|
padded_data += padder.finalize()
|
||||||
|
encryptor = self.cipher.encryptor()
|
||||||
|
encrypted_data = encryptor.update(padded_data)
|
||||||
|
encrypted_data += encryptor.finalize()
|
||||||
|
|
||||||
|
prefix = ".".join(map(str, self.version)).encode("utf8")
|
||||||
|
if self.version < (3, 3):
|
||||||
|
payload = base64.b64encode(encrypted_data)
|
||||||
|
hash = self.hash(payload)
|
||||||
|
prefix += hash.encode("utf8")
|
||||||
|
else:
|
||||||
|
payload = encrypted_data
|
||||||
|
if command in (Message.SET_COMMAND, Message.GRATUITOUS_UPDATE):
|
||||||
|
prefix += b"\x00" * 12
|
||||||
|
else:
|
||||||
|
prefix = b""
|
||||||
|
|
||||||
|
return prefix + payload
|
||||||
|
|
||||||
|
def hash(self, data):
|
||||||
|
digest = Hash(MD5(), backend=openssl_backend)
|
||||||
|
to_hash = "data={}||lpv={}||{}".format(
|
||||||
|
data.decode("ascii"), ".".join(map(str, self.version)), self.key
|
||||||
|
)
|
||||||
|
digest.update(to_hash.encode("utf8"))
|
||||||
|
intermediate = digest.finalize().hex()
|
||||||
|
return intermediate[8:24]
|
||||||
|
|
||||||
|
|
||||||
|
def crc(data):
|
||||||
|
"""Calculate the Tuya-flavored CRC of some data."""
|
||||||
|
c = 0xFFFFFFFF
|
||||||
|
for b in data:
|
||||||
|
c = (c >> 8) ^ CRC_32_TABLE[(c ^ b) & 255]
|
||||||
|
|
||||||
|
return c ^ 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
class Message:
|
||||||
|
PING_COMMAND = 0x09
|
||||||
|
GET_COMMAND = 0x0A
|
||||||
|
SET_COMMAND = 0x07
|
||||||
|
GRATUITOUS_UPDATE = 0x08
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
command,
|
||||||
|
payload=None,
|
||||||
|
sequence=None,
|
||||||
|
encrypt=False,
|
||||||
|
device=None,
|
||||||
|
expect_response=True,
|
||||||
|
ttl=5,
|
||||||
|
):
|
||||||
|
if payload is None:
|
||||||
|
payload = b""
|
||||||
|
self.payload = payload
|
||||||
|
self.command = command
|
||||||
|
self.original_sequence = sequence
|
||||||
|
if sequence is None:
|
||||||
|
self.set_sequence()
|
||||||
|
else:
|
||||||
|
self.sequence = sequence
|
||||||
|
self.encrypt = encrypt
|
||||||
|
self.device = device
|
||||||
|
self.expiry = int(time.time()) + ttl
|
||||||
|
self.expect_response = expect_response
|
||||||
|
self.listener = None
|
||||||
|
if expect_response is True:
|
||||||
|
self.listener = asyncio.Semaphore(0)
|
||||||
|
if device is not None:
|
||||||
|
device._listeners[self.sequence] = self.listener
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}({}, {!r}, {!r}, {})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
hex(self.command),
|
||||||
|
self.payload,
|
||||||
|
self.sequence,
|
||||||
|
"<Device {}>".format(self.device) if self.device else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_sequence(self):
|
||||||
|
self.sequence = int(time.perf_counter() * 1000) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
def hex(self):
|
||||||
|
return self.bytes().hex()
|
||||||
|
|
||||||
|
def bytes(self):
|
||||||
|
payload_data = self.payload
|
||||||
|
if isinstance(payload_data, dict):
|
||||||
|
payload_data = json.dumps(payload_data, separators=(",", ":"))
|
||||||
|
if not isinstance(payload_data, bytes):
|
||||||
|
payload_data = payload_data.encode("utf8")
|
||||||
|
|
||||||
|
if self.encrypt:
|
||||||
|
payload_data = self.device.cipher.encrypt(self.command, payload_data)
|
||||||
|
|
||||||
|
payload_size = len(payload_data) + struct.calcsize(MESSAGE_SUFFIX_FORMAT)
|
||||||
|
|
||||||
|
header = struct.pack(
|
||||||
|
MESSAGE_PREFIX_FORMAT,
|
||||||
|
MAGIC_PREFIX,
|
||||||
|
self.sequence,
|
||||||
|
self.command,
|
||||||
|
payload_size,
|
||||||
|
)
|
||||||
|
if self.device and self.device.version >= (3, 3):
|
||||||
|
checksum = crc(header + payload_data)
|
||||||
|
else:
|
||||||
|
checksum = crc(payload_data)
|
||||||
|
footer = struct.pack(MESSAGE_SUFFIX_FORMAT, checksum, MAGIC_SUFFIX)
|
||||||
|
return header + payload_data + footer
|
||||||
|
|
||||||
|
__bytes__ = bytes
|
||||||
|
|
||||||
|
async def async_send(self):
|
||||||
|
await self.device._async_send(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, device, data, cipher=None):
|
||||||
|
try:
|
||||||
|
prefix, sequence, command, payload_size = struct.unpack_from(
|
||||||
|
MESSAGE_PREFIX_FORMAT, data
|
||||||
|
)
|
||||||
|
except struct.error as e:
|
||||||
|
raise InvalidMessage("Invalid message header format.") from e
|
||||||
|
if prefix != MAGIC_PREFIX:
|
||||||
|
raise InvalidMessage("Magic prefix missing from message.")
|
||||||
|
|
||||||
|
# check for an optional return code
|
||||||
|
header_size = struct.calcsize(MESSAGE_PREFIX_FORMAT)
|
||||||
|
try:
|
||||||
|
(return_code,) = struct.unpack_from(">I", data, header_size)
|
||||||
|
except struct.error as e:
|
||||||
|
raise InvalidMessage("Unable to unpack return code.") from e
|
||||||
|
if return_code >> 8:
|
||||||
|
payload_data = data[
|
||||||
|
header_size : header_size
|
||||||
|
+ payload_size
|
||||||
|
- struct.calcsize(MESSAGE_SUFFIX_FORMAT)
|
||||||
|
]
|
||||||
|
return_code = None
|
||||||
|
else:
|
||||||
|
payload_data = data[
|
||||||
|
header_size
|
||||||
|
+ struct.calcsize(">I") : header_size
|
||||||
|
+ payload_size
|
||||||
|
- struct.calcsize(MESSAGE_SUFFIX_FORMAT)
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
expected_crc, suffix = struct.unpack_from(
|
||||||
|
MESSAGE_SUFFIX_FORMAT,
|
||||||
|
data,
|
||||||
|
header_size + payload_size - struct.calcsize(MESSAGE_SUFFIX_FORMAT),
|
||||||
|
)
|
||||||
|
except struct.error as e:
|
||||||
|
raise InvalidMessage("Invalid message suffix format.") from e
|
||||||
|
if suffix != MAGIC_SUFFIX:
|
||||||
|
raise InvalidMessage("Magic suffix missing from message")
|
||||||
|
|
||||||
|
actual_crc = crc(
|
||||||
|
data[: header_size + payload_size - struct.calcsize(MESSAGE_SUFFIX_FORMAT)]
|
||||||
|
)
|
||||||
|
if expected_crc != actual_crc:
|
||||||
|
raise InvalidMessage("CRC check failed")
|
||||||
|
|
||||||
|
payload = None
|
||||||
|
if payload_data:
|
||||||
|
try:
|
||||||
|
payload_data = cipher.decrypt(command, payload_data)
|
||||||
|
except ValueError as e:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
payload_text = payload_data.decode("utf8")
|
||||||
|
except UnicodeDecodeError as e:
|
||||||
|
device._LOGGER.debug(payload_data.hex())
|
||||||
|
device._LOGGER.error(e)
|
||||||
|
raise MessageDecodeFailed() from e
|
||||||
|
try:
|
||||||
|
payload = json.loads(payload_text)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
# data may be encrypted
|
||||||
|
device._LOGGER.debug(payload_data.hex())
|
||||||
|
device._LOGGER.error(e)
|
||||||
|
raise MessageDecodeFailed() from e
|
||||||
|
|
||||||
|
return cls(command, payload, sequence)
|
||||||
|
|
||||||
|
|
||||||
|
class TuyaDevice:
|
||||||
|
"""Represents a generic Tuya device."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model_details,
|
||||||
|
device_id,
|
||||||
|
host,
|
||||||
|
timeout,
|
||||||
|
ping_interval,
|
||||||
|
update_entity_state,
|
||||||
|
local_key=None,
|
||||||
|
port=6668,
|
||||||
|
gateway_id=None,
|
||||||
|
version=(3, 3),
|
||||||
|
):
|
||||||
|
"""Initialize the device."""
|
||||||
|
self._LOGGER = _LOGGER.getChild(device_id)
|
||||||
|
self.model_details = model_details
|
||||||
|
self.device_id = device_id
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
if not gateway_id:
|
||||||
|
gateway_id = self.device_id
|
||||||
|
self.gateway_id = gateway_id
|
||||||
|
self.version = version
|
||||||
|
self.timeout = timeout
|
||||||
|
self.last_pong = 0
|
||||||
|
self.ping_interval = ping_interval
|
||||||
|
self.update_entity_state_cb = update_entity_state
|
||||||
|
|
||||||
|
if len(local_key) != 16:
|
||||||
|
raise InvalidKey("Local key should be a 16-character string")
|
||||||
|
|
||||||
|
self.cipher = TuyaCipher(local_key, self.version)
|
||||||
|
self.writer = None
|
||||||
|
self._response_task = None
|
||||||
|
self._recieve_task = None
|
||||||
|
self._ping_task = None
|
||||||
|
self._handlers: dict[int, Callable[[Message], Coroutine]] = {
|
||||||
|
Message.GRATUITOUS_UPDATE: self.async_gratuitous_update_state,
|
||||||
|
Message.PING_COMMAND: self._async_pong_received,
|
||||||
|
}
|
||||||
|
self._dps = {}
|
||||||
|
self._connected = False
|
||||||
|
self._enabled = True
|
||||||
|
self._queue = []
|
||||||
|
self._listeners = {}
|
||||||
|
self._backoff = False
|
||||||
|
self._queue_interval = INITIAL_QUEUE_TIME
|
||||||
|
self._failures = 0
|
||||||
|
|
||||||
|
asyncio.create_task(self.process_queue())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}({!r}, {!r}, {!r}, {!r})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.device_id,
|
||||||
|
self.host,
|
||||||
|
self.port,
|
||||||
|
self.cipher.key,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} ({}:{})".format(self.device_id, self.host, self.port)
|
||||||
|
|
||||||
|
async def process_queue(self):
|
||||||
|
if self._enabled is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.clean_queue()
|
||||||
|
|
||||||
|
if len(self._queue) > 0:
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"Processing queue. Current length: {}".format(len(self._queue))
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
message = self._queue.pop(0)
|
||||||
|
await message.async_send()
|
||||||
|
self._failures = 0
|
||||||
|
self._queue_interval = INITIAL_QUEUE_TIME
|
||||||
|
self._backoff = False
|
||||||
|
except Exception as e:
|
||||||
|
self._failures += 1
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"{} failures. Most recent: {}".format(self._failures, e)
|
||||||
|
)
|
||||||
|
if self._failures > 3:
|
||||||
|
self._backoff = True
|
||||||
|
self._queue_interval = min(
|
||||||
|
INITIAL_BACKOFF * (BACKOFF_MULTIPLIER ** (self._failures - 4)),
|
||||||
|
600,
|
||||||
|
)
|
||||||
|
self._LOGGER.warn(
|
||||||
|
"{} failures, backing off for {} seconds".format(
|
||||||
|
self._failures, self._queue_interval
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(self._queue_interval)
|
||||||
|
asyncio.create_task(self.process_queue())
|
||||||
|
|
||||||
|
def clean_queue(self):
|
||||||
|
cleaned_queue = []
|
||||||
|
now = int(time.time())
|
||||||
|
for item in self._queue:
|
||||||
|
if item.expiry > now:
|
||||||
|
cleaned_queue.append(item)
|
||||||
|
self._queue = cleaned_queue
|
||||||
|
|
||||||
|
async def async_connect(self):
|
||||||
|
if self._connected is True or self._enabled is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(self.timeout)
|
||||||
|
self._LOGGER.debug("Connecting to {}".format(self))
|
||||||
|
try:
|
||||||
|
sock.connect((self.host, self.port))
|
||||||
|
except (socket.timeout, TimeoutError) as e:
|
||||||
|
self._dps[self.model_details.commands[RobovacCommand.ERROR]] = (
|
||||||
|
"CONNECTION_FAILED"
|
||||||
|
)
|
||||||
|
raise ConnectionTimeoutException("Connection timed out")
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
loop.create_connection
|
||||||
|
self.reader, self.writer = await asyncio.open_connection(sock=sock)
|
||||||
|
self._connected = True
|
||||||
|
|
||||||
|
if self._ping_task is None:
|
||||||
|
self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval))
|
||||||
|
|
||||||
|
asyncio.create_task(self._async_handle_message())
|
||||||
|
|
||||||
|
async def async_disable(self):
|
||||||
|
self._enabled = False
|
||||||
|
|
||||||
|
await self.async_disconnect()
|
||||||
|
|
||||||
|
async def async_disconnect(self):
|
||||||
|
if self._connected is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._LOGGER.debug("Disconnected from {}".format(self))
|
||||||
|
self._connected = False
|
||||||
|
self.last_pong = 0
|
||||||
|
|
||||||
|
if self.writer is not None:
|
||||||
|
self.writer.close()
|
||||||
|
await self.writer.wait_closed()
|
||||||
|
|
||||||
|
if self.reader is not None and not self.reader.at_eof():
|
||||||
|
self.reader.feed_eof()
|
||||||
|
|
||||||
|
async def async_get(self):
|
||||||
|
payload = {"gwId": self.gateway_id, "devId": self.device_id}
|
||||||
|
encrypt = False if self.version < (3, 3) else True
|
||||||
|
message = Message(Message.GET_COMMAND, payload, encrypt=encrypt, device=self)
|
||||||
|
self._queue.append(message)
|
||||||
|
response = await self.async_recieve(message)
|
||||||
|
await self.async_update_state(response)
|
||||||
|
|
||||||
|
async def async_set(self, dps):
|
||||||
|
t = int(time.time())
|
||||||
|
payload = {"devId": self.device_id, "uid": "", "t": t, "dps": dps}
|
||||||
|
message = Message(
|
||||||
|
Message.SET_COMMAND,
|
||||||
|
payload,
|
||||||
|
encrypt=True,
|
||||||
|
device=self,
|
||||||
|
expect_response=False,
|
||||||
|
)
|
||||||
|
self._queue.append(message)
|
||||||
|
|
||||||
|
async def async_ping(self, ping_interval):
|
||||||
|
if self._enabled is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._backoff is True:
|
||||||
|
self._LOGGER.debug("Currently in backoff, not adding ping to queue")
|
||||||
|
else:
|
||||||
|
self.last_ping = time.time()
|
||||||
|
encrypt = False if self.version < (3, 3) else True
|
||||||
|
message = Message(
|
||||||
|
Message.PING_COMMAND,
|
||||||
|
sequence=0,
|
||||||
|
encrypt=encrypt,
|
||||||
|
device=self,
|
||||||
|
expect_response=False,
|
||||||
|
)
|
||||||
|
self._queue.append(message)
|
||||||
|
|
||||||
|
await asyncio.sleep(ping_interval)
|
||||||
|
self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval))
|
||||||
|
if self.last_pong < self.last_ping:
|
||||||
|
await self.async_disconnect()
|
||||||
|
|
||||||
|
async def _async_pong_received(self, message):
|
||||||
|
self.last_pong = time.time()
|
||||||
|
|
||||||
|
async def async_gratuitous_update_state(self, state_message):
|
||||||
|
await self.async_update_state(state_message)
|
||||||
|
await self.update_entity_state_cb()
|
||||||
|
|
||||||
|
async def async_update_state(self, state_message, _=None):
|
||||||
|
if (
|
||||||
|
state_message is not None
|
||||||
|
and state_message.payload
|
||||||
|
and state_message.payload["dps"]
|
||||||
|
):
|
||||||
|
self._dps.update(state_message.payload["dps"])
|
||||||
|
self._LOGGER.debug("Received updated state {}: {}".format(self, self._dps))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return dict(self._dps)
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state_setter(self, new_values):
|
||||||
|
asyncio.create_task(self.async_set(new_values))
|
||||||
|
|
||||||
|
async def _async_handle_message(self):
|
||||||
|
if self._enabled is False or self._connected is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._response_task = asyncio.create_task(
|
||||||
|
self.reader.readuntil(MAGIC_SUFFIX_BYTES)
|
||||||
|
)
|
||||||
|
await self._response_task
|
||||||
|
response_data = self._response_task.result()
|
||||||
|
message = Message.from_bytes(self, response_data, self.cipher)
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, InvalidMessage):
|
||||||
|
self._LOGGER.debug("Invalid message from {}: {}".format(self, e))
|
||||||
|
elif isinstance(e, MessageDecodeFailed):
|
||||||
|
self._LOGGER.debug("Failed to decrypt message from {}".format(self))
|
||||||
|
elif isinstance(e, asyncio.IncompleteReadError):
|
||||||
|
if self._connected:
|
||||||
|
self._LOGGER.debug("Incomplete read")
|
||||||
|
elif isinstance(e, ConnectionResetError):
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"Connection reset: {}\n{}".format(e, traceback.format_exc())
|
||||||
|
)
|
||||||
|
await self.async_disconnect()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._LOGGER.debug("Received message from {}: {}".format(self, message))
|
||||||
|
if message.sequence in self._listeners:
|
||||||
|
sem = self._listeners[message.sequence]
|
||||||
|
if isinstance(sem, asyncio.Semaphore):
|
||||||
|
self._listeners[message.sequence] = message
|
||||||
|
sem.release()
|
||||||
|
else:
|
||||||
|
handler = self._handlers.get(message.command, None)
|
||||||
|
if handler is not None:
|
||||||
|
asyncio.create_task(handler(message))
|
||||||
|
|
||||||
|
self._response_task = None
|
||||||
|
asyncio.create_task(self._async_handle_message())
|
||||||
|
|
||||||
|
async def _async_send(self, message, retries=2):
|
||||||
|
self._LOGGER.debug("Sending to {}: {}".format(self, message))
|
||||||
|
try:
|
||||||
|
await self.async_connect()
|
||||||
|
self.writer.write(message.bytes())
|
||||||
|
await self.writer.drain()
|
||||||
|
except Exception as e:
|
||||||
|
if retries == 0:
|
||||||
|
if isinstance(e, socket.error):
|
||||||
|
await self.async_disconnect()
|
||||||
|
|
||||||
|
raise ConnectionException(
|
||||||
|
"Connection to {} failed: {}".format(self, e)
|
||||||
|
)
|
||||||
|
elif isinstance(e, asyncio.IncompleteReadError):
|
||||||
|
raise InvalidMessage(
|
||||||
|
"Incomplete read from: {} : {}".format(self, e)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise TuyaException("Failed to send data to {}".format(self))
|
||||||
|
|
||||||
|
if isinstance(e, socket.error):
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"Retrying send due to error. Connection to {} failed: {}".format(
|
||||||
|
self, e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(e, asyncio.IncompleteReadError):
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"Retrying send due to error. Incomplete read from: {} : {}. Partial data recieved: {}".format(
|
||||||
|
self, e, e.partial
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._LOGGER.debug(
|
||||||
|
"Retrying send due to error. Failed to send data to {}".format(self)
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0.25)
|
||||||
|
await self._async_send(message, retries=retries - 1)
|
||||||
|
|
||||||
|
async def async_recieve(self, message):
|
||||||
|
if message.expect_response is True:
|
||||||
|
try:
|
||||||
|
self._recieve_task = asyncio.create_task(
|
||||||
|
asyncio.wait_for(message.listener.acquire(), timeout=self.timeout)
|
||||||
|
)
|
||||||
|
await self._recieve_task
|
||||||
|
response = self._listeners.pop(message.sequence)
|
||||||
|
|
||||||
|
if isinstance(response, Exception):
|
||||||
|
raise response
|
||||||
|
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
del self._listeners[message.sequence]
|
||||||
|
await self.async_disconnect()
|
||||||
|
|
||||||
|
if isinstance(e, TimeoutError):
|
||||||
|
raise ResponseTimeoutException(
|
||||||
|
"Timed out waiting for response to sequence number {}".format(
|
||||||
|
message.sequence
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
UDP_KEY = md5(b"yGAdlopoPVldABfn").digest()
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryPortsNotAvailableException(Exception):
|
||||||
|
"""This model is not supported"""
|
||||||
|
|
||||||
|
|
||||||
|
class TuyaLocalDiscovery(asyncio.DatagramProtocol):
|
||||||
|
def __init__(self, callback):
|
||||||
|
self.devices = {}
|
||||||
|
self._listeners = []
|
||||||
|
self.discovered_callback = callback
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
listener = loop.create_datagram_endpoint(
|
||||||
|
lambda: self, local_addr=("0.0.0.0", 6666), reuse_port=True
|
||||||
|
)
|
||||||
|
encrypted_listener = loop.create_datagram_endpoint(
|
||||||
|
lambda: self, local_addr=("0.0.0.0", 6667), reuse_port=True
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._listeners = await asyncio.gather(listener, encrypted_listener)
|
||||||
|
_LOGGER.debug("Listening to broadcasts on UDP port 6666 and 6667")
|
||||||
|
except Exception as e:
|
||||||
|
raise DiscoveryPortsNotAvailableException(
|
||||||
|
"Ports 6666 and 6667 are needed for autodiscovery but are unavailable. This may be due to having the localtuya integration installed and it not allowing other integrations to use the same ports. A pull request has been raised to address this: https://github.com/rospogrigio/localtuya/pull/1481"
|
||||||
|
)
|
||||||
|
|
||||||
|
def close(self, *args, **kwargs):
|
||||||
|
for transport, _ in self._listeners:
|
||||||
|
transport.close()
|
||||||
|
|
||||||
|
def datagram_received(self, data, addr):
|
||||||
|
data = data[20:-8]
|
||||||
|
try:
|
||||||
|
cipher = Cipher(algorithms.AES(UDP_KEY), modes.ECB(), default_backend())
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
padded_data = decryptor.update(data) + decryptor.finalize()
|
||||||
|
data = padded_data[: -ord(padded_data[len(padded_data) - 1 :])]
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
data = data.decode()
|
||||||
|
|
||||||
|
decoded = json.loads(data)
|
||||||
|
asyncio.ensure_future(self.discovered_callback(decoded))
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
"""Original Work from here: Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber"""
|
||||||
|
|
||||||
|
from hashlib import md5, sha256
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends.openssl import backend as openssl_backend
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
import requests
|
||||||
|
|
||||||
|
TUYA_INITIAL_BASE_URL = "https://a1.tuyaeu.com"
|
||||||
|
|
||||||
|
EUFY_HMAC_KEY = (
|
||||||
|
"A_cepev5pfnhua4dkqkdpmnrdxx378mpjr_s8x78u7xwymasd9kqa7a73pjhxqsedaj".encode()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def unpadded_rsa(key_exponent: int, key_n: int, plaintext: bytes) -> bytes:
|
||||||
|
keylength = math.ceil(key_n.bit_length() / 8)
|
||||||
|
input_nr = int.from_bytes(plaintext, byteorder="big")
|
||||||
|
crypted_nr = pow(input_nr, key_exponent, key_n)
|
||||||
|
return crypted_nr.to_bytes(keylength, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
|
def shuffled_md5(value: str) -> str:
|
||||||
|
_hash = md5(value.encode("utf-8")).hexdigest()
|
||||||
|
return _hash[8:16] + _hash[0:8] + _hash[24:32] + _hash[16:24]
|
||||||
|
|
||||||
|
|
||||||
|
TUYA_PASSWORD_INNER_CIPHER = Cipher(
|
||||||
|
algorithms.AES(
|
||||||
|
bytearray(
|
||||||
|
[36, 78, 109, 138, 86, 172, 135, 145, 36, 67, 45, 139, 108, 188, 162, 196]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
modes.CBC(
|
||||||
|
bytearray(
|
||||||
|
[119, 36, 86, 242, 167, 102, 76, 243, 57, 44, 53, 151, 233, 62, 87, 71]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
backend=openssl_backend,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEFAULT_TUYA_HEADERS = {"User-Agent": "TY-UA=APP/Android/2.4.0/SDK/null"}
|
||||||
|
|
||||||
|
SIGNATURE_RELEVANT_PARAMETERS = {
|
||||||
|
"a",
|
||||||
|
"v",
|
||||||
|
"lat",
|
||||||
|
"lon",
|
||||||
|
"lang",
|
||||||
|
"deviceId",
|
||||||
|
"appVersion",
|
||||||
|
"ttid",
|
||||||
|
"isH5",
|
||||||
|
"h5Token",
|
||||||
|
"os",
|
||||||
|
"clientId",
|
||||||
|
"postData",
|
||||||
|
"time",
|
||||||
|
"requestId",
|
||||||
|
"et",
|
||||||
|
"n4h5",
|
||||||
|
"sid",
|
||||||
|
"sp",
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_TUYA_QUERY_PARAMS = {
|
||||||
|
"appVersion": "2.4.0",
|
||||||
|
"deviceId": "",
|
||||||
|
"platform": "sdk_gphone64_arm64",
|
||||||
|
"clientId": "yx5v9uc3ef9wg3v9atje",
|
||||||
|
"lang": "en",
|
||||||
|
"osSystem": "12",
|
||||||
|
"os": "Android",
|
||||||
|
"timeZoneId": "",
|
||||||
|
"ttid": "android",
|
||||||
|
"et": "0.0.1",
|
||||||
|
"sdkVersion": "3.0.8cAnker",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TuyaAPISession:
|
||||||
|
username = None
|
||||||
|
country_code = None
|
||||||
|
session_id = None
|
||||||
|
|
||||||
|
def __init__(self, username, region, timezone, phone_code):
|
||||||
|
self.session = requests.session()
|
||||||
|
self.session.headers = DEFAULT_TUYA_HEADERS.copy()
|
||||||
|
self.default_query_params = DEFAULT_TUYA_QUERY_PARAMS.copy()
|
||||||
|
self.default_query_params["deviceId"] = self.generate_new_device_id()
|
||||||
|
self.username = username
|
||||||
|
self.country_code = phone_code
|
||||||
|
self.base_url = {
|
||||||
|
"AZ": "https://a1.tuyaus.com",
|
||||||
|
"AY": "https://a1.tuyacn.com",
|
||||||
|
"IN": "https://a1.tuyain.com",
|
||||||
|
"EU": "https://a1.tuyaeu.com",
|
||||||
|
}.get(region, "https://a1.tuyaeu.com")
|
||||||
|
|
||||||
|
DEFAULT_TUYA_QUERY_PARAMS["timeZoneId"] = timezone
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_new_device_id():
|
||||||
|
expected_length = 44
|
||||||
|
base64_characters = string.ascii_letters + string.digits
|
||||||
|
device_id_dependent_part = "8534c8ec0ed0"
|
||||||
|
return device_id_dependent_part + "".join(
|
||||||
|
random.choice(base64_characters)
|
||||||
|
for _ in range(expected_length - len(device_id_dependent_part))
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_signature(query_params: dict, encoded_post_data: str):
|
||||||
|
query_params = query_params.copy()
|
||||||
|
if encoded_post_data:
|
||||||
|
query_params["postData"] = encoded_post_data
|
||||||
|
sorted_pairs = sorted(query_params.items())
|
||||||
|
filtered_pairs = filter(
|
||||||
|
lambda p: p[0] and p[0] in SIGNATURE_RELEVANT_PARAMETERS, sorted_pairs
|
||||||
|
)
|
||||||
|
mapped_pairs = map(
|
||||||
|
# postData is pre-emptively hashed (for performance reasons?), everything else is included as-is
|
||||||
|
lambda p: p[0] + "=" + (shuffled_md5(p[1]) if p[0] == "postData" else p[1]),
|
||||||
|
filtered_pairs,
|
||||||
|
)
|
||||||
|
message = "||".join(mapped_pairs)
|
||||||
|
return hmac.HMAC(
|
||||||
|
key=EUFY_HMAC_KEY, msg=message.encode("utf-8"), digestmod=sha256
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
def _request(
|
||||||
|
self,
|
||||||
|
action: str,
|
||||||
|
version="1.0",
|
||||||
|
data: dict = None,
|
||||||
|
query_params: dict = None,
|
||||||
|
_requires_session=True,
|
||||||
|
):
|
||||||
|
if not self.session_id and _requires_session:
|
||||||
|
self.acquire_session()
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
request_id = uuid.uuid4()
|
||||||
|
extra_query_params = {
|
||||||
|
"time": str(int(current_time)),
|
||||||
|
"requestId": str(request_id),
|
||||||
|
"a": action,
|
||||||
|
"v": version,
|
||||||
|
**(query_params or {}),
|
||||||
|
}
|
||||||
|
query_params = {**self.default_query_params, **extra_query_params}
|
||||||
|
encoded_post_data = json.dumps(data, separators=(",", ":")) if data else ""
|
||||||
|
resp = self.session.post(
|
||||||
|
self.base_url + "/api.json",
|
||||||
|
params={
|
||||||
|
**query_params,
|
||||||
|
"sign": self.get_signature(query_params, encoded_post_data),
|
||||||
|
},
|
||||||
|
data={"postData": encoded_post_data} if encoded_post_data else None,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
if "result" not in data:
|
||||||
|
raise Exception(
|
||||||
|
f"No 'result' key in the response - the entire response is {data}."
|
||||||
|
)
|
||||||
|
return data["result"]
|
||||||
|
|
||||||
|
def request_token(self, username, country_code):
|
||||||
|
return self._request(
|
||||||
|
action="tuya.m.user.uid.token.create",
|
||||||
|
data={"uid": username, "countryCode": country_code},
|
||||||
|
_requires_session=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def determine_password(self, username: str):
|
||||||
|
new_uid = username
|
||||||
|
padded_size = 16 * math.ceil(len(new_uid) / 16)
|
||||||
|
password_uid = new_uid.zfill(padded_size)
|
||||||
|
encryptor = TUYA_PASSWORD_INNER_CIPHER.encryptor()
|
||||||
|
encrypted_uid = encryptor.update(password_uid.encode("utf8"))
|
||||||
|
encrypted_uid += encryptor.finalize()
|
||||||
|
return md5(encrypted_uid.hex().upper().encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
def request_session(self, username, password, country_code):
|
||||||
|
token_response = self.request_token(username, country_code)
|
||||||
|
encrypted_password = unpadded_rsa(
|
||||||
|
key_exponent=int(token_response["exponent"]),
|
||||||
|
key_n=int(token_response["publicKey"]),
|
||||||
|
plaintext=password.encode("utf-8"),
|
||||||
|
)
|
||||||
|
data = {
|
||||||
|
"uid": username,
|
||||||
|
"createGroup": True,
|
||||||
|
"ifencrypt": 1,
|
||||||
|
"passwd": encrypted_password.hex(),
|
||||||
|
"countryCode": country_code,
|
||||||
|
"options": '{"group": 1}',
|
||||||
|
"token": token_response["token"],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._request(
|
||||||
|
action="tuya.m.user.uid.password.login.reg",
|
||||||
|
data=data,
|
||||||
|
_requires_session=False,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
error_password = md5("12345678".encode("utf8")).hexdigest()
|
||||||
|
|
||||||
|
if password != error_password:
|
||||||
|
return self.request_session(username, error_password, country_code)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def acquire_session(self):
|
||||||
|
password = self.determine_password(self.username)
|
||||||
|
session_response = self.request_session(
|
||||||
|
self.username, password, self.country_code
|
||||||
|
)
|
||||||
|
self.session_id = self.default_query_params["sid"] = session_response["sid"]
|
||||||
|
self.base_url = session_response["domain"]["mobileApiUrl"]
|
||||||
|
self.country_code = (
|
||||||
|
session_response["phoneCode"]
|
||||||
|
if session_response["phoneCode"]
|
||||||
|
else self.getCountryCode(session_response["domain"]["regionCode"])
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_homes(self):
|
||||||
|
return self._request(action="tuya.m.location.list", version="2.1")
|
||||||
|
|
||||||
|
def get_device(self, devId):
|
||||||
|
return self._request(
|
||||||
|
action="tuya.m.device.get", version="1.0", data={"devId": devId}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,591 @@
|
||||||
|
# Copyright 2022 Brendan McCluskey
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Eufy Robovac sensor platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import ast
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
from enum import IntEnum, StrEnum
|
||||||
|
from homeassistant.loader import bind_hass
|
||||||
|
from homeassistant.components.vacuum import (
|
||||||
|
StateVacuumEntity,
|
||||||
|
STATE_CLEANING,
|
||||||
|
STATE_DOCKED,
|
||||||
|
STATE_ERROR,
|
||||||
|
STATE_IDLE,
|
||||||
|
STATE_RETURNING,
|
||||||
|
STATE_PAUSED
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import (
|
||||||
|
CONNECTION_NETWORK_MAC,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
|
CONF_DESCRIPTION,
|
||||||
|
CONF_MAC,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .vacuums.base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
from .tuyalocalapi import TuyaException
|
||||||
|
from .const import CONF_VACS, DOMAIN, REFRESH_RATE, PING_RATE, TIMEOUT
|
||||||
|
|
||||||
|
from .errors import getErrorMessage
|
||||||
|
from .robovac import (
|
||||||
|
ModelNotSupportedException,
|
||||||
|
RoboVac,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||||
|
|
||||||
|
ATTR_BATTERY_ICON = "battery_icon"
|
||||||
|
ATTR_ERROR = "error"
|
||||||
|
ATTR_FAN_SPEED = "fan_speed"
|
||||||
|
ATTR_FAN_SPEED_LIST = "fan_speed_list"
|
||||||
|
ATTR_STATUS = "status"
|
||||||
|
ATTR_ERROR_CODE = "error_code"
|
||||||
|
ATTR_MODEL_CODE = "model_code"
|
||||||
|
ATTR_CLEANING_AREA = "cleaning_area"
|
||||||
|
ATTR_CLEANING_TIME = "cleaning_time"
|
||||||
|
ATTR_AUTO_RETURN = "auto_return"
|
||||||
|
ATTR_DO_NOT_DISTURB = "do_not_disturb"
|
||||||
|
ATTR_BOOST_IQ = "boost_iq"
|
||||||
|
ATTR_CONSUMABLES = "consumables"
|
||||||
|
ATTR_MODE = "mode"
|
||||||
|
|
||||||
|
MODE_MAPPING = { #152
|
||||||
|
"AggO": "Auto cleaning",
|
||||||
|
"BBoCCAE=": "Start auto",
|
||||||
|
"AggN": "Pause",
|
||||||
|
"AggG": "Stop / Go to charge",
|
||||||
|
"AA==": "Standby"
|
||||||
|
}
|
||||||
|
|
||||||
|
EMPTY_MAPPING = { #173
|
||||||
|
"BBICGAE=": "Empty dust",
|
||||||
|
"BBICIAE=": "Wash mop",
|
||||||
|
"BBICEAE=": "Dry mop"
|
||||||
|
}
|
||||||
|
|
||||||
|
TUYA_STATUS_MAPPING = { #153
|
||||||
|
"BgoAEAUyAA==": "AUTO",
|
||||||
|
"BgoAEAVSAA==": "POSITION",
|
||||||
|
"CAoAEAUyAggB": "PAUSE",
|
||||||
|
"CAoCCAEQBTIA": "ROOM",
|
||||||
|
"CAoCCAEQBVIA": "ROOM_POSITION",
|
||||||
|
"CgoCCAEQBTICCAE=": "ROOM_PAUSE",
|
||||||
|
"CAoCCAIQBTIA": "SPOT",
|
||||||
|
"CAoCCAIQBVIA": "SPOT_POSITION",
|
||||||
|
"CgoCCAIQBTICCAE=": "SPOT_PAUSE",
|
||||||
|
"BAoAEAY=": "START_MANUAL",
|
||||||
|
"BBAHQgA=": "GOING_TO_CHARGE",
|
||||||
|
"BBADGgA=": "CHARGING",
|
||||||
|
"BhADGgIIAQ==": "COMPLETED",
|
||||||
|
"AA==": "STANDBY",
|
||||||
|
"AhAB": "SLEEPING",
|
||||||
|
}
|
||||||
|
|
||||||
|
STATUS_MAPPING = {
|
||||||
|
"AUTO" : "Auto cleaning",
|
||||||
|
"POSITION": "Positioning",
|
||||||
|
"PAUSE": "Cleaning paused",
|
||||||
|
"ROOM": "Cleaning room",
|
||||||
|
"ROOM_POSITION": "Positioning room",
|
||||||
|
"ROOM_PAUSE": "Cleaning room paused",
|
||||||
|
"SPOT": "Spot cleaning",
|
||||||
|
"SPOT_POSITION": "Positioning spot",
|
||||||
|
"SPOT_PAUSE": "Cleaning spot paused",
|
||||||
|
"START_MANUAL": "Manual mode",
|
||||||
|
"GOING_TO_CHARGE": "Recharge",
|
||||||
|
"CHARGING": "Charging",
|
||||||
|
"COMPLETED": "Completed",
|
||||||
|
"STANDBY": "Standby",
|
||||||
|
"SLEEPING": "Sleeping",
|
||||||
|
}
|
||||||
|
|
||||||
|
ERROR_MAPPING = { #177
|
||||||
|
"DAiI6suO9dXszgFSAA==": "no_error",
|
||||||
|
"FAjwudWorOPszgEaAqURUgQSAqUR": "Sidebrush stuck",
|
||||||
|
"FAj+nMu7zuPszgEaAtg2UgQSAtg2": "Robot stuck",
|
||||||
|
"DAjtzbfps+XszgFSAA==": "no_error",
|
||||||
|
"DAiom9rd6eTszgFSAA==": "no_error",
|
||||||
|
"DAia8JTV5OPszgFSAA==": "no_error",
|
||||||
|
"DAj489bWsePszgFSAA==": "no_error",
|
||||||
|
|
||||||
|
# DAjH1er4vtbszgFSAA==
|
||||||
|
# DAi73bTN+uLszgFSAA==
|
||||||
|
# DAj489bWsePszgFSAA==
|
||||||
|
# DAia8JTV5OPszgFSAA==
|
||||||
|
# DAiom9rd6eTszgFSAA==
|
||||||
|
# DAjtzbfps+XszgFSAA==
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=REFRESH_RATE)
|
||||||
|
UPDATE_RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize my test integration 2 config entry."""
|
||||||
|
vacuums = config_entry.data[CONF_VACS]
|
||||||
|
for item in vacuums:
|
||||||
|
item = vacuums[item]
|
||||||
|
entity = RoboVacEntity(item)
|
||||||
|
hass.data[DOMAIN][CONF_VACS][item[CONF_ID]] = entity
|
||||||
|
async_add_entities([entity])
|
||||||
|
|
||||||
|
|
||||||
|
class RoboVacEntity(StateVacuumEntity):
|
||||||
|
"""Eufy Robovac version of a Vacuum entity"""
|
||||||
|
|
||||||
|
_attr_should_poll = True
|
||||||
|
|
||||||
|
_attr_access_token: str | None = None
|
||||||
|
_attr_ip_address: str | None = None
|
||||||
|
_attr_model_code: str | None = None
|
||||||
|
_attr_cleaning_area: str | None = None
|
||||||
|
_attr_cleaning_time: str | None = None
|
||||||
|
_attr_auto_return: str | None = None
|
||||||
|
_attr_do_not_disturb: str | None = None
|
||||||
|
_attr_boost_iq: str | None = None
|
||||||
|
_attr_consumables: str | None = None
|
||||||
|
_attr_mode: str | None = None
|
||||||
|
_attr_robovac_supported: str | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def robovac_supported(self) -> str | None:
|
||||||
|
"""Return the supported features of the vacuum cleaner."""
|
||||||
|
return self._attr_robovac_supported
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self) -> str | None:
|
||||||
|
"""Return the cleaning mode of the vacuum cleaner."""
|
||||||
|
return self._attr_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def consumables(self) -> str | None:
|
||||||
|
"""Return the consumables status of the vacuum cleaner."""
|
||||||
|
return self._attr_consumables
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cleaning_area(self) -> str | None:
|
||||||
|
"""Return the cleaning area of the vacuum cleaner."""
|
||||||
|
return self._attr_cleaning_area
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cleaning_time(self) -> str | None:
|
||||||
|
"""Return the cleaning time of the vacuum cleaner."""
|
||||||
|
return self._attr_cleaning_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_return(self) -> str | None:
|
||||||
|
"""Return the auto_return mode of the vacuum cleaner."""
|
||||||
|
return self._attr_auto_return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def do_not_disturb(self) -> str | None:
|
||||||
|
"""Return the do not disturb mode of the vacuum cleaner."""
|
||||||
|
return self._attr_do_not_disturb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boost_iq(self) -> str | None:
|
||||||
|
"""Return the boost iq mode of the vacuum cleaner."""
|
||||||
|
return self._attr_boost_iq
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_code(self) -> str | None:
|
||||||
|
"""Return the model code of the vacuum cleaner."""
|
||||||
|
return self._attr_model_code
|
||||||
|
|
||||||
|
@property
|
||||||
|
def access_token(self) -> str | None:
|
||||||
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
|
return self._attr_access_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip_address(self) -> str | None:
|
||||||
|
"""Return the ip address of the vacuum cleaner."""
|
||||||
|
return self._attr_ip_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str | None:
|
||||||
|
if self.tuya_state is None:
|
||||||
|
return STATE_UNAVAILABLE
|
||||||
|
elif (
|
||||||
|
type(self.error_code) is not None
|
||||||
|
and self.error_code
|
||||||
|
and self.error_code
|
||||||
|
not in [
|
||||||
|
0,
|
||||||
|
"no_error",
|
||||||
|
]
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"State changed to error. Error message: {}".format(
|
||||||
|
getErrorMessage(self.error_code)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return STATE_ERROR
|
||||||
|
elif self.tuya_state == "Charging" or self.tuya_state == "Completed":
|
||||||
|
return STATE_DOCKED
|
||||||
|
elif self.tuya_state == "Recharge":
|
||||||
|
return STATE_RETURNING
|
||||||
|
elif self.tuya_state == "Sleeping" or self.tuya_state == "Standby":
|
||||||
|
return STATE_IDLE
|
||||||
|
else:
|
||||||
|
return STATE_CLEANING
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return the device-specific state attributes of this vacuum."""
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
|
||||||
|
if type(self.error_code) is not None and self.error_code not in [0, "no_error"]:
|
||||||
|
data[ATTR_ERROR] = getErrorMessage(self.error_code)
|
||||||
|
if (
|
||||||
|
self.robovac_supported & RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
and self.cleaning_area
|
||||||
|
):
|
||||||
|
data[ATTR_CLEANING_AREA] = self.cleaning_area
|
||||||
|
if (
|
||||||
|
self.robovac_supported & RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
and self.cleaning_time
|
||||||
|
):
|
||||||
|
data[ATTR_CLEANING_TIME] = self.cleaning_time
|
||||||
|
if (
|
||||||
|
self.robovac_supported & RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
and self.auto_return
|
||||||
|
):
|
||||||
|
data[ATTR_AUTO_RETURN] = self.auto_return
|
||||||
|
if (
|
||||||
|
self.robovac_supported & RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
and self.do_not_disturb
|
||||||
|
):
|
||||||
|
data[ATTR_DO_NOT_DISTURB] = self.do_not_disturb
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.BOOST_IQ and self.boost_iq:
|
||||||
|
data[ATTR_BOOST_IQ] = self.boost_iq
|
||||||
|
if (
|
||||||
|
self.robovac_supported & RoboVacEntityFeature.CONSUMABLES
|
||||||
|
and self.consumables
|
||||||
|
):
|
||||||
|
data[ATTR_CONSUMABLES] = self.consumables
|
||||||
|
if self.mode:
|
||||||
|
data[ATTR_MODE] = self.mode
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __init__(self, item) -> None:
|
||||||
|
"""Initialize Eufy Robovac"""
|
||||||
|
super().__init__()
|
||||||
|
self._attr_battery_level = 0
|
||||||
|
self._attr_name = item[CONF_NAME]
|
||||||
|
self._attr_unique_id = item[CONF_ID]
|
||||||
|
self._attr_model_code = item[CONF_MODEL]
|
||||||
|
self._attr_ip_address = item[CONF_IP_ADDRESS]
|
||||||
|
self._attr_access_token = item[CONF_ACCESS_TOKEN]
|
||||||
|
|
||||||
|
self.update_failures = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.vacuum = RoboVac(
|
||||||
|
device_id=self.unique_id,
|
||||||
|
host=self.ip_address,
|
||||||
|
local_key=self.access_token,
|
||||||
|
timeout=TIMEOUT,
|
||||||
|
ping_interval=PING_RATE,
|
||||||
|
model_code=self.model_code[0:5],
|
||||||
|
update_entity_state=self.pushed_update_handler,
|
||||||
|
)
|
||||||
|
except ModelNotSupportedException:
|
||||||
|
self.error_code = "UNSUPPORTED_MODEL"
|
||||||
|
|
||||||
|
self._attr_supported_features = self.vacuum.getHomeAssistantFeatures()
|
||||||
|
self._attr_robovac_supported = self.vacuum.getRoboVacFeatures()
|
||||||
|
|
||||||
|
fan_speeds = self.vacuum.getFanSpeeds()
|
||||||
|
self.fan_speed_map = {}
|
||||||
|
|
||||||
|
for speed in fan_speeds:
|
||||||
|
self.fan_speed_map[friendly_text(speed)] = speed
|
||||||
|
|
||||||
|
self._attr_fan_speed_list = list(self.fan_speed_map.keys())
|
||||||
|
_LOGGER.debug(self._attr_fan_speed_list)
|
||||||
|
self._tuya_command_codes = self.vacuum.getCommandCodes()
|
||||||
|
|
||||||
|
self._attr_mode = None
|
||||||
|
self._attr_consumables = None
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, item[CONF_ID])},
|
||||||
|
name=item[CONF_NAME],
|
||||||
|
manufacturer="Eufy",
|
||||||
|
model=item[CONF_DESCRIPTION],
|
||||||
|
connections=[
|
||||||
|
(CONNECTION_NETWORK_MAC, item[CONF_MAC]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.error_code = None
|
||||||
|
self.tuya_state = None
|
||||||
|
self.tuyastatus = None
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
await self.async_forced_update()
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Synchronise state from the vacuum."""
|
||||||
|
try:
|
||||||
|
await self.async_update_vacuum()
|
||||||
|
self.update_failures = 0
|
||||||
|
except TuyaException as e:
|
||||||
|
self.update_failures += 1
|
||||||
|
_LOGGER.warn(
|
||||||
|
"Update errored. Current update failure count: {}. Reason: {}".format(
|
||||||
|
self.update_failures, e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.update_failures >= UPDATE_RETRIES:
|
||||||
|
self.error_code = "CONNECTION_FAILED"
|
||||||
|
|
||||||
|
async def async_update_vacuum(self):
|
||||||
|
if self.error_code == "UNSUPPORTED_MODEL":
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.ip_address == "":
|
||||||
|
self.error_code = "IP_ADDRESS"
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.vacuum.async_get()
|
||||||
|
self.update_entity_values()
|
||||||
|
|
||||||
|
async def async_forced_update(self):
|
||||||
|
await self.async_update_vacuum()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def pushed_update_handler(self):
|
||||||
|
self.update_entity_values()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
def update_entity_values(self):
|
||||||
|
self.tuyastatus = self.vacuum._dps
|
||||||
|
_LOGGER.debug("tuyastatus %s", self.tuyastatus)
|
||||||
|
|
||||||
|
self._attr_battery_level = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.BATTERY]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_battery_level %s", self._attr_battery_level)
|
||||||
|
|
||||||
|
self.tuya_state = STATUS_MAPPING.get(
|
||||||
|
TUYA_STATUS_MAPPING.get(
|
||||||
|
self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.STATUS]
|
||||||
|
), None
|
||||||
|
), None
|
||||||
|
)
|
||||||
|
_LOGGER.debug("tuya_state %s", self.tuya_state)
|
||||||
|
|
||||||
|
self.error_code = ERROR_MAPPING.get(
|
||||||
|
self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.ERROR]
|
||||||
|
), None
|
||||||
|
)
|
||||||
|
_LOGGER.debug("error_code %s", self.error_code)
|
||||||
|
|
||||||
|
self._attr_mode = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.MODE]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_mode %s", self._attr_mode)
|
||||||
|
|
||||||
|
self._attr_fan_speed = friendly_text(
|
||||||
|
self.tuyastatus.get(self._tuya_command_codes[RobovacCommand.FAN_SPEED], "")
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_fan_speed %s", self._attr_fan_speed)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.CLEANING_AREA:
|
||||||
|
self._attr_cleaning_area = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.CLEANING_AREA]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_cleaning_area %s", self._attr_cleaning_area)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.CLEANING_TIME:
|
||||||
|
self._attr_cleaning_time = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.CLEANING_TIME]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_cleaning_time %s", self._attr_cleaning_time)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.AUTO_RETURN:
|
||||||
|
self._attr_auto_return = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.AUTO_RETURN]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_auto_return %s", self._attr_auto_return)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.DO_NOT_DISTURB:
|
||||||
|
self._attr_do_not_disturb = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.DO_NOT_DISTURB]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_do_not_disturb %s", self._attr_do_not_disturb)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.BOOST_IQ:
|
||||||
|
self._attr_boost_iq = self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.BOOST_IQ]
|
||||||
|
)
|
||||||
|
_LOGGER.debug("_attr_boost_iq %s", self._attr_boost_iq)
|
||||||
|
|
||||||
|
if self.robovac_supported & RoboVacEntityFeature.CONSUMABLES:
|
||||||
|
consumables = ast.literal_eval(
|
||||||
|
base64.b64decode(
|
||||||
|
self.tuyastatus.get(
|
||||||
|
self._tuya_command_codes[RobovacCommand.CONSUMABLES]
|
||||||
|
)
|
||||||
|
).decode("ascii")
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Consumables decoded value is: {}".format(consumables))
|
||||||
|
if "consumable" in consumables and "duration" in consumables["consumable"]:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Consumables encoded value is: {}".format(
|
||||||
|
consumables["consumable"]["duration"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._attr_consumables = consumables["consumable"]["duration"]
|
||||||
|
_LOGGER.debug("_attr_consumables %s", self._attr_consumables)
|
||||||
|
|
||||||
|
async def async_locate(self, **kwargs):
|
||||||
|
"""Locate the vacuum cleaner."""
|
||||||
|
_LOGGER.info("Locate Pressed")
|
||||||
|
code = self._tuya_command_codes[RobovacCommand.LOCATE]
|
||||||
|
if self.tuyastatus.get(code):
|
||||||
|
await self.vacuum.async_set({code: False})
|
||||||
|
else:
|
||||||
|
await self.vacuum.async_set({code: True})
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_return_to_base(self, **kwargs):
|
||||||
|
"""Set the vacuum cleaner to return to the dock."""
|
||||||
|
_LOGGER.info("Return home Pressed")
|
||||||
|
await self.vacuum.async_set(
|
||||||
|
{self._tuya_command_codes[RobovacCommand.MODE]: "AggG"}
|
||||||
|
)
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_start(self, **kwargs):
|
||||||
|
await self.vacuum.async_set(
|
||||||
|
{self._tuya_command_codes[RobovacCommand.MODE]: "BBoCCAE="}
|
||||||
|
)
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_pause(self, **kwargs):
|
||||||
|
await self.vacuum.async_set(
|
||||||
|
{self._tuya_command_codes[RobovacCommand.MODE]: "AggN"}
|
||||||
|
)
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_stop(self, **kwargs):
|
||||||
|
await self.async_return_to_base()
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_clean_spot(self, **kwargs):
|
||||||
|
"""Perform a spot clean-up."""
|
||||||
|
_LOGGER.info("Spot Clean Pressed")
|
||||||
|
await self.vacuum.async_set(
|
||||||
|
{self._tuya_command_codes[RobovacCommand.MODE]: "Spot"}
|
||||||
|
)
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||||
|
"""Set fan speed."""
|
||||||
|
_LOGGER.info("Fan Speed Selected")
|
||||||
|
await self.vacuum.async_set(
|
||||||
|
{
|
||||||
|
self._tuya_command_codes[RobovacCommand.FAN_SPEED]: self.fan_speed_map[
|
||||||
|
fan_speed
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_send_command(
|
||||||
|
self, command: str, params: dict | list | None = None, **kwargs
|
||||||
|
) -> None:
|
||||||
|
"""Send a command to a vacuum cleaner."""
|
||||||
|
_LOGGER.info("Send Command %s Pressed", command)
|
||||||
|
if command == "edgeClean":
|
||||||
|
await self.vacuum.async_set({"5": "Edge"})
|
||||||
|
elif command == "smallRoomClean":
|
||||||
|
await self.vacuum.async_set({"5": "SmallRoom"})
|
||||||
|
elif command == "autoClean":
|
||||||
|
await self.vacuum.async_set({"152": "BBoCCAE="})
|
||||||
|
elif command == "autoReturn":
|
||||||
|
if self.auto_return:
|
||||||
|
await self.vacuum.async_set({"135": False})
|
||||||
|
else:
|
||||||
|
await self.vacuum.async_set({"135": True})
|
||||||
|
elif command == "doNotDisturb":
|
||||||
|
if self.do_not_disturb:
|
||||||
|
await self.vacuum.async_set({"139": "MEQ4MDAwMDAw"})
|
||||||
|
await self.vacuum.async_set({"107": False})
|
||||||
|
else:
|
||||||
|
await self.vacuum.async_set({"139": "MTAwMDAwMDAw"})
|
||||||
|
await self.vacuum.async_set({"107": True})
|
||||||
|
elif command == "boostIQ":
|
||||||
|
if self.boost_iq:
|
||||||
|
await self.vacuum.async_set({"118": False})
|
||||||
|
else:
|
||||||
|
await self.vacuum.async_set({"118": True})
|
||||||
|
elif command == "roomClean":
|
||||||
|
roomIds = params.get("roomIds", [1])
|
||||||
|
count = params.get("count", 1)
|
||||||
|
clean_request = {"roomIds": roomIds, "cleanTimes": count}
|
||||||
|
method_call = {
|
||||||
|
"method": "selectRoomsClean",
|
||||||
|
"data": clean_request,
|
||||||
|
"timestamp": round(time.time() * 1000),
|
||||||
|
}
|
||||||
|
json_str = json.dumps(method_call, separators=(",", ":"))
|
||||||
|
base64_str = base64.b64encode(json_str.encode("utf8")).decode("utf8")
|
||||||
|
_LOGGER.info("roomClean call %s", json_str)
|
||||||
|
await self.vacuum.async_set({"124": base64_str})
|
||||||
|
else:
|
||||||
|
await self.vacuum.async_set({command: params.get("value", "")})
|
||||||
|
asyncio.create_task(self.async_forced_update())
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
await self.vacuum.async_disable()
|
||||||
|
|
||||||
|
|
||||||
|
def friendly_text(input):
|
||||||
|
return " ".join(
|
||||||
|
word[0].upper() + word[1:] for word in input.replace("_", " ").split()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T1250:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2103:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2117:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2118:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2119:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2120:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2123:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2128:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2130:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2132:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["No_suction","Standard","Boost_IQ","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2150:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2181:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2182:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2190:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2192:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2193:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2194:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Quiet","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2250:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2251:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2252:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2253:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2254:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2255:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2256:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2257:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2258:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2259:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2261:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Pure","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2262:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Pure","Standard","Turbo","Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.BOOST_IQ: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
|
||||||
|
from .base import RobovacCommand, RoboVacEntityFeature
|
||||||
|
|
||||||
|
|
||||||
|
class T2266:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { # works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN", "AA==", "AggG", "BBoCCAE=", "AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { # works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": [
|
||||||
|
"BgoAEAUyAA===",
|
||||||
|
"BgoAEAVSAA===",
|
||||||
|
"CAoAEAUyAggB",
|
||||||
|
"CAoCCAEQBTIA",
|
||||||
|
"CAoCCAEQBVIA",
|
||||||
|
"CgoCCAEQBTICCAE=",
|
||||||
|
"CAoCCAIQBTIA",
|
||||||
|
"CAoCCAIQBVIA",
|
||||||
|
"CgoCCAIQBTICCAE=",
|
||||||
|
"BAoAEAY=",
|
||||||
|
"BBAHQgA=",
|
||||||
|
"BBADGgA=",
|
||||||
|
"BhADGgIIAQ==",
|
||||||
|
"AA==",
|
||||||
|
"AhAB",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { # untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { # works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, # encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, # encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2267:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2270:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2272:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2273:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
)
|
||||||
|
robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.START_PAUSE: 2,
|
||||||
|
RobovacCommand.DIRECTION: {
|
||||||
|
"code": 3,
|
||||||
|
"values": ["forward", "back", "left", "right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.MODE: {
|
||||||
|
"code": 5,
|
||||||
|
"values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: 15,
|
||||||
|
RobovacCommand.RETURN_HOME: 101,
|
||||||
|
RobovacCommand.FAN_SPEED: {
|
||||||
|
"code": 102,
|
||||||
|
"values": ["Standard","Turbo","Max","Boost_IQ"],
|
||||||
|
},
|
||||||
|
RobovacCommand.LOCATE: 103,
|
||||||
|
RobovacCommand.BATTERY: 104,
|
||||||
|
RobovacCommand.ERROR: 106,
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# RobovacCommand.DO_NOT_DISTURB: 0,
|
||||||
|
# RobovacCommand.CONSUMABLES: 0,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2275:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2276:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2277:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2278:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2320:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from .base import RoboVacEntityFeature, RobovacCommand
|
||||||
|
|
||||||
|
|
||||||
|
class T2351:
|
||||||
|
homeassistant_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
# | VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.PAUSE
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.START
|
||||||
|
| VacuumEntityFeature.STATE
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
# | VacuumEntityFeature.MAP
|
||||||
|
)
|
||||||
|
robovac_features = (
|
||||||
|
# RoboVacEntityFeature.CLEANING_TIME
|
||||||
|
# | RoboVacEntityFeature.CLEANING_AREA
|
||||||
|
RoboVacEntityFeature.DO_NOT_DISTURB
|
||||||
|
# | RoboVacEntityFeature.AUTO_RETURN
|
||||||
|
# | RoboVacEntityFeature.ROOM
|
||||||
|
# | RoboVacEntityFeature.ZONE
|
||||||
|
| RoboVacEntityFeature.BOOST_IQ
|
||||||
|
# | RoboVacEntityFeature.MAP
|
||||||
|
# | RoboVacEntityFeature.CONSUMABLES
|
||||||
|
)
|
||||||
|
commands = {
|
||||||
|
RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested)
|
||||||
|
"code": 152,
|
||||||
|
"values": ["AggN","AA==","AggG","BBoCCAE=","AggO"],
|
||||||
|
},
|
||||||
|
RobovacCommand.STATUS: { #works (status only)
|
||||||
|
"code": 153,
|
||||||
|
"values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"],
|
||||||
|
},
|
||||||
|
RobovacCommand.DIRECTION: { #untested
|
||||||
|
"code": 155,
|
||||||
|
"values": ["Brake", "Forward", "Back", "Left", "Right"],
|
||||||
|
},
|
||||||
|
RobovacCommand.START_PAUSE: 156, # True, False #works (status only)
|
||||||
|
RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested
|
||||||
|
RobovacCommand.FAN_SPEED: { #works (status and update)
|
||||||
|
"code": 158,
|
||||||
|
"values": ["Quiet", "Standard", "Turbo", "Max"],
|
||||||
|
},
|
||||||
|
RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update)
|
||||||
|
RobovacCommand.LOCATE: 160, # True, False #works (status)
|
||||||
|
# Speaker volume: 161 #works, not yet implemented
|
||||||
|
RobovacCommand.BATTERY: 163, # int #works (status)
|
||||||
|
RobovacCommand.CONSUMABLES: 168, #encrypted, not usable
|
||||||
|
RobovacCommand.RETURN_HOME: 173, #encrypted, not usable
|
||||||
|
# FgoQMggKAggBEgIQAToECgIIARICCAE=
|
||||||
|
# FgoQMg4KAggBEggIARj/////DxICCAE=
|
||||||
|
# FAoQMggKAggBEgIQAToECgIIARIA
|
||||||
|
# GAoQMggKAggBEgIQAToECgIIARIECAE4AQ==
|
||||||
|
# GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB
|
||||||
|
RobovacCommand.ERROR: 177, # #encrypted, few known values
|
||||||
|
# SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR"
|
||||||
|
# ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2"
|
||||||
|
|
||||||
|
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ==
|
||||||
|
# IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ==
|
||||||
|
# IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ==
|
||||||
|
|
||||||
|
# These commands need codes adding
|
||||||
|
# RobovacCommand.CLEANING_AREA: 0,
|
||||||
|
# RobovacCommand.CLEANING_TIME: 0,
|
||||||
|
# RobovacCommand.AUTO_RETURN: 0,
|
||||||
|
# Unknown: 151 (true/false)
|
||||||
|
# Unknown: 154
|
||||||
|
# DgoKCgAaAggBIgIIARIA
|
||||||
|
# DAoICgAaAggBIgASAA==
|
||||||
|
# Unknown: 164
|
||||||
|
# MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA==
|
||||||
|
# NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA=
|
||||||
|
# Unknown: 167
|
||||||
|
# FAoAEgcIiEoQbhgEGgcI1EgQbBgC
|
||||||
|
# FgoCEAESBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg==
|
||||||
|
# GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI=
|
||||||
|
# GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw==
|
||||||
|
# Unknown: 171
|
||||||
|
# AhAB
|
||||||
|
# Unknown: 176
|
||||||
|
# MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY=
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG
|
||||||
|
# Unknown: 178
|
||||||
|
# DQjRidjfv9bszgESAR8=
|
||||||
|
# DQiMrPbd+eLszgESAVU=
|
||||||
|
# DQiW0dXL+uLszgESAR8=
|
||||||
|
# Cgiv6NbWsePszgE=
|
||||||
|
# DQjPuorb6eTszgESAR8=
|
||||||
|
# DQjayd7nsOXszgESASg=
|
||||||
|
# Unknown: 179
|
||||||
|
# EBIOKgwIBRACGAEgwYyAtQY=
|
||||||
|
# FhIUEhIIBRABIFsowYyAtQYw74yAtQY=
|
||||||
|
# DhIMKgoIBhgCIPvagrUG
|
||||||
|
# DhIMKgoIBxgCIJLbgrUG
|
||||||
|
# EBIOKgwIBxADGAIg3eyCtQY=
|
||||||
|
# EBIOKgwIBxAEGAIgrPGCtQY=
|
||||||
|
# DhIMKgoICBgCILHxgrUG
|
||||||
|
# DhIMKgoICBADIIj6grUG
|
||||||
|
# DhIMKgoICBADIOqMg7UG
|
||||||
|
# DhIMKgoICBAEIOuMg7UG
|
||||||
|
# DBIKKggICSCljYO1Bg==
|
||||||
|
# DhIMKgoICRACIJmcg7UG
|
||||||
|
# FhIUEhIICRABIBoomZyDtQYw6pyDtQY=
|
||||||
|
# DhIMIgoICRABGO+cg7UG
|
||||||
|
# DhIMIgoICRABGLedg7UG
|
||||||
|
# IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA==
|
||||||
|
# Unknown: 169
|
||||||
|
# cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE=
|
||||||
|
# s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
from .T1250 import T1250
|
||||||
|
from .T2103 import T2103
|
||||||
|
from .T2117 import T2117
|
||||||
|
from .T2118 import T2118
|
||||||
|
from .T2119 import T2119
|
||||||
|
from .T2120 import T2120
|
||||||
|
from .T2123 import T2123
|
||||||
|
from .T2128 import T2128
|
||||||
|
from .T2130 import T2130
|
||||||
|
from .T2132 import T2132
|
||||||
|
from .T2150 import T2150
|
||||||
|
from .T2181 import T2181
|
||||||
|
from .T2182 import T2182
|
||||||
|
from .T2190 import T2190
|
||||||
|
from .T2192 import T2192
|
||||||
|
from .T2193 import T2193
|
||||||
|
from .T2194 import T2194
|
||||||
|
from .T2250 import T2250
|
||||||
|
from .T2251 import T2251
|
||||||
|
from .T2252 import T2252
|
||||||
|
from .T2253 import T2253
|
||||||
|
from .T2254 import T2254
|
||||||
|
from .T2255 import T2255
|
||||||
|
from .T2256 import T2256
|
||||||
|
from .T2257 import T2257
|
||||||
|
from .T2258 import T2258
|
||||||
|
from .T2259 import T2259
|
||||||
|
from .T2261 import T2261
|
||||||
|
from .T2262 import T2262
|
||||||
|
from .T2266 import T2266
|
||||||
|
from .T2267 import T2267
|
||||||
|
from .T2270 import T2270
|
||||||
|
from .T2272 import T2272
|
||||||
|
from .T2273 import T2273
|
||||||
|
from .T2275 import T2275
|
||||||
|
from .T2276 import T2276
|
||||||
|
from .T2277 import T2277
|
||||||
|
from .T2278 import T2278
|
||||||
|
from .T2320 import T2320
|
||||||
|
from .T2351 import T2351
|
||||||
|
|
||||||
|
ROBOVAC_MODELS = {
|
||||||
|
"T2103": T2103,
|
||||||
|
"T2117": T2117,
|
||||||
|
"T2118": T2118,
|
||||||
|
"T2119": T2119,
|
||||||
|
"T2120": T2120,
|
||||||
|
"T2123": T2123,
|
||||||
|
"T2128": T2128,
|
||||||
|
"T2130": T2130,
|
||||||
|
"T2132": T2132,
|
||||||
|
"T1250": T1250,
|
||||||
|
"T2250": T2250,
|
||||||
|
"T2251": T2251,
|
||||||
|
"T2252": T2252,
|
||||||
|
"T2253": T2253,
|
||||||
|
"T2254": T2254,
|
||||||
|
"T2150": T2150,
|
||||||
|
"T2255": T2255,
|
||||||
|
"T2256": T2256,
|
||||||
|
"T2257": T2257,
|
||||||
|
"T2258": T2258,
|
||||||
|
"T2259": T2259,
|
||||||
|
"T2270": T2270,
|
||||||
|
"T2272": T2272,
|
||||||
|
"T2273": T2273,
|
||||||
|
"T2181": T2181,
|
||||||
|
"T2182": T2182,
|
||||||
|
"T2190": T2190,
|
||||||
|
"T2192": T2192,
|
||||||
|
"T2193": T2193,
|
||||||
|
"T2194": T2194,
|
||||||
|
"T2267": T2267,
|
||||||
|
"T2277": T2277,
|
||||||
|
"T2278": T2278,
|
||||||
|
"T2261": T2261,
|
||||||
|
"T2262": T2262,
|
||||||
|
"T2266": T2266,
|
||||||
|
"T2320": T2320,
|
||||||
|
"T2351": T2351,
|
||||||
|
"T2275": T2275,
|
||||||
|
"T2276": T2276,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
from enum import IntEnum, StrEnum
|
||||||
|
|
||||||
|
|
||||||
|
class RoboVacEntityFeature(IntEnum):
|
||||||
|
"""Supported features of the RoboVac entity."""
|
||||||
|
|
||||||
|
EDGE = 1
|
||||||
|
SMALL_ROOM = 2
|
||||||
|
CLEANING_TIME = 4
|
||||||
|
CLEANING_AREA = 8
|
||||||
|
DO_NOT_DISTURB = 16
|
||||||
|
AUTO_RETURN = 32
|
||||||
|
CONSUMABLES = 64
|
||||||
|
ROOM = 128
|
||||||
|
ZONE = 256
|
||||||
|
MAP = 512
|
||||||
|
BOOST_IQ = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class RobovacCommand(StrEnum):
|
||||||
|
START_PAUSE = "start_pause"
|
||||||
|
DIRECTION = "direction"
|
||||||
|
MODE = "mode"
|
||||||
|
STATUS = "status"
|
||||||
|
RETURN_HOME = "return_home"
|
||||||
|
FAN_SPEED = "fan_speed"
|
||||||
|
LOCATE = "locate"
|
||||||
|
BATTERY = "battery"
|
||||||
|
ERROR = "error"
|
||||||
|
CLEANING_AREA = "cleaning_area"
|
||||||
|
CLEANING_TIME = "cleaning_time"
|
||||||
|
AUTO_RETURN = "auto_return"
|
||||||
|
DO_NOT_DISTURB = "do_not_disturb"
|
||||||
|
BOOST_IQ = "boost_iq"
|
||||||
|
CONSUMABLES = "consumables"
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"conventional-changelog-conventionalcommits": "^7.0.2",
|
||||||
|
"semantic-release": "^23.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
cryptography==41.0.4
|
||||||
|
homeassistant==2023.8.4
|
||||||
|
Requests==2.31.0
|
||||||
|
setuptools==68.0.0
|
||||||
|
voluptuous==0.13.1
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2022 Brendan McCluskey
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
dynamic_requires = []
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="robovac",
|
||||||
|
version="1.0",
|
||||||
|
author="Luke Morrigan",
|
||||||
|
url="http://github.com/codefoodpixels/robovac",
|
||||||
|
packages=find_packages(),
|
||||||
|
scripts=[],
|
||||||
|
description="Python API for controlling Eufy Robovac vacuum cleaners",
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: Apache Software License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
],
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue