Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vacuum platform #425

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
6e07a55
First try to add scenes.
ultratoto14 Nov 9, 2020
1006981
Remove unecessary constants.
ultratoto14 Nov 10, 2020
2ca5a15
Merge branch 'master' into light_scenes
ultratoto14 Nov 15, 2020
8d91d92
Merge branch 'master' into light_scenes
ultratoto14 Nov 16, 2020
9be72d1
Update internal state whatever mode is.
ultratoto14 Nov 16, 2020
352ba58
Add RGB only scenes.
ultratoto14 Nov 16, 2020
9ca3efb
Fix brightness issues.
ultratoto14 Nov 17, 2020
26f5e17
Merge branch 'master' into light_scenes
ultratoto14 Nov 17, 2020
457bae1
Follow review comments.
ultratoto14 Nov 17, 2020
92f2372
Merge pull request #146 from ultratoto14/light_scenes
ultratoto14 Nov 18, 2020
4793e09
Cover fake positioning revised (#162)
rospogrigio Nov 18, 2020
3356b3d
Wrong check that enable music mode evry time.
ultratoto14 Nov 18, 2020
1dbae75
Give error if no DPS are found in config flow (#167)
postlund Nov 18, 2020
5ec66f2
Merge branch 'master' into music_mode_fix
ultratoto14 Nov 18, 2020
5eeb4c4
Merge pull request #168 from ultratoto14/music_mode_fix
ultratoto14 Nov 18, 2020
27af622
Store product key in config entry (#170)
postlund Nov 19, 2020
f376d85
Fix broken exception logging (#173)
postlund Nov 20, 2020
5668595
Add missing variable to log point (#174)
postlund Nov 20, 2020
52c62c5
Fix typo of dp_id in fan platform (#178)
postlund Nov 22, 2020
14333b2
Use color picker white point.
ultratoto14 Nov 23, 2020
3ecae04
overzealous check.
ultratoto14 Nov 23, 2020
095f649
Merge pull request #182 from rospogrigio/white_point
ultratoto14 Nov 23, 2020
a74af93
Fix for issue #186 (wrong initialization values for _timer_start and …
rospogrigio Nov 24, 2020
b50ee8b
Properly close discovery listeners (#225)
postlund Dec 7, 2020
3b7e806
Update README.md
cjj25 Nov 28, 2020
1d58abd
Update README.md
cjj25 Nov 30, 2020
10e55e6
Add support for passive devices (#171)
postlund Dec 15, 2020
e7d58be
Add localtuya.set_dp service (#236)
postlund Dec 15, 2020
aeb27a4
Fix improper closing in config flow (#237)
postlund Dec 15, 2020
d0653d4
Fix HA scenes with bulb without color_temp
ultratoto14 Dec 15, 2020
89dabfc
Reduce heartbeat interval.
ultratoto14 Dec 15, 2020
d2a307f
Merge pull request #239 from rospogrigio/tighter_heartbeat
ultratoto14 Dec 15, 2020
3737dec
Merge branch 'master' into light_fixes
ultratoto14 Dec 15, 2020
2d2a621
Merge pull request #238 from rospogrigio/light_fixes
ultratoto14 Dec 15, 2020
c28fac7
This makes lights smoothly fade from one state to a new state, withou…
maartenweyns Dec 15, 2020
a6e8f28
Fixed editor line indentation
maartenweyns Dec 15, 2020
3cf15f2
Merge pull request #240 from maartenweyns/master
ultratoto14 Dec 15, 2020
f72cbc0
Run pylint from tox (#243)
postlund Dec 17, 2020
52bf9cc
Add dependabot config (#249)
postlund Dec 17, 2020
acd70b6
Fix name issue from pylint changes (#252)
postlund Dec 17, 2020
ad0f80f
Bump homeassistant from 2020.12.0 to 2020.12.1 (#244)
dependabot-preview[bot] Dec 17, 2020
f5add5a
Bump codespell from 1.17.1 to 2.0.0
dependabot-preview[bot] Dec 17, 2020
be29474
Bump flake8 from 3.8.3 to 3.8.4
dependabot-preview[bot] Dec 17, 2020
3a49403
Use debug message for timeout.
ultratoto14 Dec 17, 2020
8e1e349
Fix HA scene, one more.
dependabot-preview[bot] Dec 17, 2020
41a1e78
Merge branch 'master' into scene_fix_2
ultratoto14 Dec 17, 2020
6869a0a
Bump mypy from 0.782 to 0.790 (#245)
dependabot-preview[bot] Dec 17, 2020
654c6cd
Support adding and removing entities to devices (#191)
postlund Dec 17, 2020
acf3e6e
Merge branch 'master' into scene_fix_2
ultratoto14 Dec 17, 2020
444ec78
Fix review comment.
ultratoto14 Dec 17, 2020
fd404cb
Merge branch 'master' into timeout_log
ultratoto14 Dec 17, 2020
6cc178f
Merge pull request #256 from rospogrigio/scene_fix_2
ultratoto14 Dec 17, 2020
0518f58
Merge branch 'master' into timeout_log
ultratoto14 Dec 17, 2020
d8de7e3
Merge pull request #255 from rospogrigio/timeout_log
ultratoto14 Dec 17, 2020
94d1c93
Bump homeassistant from 2020.12.1 to 2020.12.2
dependabot-preview[bot] Dec 30, 2020
9bfeb10
Cover fake position persistent (#213)
rospogrigio Dec 30, 2020
397e99f
allow set small / large values for low and high speed for fans
Jan 1, 2021
4b361c4
Merge pull request #290 from itairaz1/allow-small-large-values-for-fan
ultratoto14 Jan 2, 2021
29622f3
Bump homeassistant from 2020.12.2 to 2021.1.0
dependabot-preview[bot] Jan 6, 2021
5637060
Fixed close button disabled with 'none' positioning mode
rospogrigio Jan 6, 2021
e030c38
Bump homeassistant from 2021.1.0 to 2021.1.1
dependabot-preview[bot] Jan 9, 2021
8483cb6
Bump homeassistant from 2021.1.1 to 2021.1.3
dependabot-preview[bot] Jan 15, 2021
496fb1a
Bump homeassistant from 2021.1.3 to 2021.1.4
dependabot-preview[bot] Jan 16, 2021
1a5fd2f
Bump mypy from 0.790 to 0.800
dependabot-preview[bot] Jan 22, 2021
09f6de8
Updated manigest.json adding "version" field
rospogrigio Mar 4, 2021
9a240bc
Swtiching to version 3.2.1 in manifest.json
rospogrigio Mar 4, 2021
66fdc16
Make connection retries every minute (#288)
postlund Mar 5, 2021
57fe963
Bump pylint from 2.6.0 to 2.7.2 (#373)
dependabot-preview[bot] Mar 8, 2021
93b6ad6
Bump mypy from 0.800 to 0.812 (#363)
dependabot-preview[bot] Mar 8, 2021
dd8c669
Merge branch 'master' of https://github.com/unfilet/localtuya into va…
unfilet Mar 24, 2021
2816988
remove space
unfilet Mar 24, 2021
33dd70b
add state variants
unfilet Mar 29, 2021
481ffa8
remove error method; update set fan speed
unfilet Mar 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .dependabot/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: 1
update_configs:
- package_manager: "python"
directory: "/"
update_schedule: "live"
default_reviewers:
- postlund
- rospogrigio
- ultratoto14
default_labels:
- dependencies
default_assignees:
- postlund
- rospogrigio
- ultratoto14
automerged_updates:
- match:
dependency_type: "all"
update_type: "all"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*~
__pycache__
.tox
tuyadebug/
tuyadebug/
.pre-commit-config.yaml
56 changes: 38 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ The following Tuya device types are currently supported:
* Lights
* Covers
* Fans
* Vacuum
* Climates (soon)

Energy monitoring (voltage, current, watts, etc.) is supported for compatible devices.
Energy monitoring (voltage, current, watts, etc.) is supported for compatible devices.

This repository's development has substantially started by utilizing and merging code from NameLessJedi, mileperhour and TradeFace, and then was deeply refactored to provide proper integration with Home Assistant environment, adding config flow and other features. Refer to the "Thanks to" section below.

Expand All @@ -27,6 +28,9 @@ Alternatively, you can install localtuya through HACS by adding this repository.

**NOTE: You must have your Tuya device's Key and ID in order to use localtuya. There are several ways to obtain the localKey depending on your environment and the devices you own. A good place to start getting info is https://github.com/codetheweb/tuyapi/blob/master/docs/SETUP.md .**


**NOTE - Nov 2020: If you plan on integrating these devices on a network that has internet and blocking their internet access, you must block DNS requests too (to the local DNS server eg 192.168.1.1). If you only block outbound internet then the device will sit in zombie state, it will refuse / not respond to any connections with the localkey. Connect the devices first with an active internet connection, grab each device localkey and then implement the block.**

Devices can be configured in two ways:

# 1. YAML config files
Expand All @@ -52,11 +56,11 @@ localtuya:
friendly_name: Device Cover
id: 2
open_close_cmds: ["on_off","open_close"] # Optional, default: "on_off"
positioning_mode: ["none","position","fake"] # Optional, default: "none"
positioning_mode: ["none","position","timed"] # Optional, default: "none"
currpos_dps: 3 # Optional, required only for "position" mode
setpos_dps: 4 # Optional, required only for "position" mode
span_time: 25 # Full movement time: Optional, required only for "fake" mode
span_time: 25 # Full movement time: Optional, required only for "timed" mode

- platform: fan
friendly_name: Device Fan
id: 3
Expand All @@ -67,11 +71,13 @@ localtuya:
color_mode: 21 # Optional, usually 2 or 21, default: "none"
brightness: 22 # Optional, usually 3 or 22, default: "none"
color_temp: 23 # Optional, usually 4 or 23, default: "none"
color: 24 # Optional, usually 5 (RGB_HSV) or 24(HSV), default: "none"
color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none"
brightness_lower: 29 # Optional, usually 0 or 29, default: 29
brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000
color_temp_min_kelvin: 2700 # Optional, default: 2700
color_temp_max_kelvin: 6500 # Optional, default: 6500
scene: 25 # Optional, usually 6 (RGB_HSV) or 25 (HSV), default: "none"
music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False"


- platform: sensor
Expand All @@ -87,8 +93,22 @@ localtuya:
current: 18 # Optional
current_consumption: 19 # Optional
voltage: 20 # Optional

- platform: vacuum
friendly_name: MyVacuum
id: 5
commands_set: "smart,standby,chargego"
commands_dp: 3
idle_status_value: "0"
returning_status_value: "4"
docked_status_value: "5"
battery_dp: 6
cleaning_mode_dp: 3
cleaning_modes: "smart,random,spiral,wall_follow"
fan_speed_dp: 101
fan_speeds: "high,nas,low"
```

Note that a single device can contain several different entities. Some examples:
- a cover device might have 1 (or many) cover entities, plus a switch to control backlight
- a multi-gang switch will contain several switch entities, one for each gang controlled
Expand All @@ -98,12 +118,12 @@ Restart Home Assistant when finished editing.
# 2. Using config flow

Start by going to Configuration - Integration and pressing the "+" button to create a new Integration, then select LocalTuya in the drop-down menu.
Wait for 6 seconds for the scanning of the devices in your LAN. Then, a drop-down menu will appear containing the list of detectes devices: you can
Wait for 6 seconds for the scanning of the devices in your LAN. Then, a drop-down menu will appear containing the list of detectes devices: you can
select one of these, or manually input all the parameters.

![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png)

If you have selected one entry, you just have to input the Friendly Name of the Device, and the localKey.
If you have selected one entry, you just have to input the Friendly Name of the Device, and the localKey.
Once you press "Submit", the connection will be tested to check that everything works, in order to proceed.

![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png)
Expand All @@ -113,8 +133,8 @@ After you have defined all the needed entities leave the "Do not add more entiti

![entity_type](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/3-entity_type.png)

For each entity, the associated DP has to be selected. All the options requiring to select a DP will provide a drop-down menu showing
all the avaliable DPs found on the device (with their current status!!) for an easy identification. Each entity type has different options
For each entity, the associated DP has to be selected. All the options requiring to select a DP will provide a drop-down menu showing
all the avaliable DPs found on the device (with their current status!!) for an easy identification. Each entity type has different options
to be configured, here is an example for the "switch" entity:

![entity](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/4-entity.png)
Expand All @@ -130,23 +150,23 @@ Energy monitoring (voltage, current...) values can be obtained in two different
1) creating individual sensors, each one with the desired name. Note: Voltage and Consumption usually include the first decimal, so 0.1 as "scaling" parameter shall be used in order to get the correct values.
2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption):

```
```
sensor:
- platform: template
sensors:
tuya-sw01_voltage:
value_template: >-
{{ states.switch.sw01.attributes.voltage }}
unit_of_measurement: 'V'
unit_of_measurement: 'V'
tuya-sw01_current:
value_template: >-
value_template: >-
{{ states.switch.sw01.attributes.current }}
unit_of_measurement: 'mA'
unit_of_measurement: 'mA'
tuya-sw01_current_consumption:
value_template: >-
{{ states.switch.sw01.attributes.current_consumption }}
unit_of_measurement: 'W'
```
unit_of_measurement: 'W'
```

# Debugging

Expand All @@ -165,9 +185,9 @@ logger:

# To-do list:

* Create a (good and precise) sensor (counter) for Energy (kWh) -not just Power, but based on it-.
* Create a (good and precise) sensor (counter) for Energy (kWh) -not just Power, but based on it-.
Ideas: Use: https://www.home-assistant.io/components/integration/ and https://www.home-assistant.io/components/utility_meter/

* Everything listed in https://github.com/rospogrigio/localtuya-homeassistant/issues/15

# Thanks to:
Expand Down
130 changes: 106 additions & 24 deletions custom_components/localtuya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
id: 2
commands_set: # Optional, default: "on_off_stop"
["on_off_stop","open_close_stop","fz_zz_stop","1_2_3"]
positioning_mode: ["none","position","fake"] # Optional, default: "none"
positioning_mode: ["none","position","timed"] # Optional, default: "none"
currpos_dp: 3 # Optional, required only for "position" mode
setpos_dp: 4 # Optional, required only for "position" mode
position_inverted: [True,False] # Optional, default: False
span_time: 25 # Full movement time: Optional, required only for "fake" mode
span_time: 25 # Full movement time: Optional, required only for "timed" mode

- platform: fan
friendly_name: Device Fan
Expand Down Expand Up @@ -61,30 +61,51 @@
"""
import asyncio
import logging
from datetime import timedelta

import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_ENTITIES,
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
EVENT_HOMEASSISTANT_STOP,
SERVICE_RELOAD,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.reload import async_integration_yaml_config

from .common import TuyaDevice
from .common import TuyaDevice, async_config_entry_by_device_id
from .config_flow import config_schema
from .const import DATA_DISCOVERY, DOMAIN, TUYA_DEVICE
from .const import CONF_PRODUCT_KEY, DATA_DISCOVERY, DOMAIN, TUYA_DEVICE
from .discovery import TuyaDiscovery

_LOGGER = logging.getLogger(__name__)

UNSUB_LISTENER = "unsub_listener"

RECONNECT_INTERVAL = timedelta(seconds=60)

CONFIG_SCHEMA = config_schema()

CONF_DP = "dp"
CONF_VALUE = "value"

SERVICE_SET_DP = "set_dp"
SERVICE_SET_DP_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(CONF_DP): int,
vol.Required(CONF_VALUE): object,
}
)


@callback
def _async_update_config_entry_if_from_yaml(hass, entries_by_id, conf):
Expand Down Expand Up @@ -122,37 +143,64 @@ async def _handle_reload(service):

await asyncio.gather(*reload_tasks)

def _entry_by_device_id(device_id):
"""Look up config entry by device id."""
current_entries = hass.config_entries.async_entries(DOMAIN)
for entry in current_entries:
if entry.data[CONF_DEVICE_ID] == device_id:
return entry
return None
async def _handle_set_dp(event):
"""Handle set_dp service call."""
entry = async_config_entry_by_device_id(hass, event.data[CONF_DEVICE_ID])
if not entry:
raise HomeAssistantError("unknown device id")

if entry.entry_id not in hass.data[DOMAIN]:
raise HomeAssistantError("device has not been discovered")

device = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE]
if not device.connected:
raise HomeAssistantError("not connected to device")

await device.set_dp(event.data[CONF_VALUE], event.data[CONF_DP])

def _device_discovered(device):
"""Update address of device if it has changed."""
device_ip = device["ip"]
device_id = device["gwId"]
product_key = device["productKey"]

# If device is not in cache, check if a config entry exists
if device_id not in device_cache:
entry = _entry_by_device_id(device_id)
entry = async_config_entry_by_device_id(hass, device_id)
if entry:
# Save address from config entry in cache to trigger
# potential update below
device_cache[device_id] = entry.data[CONF_HOST]

# If device is in cache and address changed...
if device_id in device_cache and device_cache[device_id] != device_ip:
_LOGGER.debug("Device %s changed IP to %s", device_id, device_ip)
if device_id not in device_cache:
return

entry = _entry_by_device_id(device_id)
if entry:
hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_HOST: device_ip}
)
device_cache[device_id] = device_ip
entry = async_config_entry_by_device_id(hass, device_id)
if entry is None:
return

updates = {}

if device_cache[device_id] != device_ip:
updates[CONF_HOST] = device_ip
device_cache[device_id] = device_ip

if entry.data.get(CONF_PRODUCT_KEY) != product_key:
updates[CONF_PRODUCT_KEY] = product_key

# Update settings if something changed, otherwise try to connect. Updating
# settings triggers a reload of the config entry, which tears down the device
# so no need to connect in that case.
if updates:
_LOGGER.debug("Update keys for device %s: %s", device_id, updates)
hass.config_entries.async_update_entry(
entry, data={**entry.data, **updates}
)
elif entry.entry_id in hass.data[DOMAIN]:
_LOGGER.debug("Device %s found with IP %s", device_id, device_ip)

device = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE]
device.async_connect()

discovery = TuyaDiscovery(_device_discovered)

Expand All @@ -164,15 +212,31 @@ def _shutdown(event):
await discovery.start()
hass.data[DOMAIN][DATA_DISCOVERY] = discovery
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
except Exception:
except Exception: # pylint: disable=broad-except
_LOGGER.exception("failed to set up discovery")

async def _async_reconnect(now):
"""Try connecting to devices not already connected to."""
for entry_id, value in hass.data[DOMAIN].items():
if entry_id == DATA_DISCOVERY:
continue

device = value[TUYA_DEVICE]
if not device.connected:
device.async_connect()

async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL)

hass.helpers.service.async_register_admin_service(
DOMAIN,
SERVICE_RELOAD,
_handle_reload,
)

hass.helpers.service.async_register_admin_service(
DOMAIN, SERVICE_SET_DP, _handle_set_dp, schema=SERVICE_SET_DP_SCHEMA
)

for host_config in config.get(DOMAIN, []):
hass.async_create_task(
hass.config_entries.flow.async_init(
Expand Down Expand Up @@ -202,7 +266,9 @@ async def setup_entities():
for platform in platforms
]
)
device.connect()
device.async_connect()

await async_remove_orphan_entities(hass, entry)

hass.async_create_task(setup_entities())

Expand All @@ -223,7 +289,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)

hass.data[DOMAIN][entry.entry_id][UNSUB_LISTENER]()
hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE].close()
await hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE].close()
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

Expand All @@ -233,3 +299,19 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def update_listener(hass, config_entry):
"""Update listener."""
await hass.config_entries.async_reload(config_entry.entry_id)


async def async_remove_orphan_entities(hass, entry):
"""Remove entities associated with config entry that has been removed."""
ent_reg = await er.async_get_registry(hass)
entities = {
int(ent.unique_id.split("_")[-1]): ent.entity_id
for ent in er.async_entries_for_config_entry(ent_reg, entry.entry_id)
}

for entity in entry.data[CONF_ENTITIES]:
if entity[CONF_ID] in entities:
del entities[entity[CONF_ID]]

for entity_id in entities.values():
ent_reg.async_remove(entity_id)
Loading