Compare commits

...

44 Commits

Author SHA1 Message Date
79804e107e Merge remote-tracking branch 'upstream/master' 2025-06-22 09:53:37 -04:00
Michiel De Wilde
9eabc43da2 Deprecation fix: use VacuumActivity
This fixes the following reported issue:

STATE_DOCKED was used from localtuya, this is a deprecated constant
which will be removed in HA Core 2026.1. Use VacuumActivity.DOCKED
instead, please report it to the author of the 'localtuya' custom
integration

STATE_ERROR was used from localtuya, this is a deprecated constant
which will be removed in HA Core 2026.1. Use VacuumActivity.ERROR
instead, please report it to the author of the 'localtuya' custom
integration

STATE_IDLE was used from localtuya, this is a deprecated constant
which will be removed in HA Core 2026.1. Use VacuumActivity.IDLE
instead, please report it to the author of the 'localtuya' custom
integration

STATE_PAUSED was used from localtuya, this is a deprecated constant
which will be removed in HA Core 2026.1. Use VacuumActivity.PAUSED
instead, please report it to the author of the 'localtuya' custom
integration

STATE_RETURNING was used from localtuya, this is a deprecated constant
which will be removed in HA Core 2026.1. Use VacuumActivity.RETURNING
instead, please report it to the author of the 'localtuya' custom
integration
2025-05-08 09:50:35 +02:00
Michiel De Wilde
2217b09286 Deprecation fix: do not call async_forward_entry_setups in an async task
This fixes the following reported issue:

Detected that custom integration 'localtuya' calls
async_forward_entry_setups for integration localtuya with title:
*** and entry_id: ***, during setup without awaiting
async_forward_entry_setups, which can cause the setup lock to be
released before the setup is done at
custom_components/localtuya/__init__.py, line 275:
await hass.config_entries.async_forward_entry_setups(entry, platforms).
This will stop working in Home Assistant 2025.1, please create a bug
report at https://github.com/rospogrigio/localtuya/issues
2025-05-08 09:50:35 +02:00
Michiel De Wilde
e6bcaa1127 Deprecation fix: use async_register_admin_service
This fixes the following reported issue:

Detected that custom integration 'localtuya' accesses
hass.helpers.service, which should be updated to import functions used
from service directly at custom_components/localtuya/__init__.py,
line 165: hass.helpers.service.async_register_admin_service(. This will
stop working in Home Assistant 2025.5, please create a bug report at
https://github.com/rospogrigio/localtuya/issues
2025-05-08 09:50:35 +02:00
rospogrigio
5f2c027c1e Update manifest.json 2025-01-14 15:05:27 +01:00
Handrail9
01a6f0fa15 Update README.md (#1808)
Tuya integration page shows new information and no longer includes how to get Client ID & Secret. Added those here.

Co-authored-by: rospogrigio <49229287+rospogrigio@users.noreply.github.com>
2025-01-14 15:03:16 +01:00
Tobias Dörfler
12d40f81af fix(common.py) - Change loglevel for missing device configuration from WARNING to DEBUG. (#1816)
Loglevel warning produces a huge amount of log messages (around 500000 a day - depends on how much tuya devices are in your local network).

Co-authored-by: Tobias Dörfler <doerfler.tobias@akdb.de>
Co-authored-by: rospogrigio <49229287+rospogrigio@users.noreply.github.com>
2025-01-14 11:26:26 +01:00
Alex
6202c74e39 add a subset of HVAC_MODE_SETS (#1875)
iPAC-40 Portable Air Conditioning Heat Pump with Wifi and Remote Control. These devices have a limited option of modes. The available modes are Hot, Cold and Dry.

Co-authored-by: rospogrigio <49229287+rospogrigio@users.noreply.github.com>
2025-01-14 11:13:07 +01:00
dim145
1369245b15 fix: climate deprecated attributes 2025-01-14 09:37:19 +01:00
dim145
096568bd20 fix: remove useless test line in status_updated of light 2025-01-14 09:37:19 +01:00
dim145
65f3dafd04 fix: add color_mode property & adapt color mode method (is_xxx_mode) 2025-01-14 09:37:19 +01:00
dim145
b01861d53e fix: add FanEntityFeature.TURN_OFF/ON to fans 2025-01-14 09:37:19 +01:00
dim145
42b6eb1fc1 refactor: define MODE_MANUAL 2025-01-14 09:37:19 +01:00
dim145
d57932e8e4 fix: do not use deprecated attr in light entity 2025-01-14 09:37:19 +01:00
dim145
e1fb0f2ccb fix: do not use deprecated features 2025-01-14 09:37:19 +01:00
Andy Barratt
1da7b313d4 Add discovered HVAC and PRESET options (#1177)
* Add discovered HVAC and PRESET options

This commit adds the HVAC Action Set and Preset Set that I've discovered my Bödenwarme Underfloor Heating thermostat to be using as detailed here: https://github.com/rospogrigio/localtuya/issues/1175

* Add HVAC mode and action sets for climate entities

These are the actions and modes as reported by the Magnum smart wifi
thermostat: https://www.magnumheating.com/product/remote-control/

---------

Co-authored-by: Rutger Kerkhoff <rutger.kerkhoff@grafana.com>
2024-09-13 15:04:02 +02:00
Esteban Zapata Rojas
87d61eea6f at init: Replace deprecated async entry setup call with suggestion.
Based on https://developers.home-assistant.io/blog/2024/06/12/async_forward_entry_setups/
it is possible to replace it with `async_forward_entry_setups` instead,
which accepts multiple platforms and it should be more efficient.
2024-07-29 18:04:05 +02:00
34ab80ad93 support boolean for HVAC Eco preset 2024-05-29 13:10:00 -04:00
f27de00793 time for a longhorn reset 2024-05-12 20:03:56 -04:00
112fbeb0dd fix translations 2024-05-11 18:58:17 -04:00
e5c98ab09d apparently i forgor to add the DP field 2024-05-11 18:56:58 -04:00
5beec4d43c lets try this 2024-05-11 17:59:40 -04:00
6ea37e31c2 reverting, breaks localtuya 2024-05-11 10:23:28 -04:00
65f61925c9 add onoff logic 2024-05-11 08:54:27 -04:00
f140290f13 apparently you can't have escape chars in these strings... 2024-05-09 22:02:01 -04:00
24e2887506 fix missing const references 2024-05-09 21:40:18 -04:00
a065a45764 implement UI changes 2024-05-09 21:31:55 -04:00
hello@theauthtoken.com
d2fa4dc2a0 added dict get() instead of assuming keys exist; now handles config_flow setup failures without crashing 2024-05-03 13:19:28 +02:00
Simon Tegelid
7b37f07fd0 Add support for more ac/fan modes (#1389)
* Add support for more ac/fan modes

* Replace deprecated constants with enums

* Add support for ClimateEntityFeature.TURN_{ON,OFF}

* Fix swing log message

---------

Co-authored-by: Simon Tegelid <simon.tegelid@bitvis.io>
2024-04-15 14:28:34 +02:00
rospogrigio
490ad9ed5a Merge pull request #1613 from elad-bar/master
Fix #1610 - Replaced SUPPORT_* with enums
2024-01-22 10:37:33 +01:00
Elad Bar
9c0e733804 Replaced SUPPORT_* with enums, set minimum HA version in HACS configuration to 2024.1.0 2024-01-05 13:29:06 +02:00
rospogrigio
e5009fd3f8 Merge pull request #1476 from ncd7/climate-minmax-default-temp-when-in-F-fix
fix bug when climate entity is using default min/max temp and temp un…
2023-09-22 19:16:59 +02:00
rospogrigio
1c173d949a Merge branch 'master' into climate-minmax-default-temp-when-in-F-fix 2023-09-22 17:15:19 +02:00
rospogrigio
180b6645d2 Merge pull request #1135 from ov1d1u/master
Allow calling localtuya.set_dp service by non-admin users
2023-09-22 11:06:43 +02:00
rospogrigio
bf8f2bb06c Merge branch 'master' into master 2023-09-16 13:03:45 +02:00
rospogrigio
0c8fd9d0ec Fixing tox issues 2023-09-15 15:04:40 +02:00
rospogrigio
69774f574a Merge pull request #1465 from Nealium/offline_init
Fixes #1328
2023-09-12 17:43:37 +02:00
rospogrigio
137ff78874 Merge branch 'master' into offline_init 2023-09-12 17:10:50 +02:00
rospogrigio
fa8217cdfb Merge branch 'master' into offline_init 2023-09-12 13:28:45 +02:00
nu
b3afa14095 fix bug when climate entity is using default min/max temp and temp unit is F
The default min/max temps are in C but when the climate entity is set up with F this will cause incorrect boundary check and an inability to change the temperature from the home assistant entity UI widget.
2023-08-17 23:59:53 -04:00
Neal Joslin
62011a9d6b Fixed initialization with API but no internet. 2023-07-25 23:17:59 -04:00
Ovidiu D. Nițan
2b3ade04c7 Merge branch 'rospogrigio:master' into master 2023-07-17 15:43:12 +03:00
Ovidiu D. Nițan
6024604f33 Merge branch 'rospogrigio:master' into master 2023-01-15 20:44:10 +02:00
Ovidiu Nitan
494d334e9e Allow calling localtuya.set_dp service by non-admin users 2022-11-20 12:46:28 +02:00
19 changed files with 397 additions and 174 deletions

View File

@@ -52,7 +52,7 @@ The Cloud API configuration page will appear, requesting to input your Tuya IoT
To setup a Tuya IoT Platform account and setup a project in it, refer to the instructions for the official Tuya integration:
https://www.home-assistant.io/integrations/tuya/
The place to find the Client ID and Secret is described in this link (in the ["Get Authorization Key"](https://www.home-assistant.io/integrations/tuya/#get-authorization-key) paragraph), while the User ID can be found in the "Link Tuya App Account" subtab within the Cloud project:
The Client ID and Secret can be found at `Cloud > Development > Overview` and the User ID can be found in the "Link Tuya App Account" subtab within the Cloud project:
![user_id.png](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/8-user_id.png)
@@ -155,6 +155,32 @@ You can obtain Energy monitoring (voltage, current) in two different ways:
unit_of_measurement: 'W'
```
# Climates
There are a multitude of Tuya based climates out there, both heaters,
thermostats and ACs. The all seems to be integrated in different ways and it's
hard to find a common DP mapping. Below are a table of DP to product mapping
which are currently seen working. Use it as a guide for your own mapping and
please contribute to the list if you have the possibility.
| DP | Moes BHT 002 | Qlima WMS S + SC52 (AB;AF) | Avatto |
|-----|---------------------------------------------------------|---------------------------------------------------------|--------------------------------------------|
| 1 | ID: On/Off<br>{true, false} | ID: On/Off<br>{true, false} | ID: On/Off<br>{true, false} |
| 2 | Target temperature<br>Integer, scaling: 0.5 | Target temperature<br>Integer, scaling 1 | Target temperature<br>Integer, scaling 1 |
| 3 | Current temperature<br>Integer, scaling: 0.5 | Current temperature<br>Integer, scaling: 1 | Current temperature<br>Integer, scaling: 1 |
| 4 | Mode<br>{0, 1} | Mode<br>{"hot", "wind", "wet", "cold", "auto"} | ? |
| 5 | Eco mode<br>? | Fan mode<br>{"strong", "high", "middle", "low", "auto"} | ? |
| 15 | Not supported | Supported, unknown<br>{true, false} | ? |
| 19 | Not supported | Temperature unit<br>{"c", "f"} | ? |
| 23 | Not supported | Supported, unknown<br>Integer, eg. 68 | ? |
| 24 | Not supported | Supported, unknown<br>Integer, eg. 64 | ? |
| 101 | Not supported | Outdoor temperature<br>Integer. Scaling: 1 | ? |
| 102 | Temperature of external sensor<br>Integer, scaling: 0.5 | Supported, unknown<br>Integer, eg. 34 | ? |
| 104 | Supported, unknown<br>{true, false(?)} | Not supported | ? |
[Moes BHT 002](https://community.home-assistant.io/t/moes-bht-002-thermostat-local-control-tuya-based/151953/47)
[Avatto thermostat](https://pl.aliexpress.com/item/1005001605377377.html?gatewayAdapt=glo2pol)
# Debugging
Whenever you write a bug report, it helps tremendously if you include debug logs directly (otherwise we will just ask for them and it will take longer). So please enable debug logs like this and include them in your issue:

View File

@@ -26,6 +26,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import async_register_admin_service
from .cloud_api import TuyaCloudApi
from .common import TuyaDevice, async_config_entry_by_device_id
@@ -139,16 +140,17 @@ async def async_setup(hass: HomeAssistant, config: dict):
)
new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000))
hass.config_entries.async_update_entry(entry, data=new_data)
device = hass.data[DOMAIN][TUYA_DEVICES][device_id]
if not device.connected:
device.async_connect()
elif device_id in hass.data[DOMAIN][TUYA_DEVICES]:
# _LOGGER.debug("Device %s found with IP %s", device_id, device_ip)
device = hass.data[DOMAIN][TUYA_DEVICES][device_id]
if not device.connected:
elif device_id in hass.data[DOMAIN][TUYA_DEVICES]:
_LOGGER.debug("Device %s found with IP %s", device_id, device_ip)
device = hass.data[DOMAIN][TUYA_DEVICES].get(device_id)
if not device:
_LOGGER.warning(f"Could not find device for device_id {device_id}")
elif not device.connected:
device.async_connect()
def _shutdown(event):
"""Clean up resources when shutting down."""
discovery.close()
@@ -161,13 +163,14 @@ async def async_setup(hass: HomeAssistant, config: dict):
async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL)
hass.helpers.service.async_register_admin_service(
async_register_admin_service(
hass,
DOMAIN,
SERVICE_RELOAD,
_handle_reload,
)
hass.helpers.service.async_register_admin_service(
hass.services.async_register(
DOMAIN, SERVICE_SET_DP, _handle_set_dp, schema=SERVICE_SET_DP_SCHEMA
)
@@ -255,26 +258,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
res = await tuya_api.async_get_access_token()
if res != "ok":
_LOGGER.error("Cloud API connection failed: %s", res)
else:
_LOGGER.info("Cloud API connection succeeded.")
res = await tuya_api.async_get_devices_list()
hass.data[DOMAIN][DATA_CLOUD] = tuya_api
async def setup_entities(device_ids):
platforms = set()
for dev_id in device_ids:
for dev_id in entry.data[CONF_DEVICES].keys():
entities = entry.data[CONF_DEVICES][dev_id][CONF_ENTITIES]
platforms = platforms.union(
set(entity[CONF_PLATFORM] for entity in entities)
)
hass.data[DOMAIN][TUYA_DEVICES][dev_id] = TuyaDevice(hass, entry, dev_id)
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_setup(entry, platform)
for platform in platforms
]
)
# Setup all platforms at once, letting HA handling each platform and avoiding
# potential integration restarts while elements are still initialising.
await hass.config_entries.async_forward_entry_setups(entry, platforms)
async def setup_entities(device_ids):
for dev_id in device_ids:
hass.data[DOMAIN][TUYA_DEVICES][dev_id].async_connect()

View File

@@ -11,18 +11,20 @@ from homeassistant.components.climate import (
ClimateEntity,
)
from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
HVACAction,
HVACMode,
PRESET_AWAY,
PRESET_ECO,
PRESET_HOME,
PRESET_NONE,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
ClimateEntityFeature,
FAN_AUTO,
FAN_LOW,
FAN_MEDIUM,
FAN_HIGH,
FAN_TOP,
SWING_ON,
SWING_OFF,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
@@ -30,15 +32,17 @@ from homeassistant.const import (
PRECISION_HALVES,
PRECISION_TENTHS,
PRECISION_WHOLE,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
UnitOfTemperature,
)
from .common import LocalTuyaEntity, async_setup_entry
from .const import (
CONF_CURRENT_TEMPERATURE_DP,
CONF_TEMP_MAX,
CONF_TEMP_MIN,
CONF_ECO_DP,
CONF_ECO_VALUE,
CONF_ECO_VALUE_BOOL,
CONF_HEURISTIC_ACTION,
CONF_HVAC_ACTION_DP,
CONF_HVAC_ACTION_SET,
@@ -52,52 +56,91 @@ from .const import (
CONF_TARGET_PRECISION,
CONF_TARGET_TEMPERATURE_DP,
CONF_TEMPERATURE_STEP,
CONF_HVAC_FAN_MODE_DP,
CONF_HVAC_FAN_MODE_SET,
CONF_HVAC_SWING_MODE_DP,
CONF_HVAC_SWING_MODE_SET,
)
_LOGGER = logging.getLogger(__name__)
HVAC_MODE_SETS = {
"manual/auto": {
HVAC_MODE_HEAT: "manual",
HVAC_MODE_AUTO: "auto",
HVACMode.HEAT: "manual",
HVACMode.AUTO: "auto",
},
"Manual/Auto": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Auto",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Auto",
},
"MANUAL/AUTO": {
HVACMode.HEAT: "MANUAL",
HVACMode.AUTO: "AUTO",
},
"Manual/Program": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Program",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Program",
},
"m/p": {
HVAC_MODE_HEAT: "m",
HVAC_MODE_AUTO: "p",
HVACMode.HEAT: "m",
HVACMode.AUTO: "p",
},
"True/False": {
HVAC_MODE_HEAT: True,
HVACMode.HEAT: True,
},
"Auto/Cold/Dry/Wind/Hot": {
HVACMode.HEAT: "hot",
HVACMode.FAN_ONLY: "wind",
HVACMode.DRY: "wet",
HVACMode.COOL: "cold",
HVACMode.AUTO: "auto",
},
"Cold/Dehumidify/Hot": {
HVACMode.HEAT: "hot",
HVACMode.DRY: "dehumidify",
HVACMode.COOL: "cold",
},
"1/0": {
HVAC_MODE_HEAT: "1",
HVAC_MODE_AUTO: "0",
HVACMode.HEAT: "1",
HVACMode.AUTO: "0",
},
}
HVAC_ACTION_SETS = {
"True/False": {
CURRENT_HVAC_HEAT: True,
CURRENT_HVAC_IDLE: False,
HVACAction.HEATING: True,
HVACAction.IDLE: False,
},
"open/close": {
CURRENT_HVAC_HEAT: "open",
CURRENT_HVAC_IDLE: "close",
HVACAction.HEATING: "open",
HVACAction.IDLE: "close",
},
"heating/no_heating": {
CURRENT_HVAC_HEAT: "heating",
CURRENT_HVAC_IDLE: "no_heating",
HVACAction.HEATING: "heating",
HVACAction.IDLE: "no_heating",
},
"Heat/Warming": {
CURRENT_HVAC_HEAT: "Heat",
CURRENT_HVAC_IDLE: "Warming",
HVACAction.HEATING: "Heat",
HVACAction.IDLE: "Warming",
},
"heating/warming": {
HVACAction.HEATING: "heating",
HVACAction.IDLE: "warming",
},
}
HVAC_FAN_MODE_SETS = {
"Auto/Low/Middle/High/Strong": {
FAN_AUTO: "auto",
FAN_LOW: "low",
FAN_MEDIUM: "middle",
FAN_HIGH: "high",
FAN_TOP: "strong",
}
}
HVAC_SWING_MODE_SETS = {
"True/False": {
SWING_ON: True,
SWING_OFF: False,
}
}
PRESET_SETS = {
"Manual/Holiday/Program": {
@@ -105,6 +148,11 @@ PRESET_SETS = {
PRESET_HOME: "Program",
PRESET_NONE: "Manual",
},
"smart/holiday/hold": {
PRESET_AWAY: "holiday",
PRESET_HOME: "smart",
PRESET_NONE: "hold",
},
}
TEMPERATURE_CELSIUS = "celsius"
@@ -121,26 +169,31 @@ def flow_schema(dps):
return {
vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_CURRENT_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_TEMPERATURE_STEP): vol.In(
vol.Optional(CONF_TEMPERATURE_STEP, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP_DP): vol.In(dps),
vol.Optional(CONF_MIN_TEMP_DP): vol.In(dps),
vol.Optional(CONF_PRECISION): vol.In(
vol.Optional(CONF_PRECISION, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HVAC_MODE_DP): vol.In(dps),
vol.Optional(CONF_HVAC_MODE_SET): vol.In(list(HVAC_MODE_SETS.keys())),
vol.Optional(CONF_HVAC_FAN_MODE_DP): vol.In(dps),
vol.Optional(CONF_HVAC_FAN_MODE_SET): vol.In(list(HVAC_FAN_MODE_SETS.keys())),
vol.Optional(CONF_HVAC_ACTION_DP): vol.In(dps),
vol.Optional(CONF_HVAC_ACTION_SET): vol.In(list(HVAC_ACTION_SETS.keys())),
vol.Optional(CONF_ECO_DP): vol.In(dps),
vol.Optional(CONF_ECO_VALUE): str,
vol.Optional(CONF_ECO_VALUE_BOOL): bool,
vol.Optional(CONF_PRESET_DP): vol.In(dps),
vol.Optional(CONF_PRESET_SET): vol.In(list(PRESET_SETS.keys())),
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
[TEMPERATURE_CELSIUS, TEMPERATURE_FAHRENHEIT]
),
vol.Optional(CONF_TARGET_PRECISION): vol.In(
vol.Optional(CONF_TARGET_PRECISION, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HEURISTIC_ACTION): bool,
@@ -163,6 +216,8 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._target_temperature = None
self._current_temperature = None
self._hvac_mode = None
self._fan_mode = None
self._swing_mode = None
self._preset_mode = None
self._hvac_action = None
self._precision = self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
@@ -173,6 +228,14 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._conf_hvac_mode_set = HVAC_MODE_SETS.get(
self._config.get(CONF_HVAC_MODE_SET), {}
)
self._conf_hvac_fan_mode_dp = self._config.get(CONF_HVAC_FAN_MODE_DP)
self._conf_hvac_fan_mode_set = HVAC_FAN_MODE_SETS.get(
self._config.get(CONF_HVAC_FAN_MODE_SET), {}
)
self._conf_hvac_swing_mode_dp = self._config.get(CONF_HVAC_SWING_MODE_DP)
self._conf_hvac_swing_mode_set = HVAC_SWING_MODE_SETS.get(
self._config.get(CONF_HVAC_SWING_MODE_SET), {}
)
self._conf_preset_dp = self._config.get(CONF_PRESET_DP)
self._conf_preset_set = PRESET_SETS.get(self._config.get(CONF_PRESET_SET), {})
self._conf_hvac_action_dp = self._config.get(CONF_HVAC_ACTION_DP)
@@ -181,6 +244,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
)
self._conf_eco_dp = self._config.get(CONF_ECO_DP)
self._conf_eco_value = self._config.get(CONF_ECO_VALUE, "ECO")
self._conf_eco_value_bool = self._config.get(CONF_ECO_VALUE_BOOL)
self._has_presets = self.has_config(CONF_ECO_DP) or self.has_config(
CONF_PRESET_DP
)
@@ -189,13 +253,17 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = 0
supported_features = ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
if self.has_config(CONF_TARGET_TEMPERATURE_DP):
supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE
supported_features = supported_features | ClimateEntityFeature.TARGET_TEMPERATURE
if self.has_config(CONF_MAX_TEMP_DP):
supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE_RANGE
supported_features = supported_features | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
if self.has_config(CONF_PRESET_DP) or self.has_config(CONF_ECO_DP):
supported_features = supported_features | SUPPORT_PRESET_MODE
supported_features = supported_features | ClimateEntityFeature.PRESET_MODE
if self.has_config(CONF_HVAC_FAN_MODE_DP) and self.has_config(CONF_HVAC_FAN_MODE_SET):
supported_features = supported_features | ClimateEntityFeature.FAN_MODE
if self.has_config(CONF_HVAC_SWING_MODE_DP):
supported_features = supported_features | ClimateEntityFeature.SWING_MODE
return supported_features
@property
@@ -215,8 +283,8 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._config.get(CONF_TEMPERATURE_UNIT, DEFAULT_TEMPERATURE_UNIT)
== TEMPERATURE_FAHRENHEIT
):
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
return UnitOfTemperature.FAHRENHEIT
return UnitOfTemperature.CELSIUS
@property
def hvac_mode(self):
@@ -228,7 +296,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
"""Return the list of available operation modes."""
if not self.has_config(CONF_HVAC_MODE_DP):
return None
return list(self._conf_hvac_mode_set) + [HVAC_MODE_OFF]
return list(self._conf_hvac_mode_set) + [HVACMode.OFF]
@property
def hvac_action(self):
@@ -237,22 +305,22 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
Need to be one of CURRENT_HVAC_*.
"""
if self._config.get(CONF_HEURISTIC_ACTION, False):
if self._hvac_mode == HVAC_MODE_HEAT:
if self._hvac_mode == HVACMode.HEAT:
if self._current_temperature < (
self._target_temperature - self._precision
):
self._hvac_action = CURRENT_HVAC_HEAT
self._hvac_action = HVACAction.HEATING
if self._current_temperature == (
self._target_temperature - self._precision
):
if self._hvac_action == CURRENT_HVAC_HEAT:
self._hvac_action = CURRENT_HVAC_HEAT
if self._hvac_action == CURRENT_HVAC_IDLE:
self._hvac_action = CURRENT_HVAC_IDLE
if self._hvac_action == HVACAction.HEATING:
self._hvac_action = HVACAction.HEATING
if self._hvac_action == HVACAction.IDLE:
self._hvac_action = HVACAction.IDLE
if (
self._current_temperature + self._precision
) > self._target_temperature:
self._hvac_action = CURRENT_HVAC_IDLE
self._hvac_action = HVACAction.IDLE
return self._hvac_action
return self._hvac_action
@@ -289,12 +357,26 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
@property
def fan_mode(self):
"""Return the fan setting."""
return NotImplementedError()
return self._fan_mode
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return NotImplementedError()
if not self.has_config(CONF_HVAC_FAN_MODE_DP):
return None
return list(self._conf_hvac_fan_mode_set)
@property
def swing_mode(self):
"""Return the swing setting."""
return self._swing_mode
@property
def swing_modes(self):
"""Return the list of available swing modes."""
if not self.has_config(CONF_HVAC_SWING_MODE_DP):
return None
return list(self._conf_hvac_swing_mode_set)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -304,13 +386,21 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
temperature, self._config[CONF_TARGET_TEMPERATURE_DP]
)
def set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
return NotImplementedError()
if self._conf_hvac_fan_mode_dp is None:
_LOGGER.error("Fan speed unsupported (no DP)")
return
if fan_mode not in self._conf_hvac_fan_mode_set:
_LOGGER.error("Unsupported fan_mode: %s" % fan_mode)
return
await self._device.set_dp(
self._conf_hvac_fan_mode_set[fan_mode], self._conf_hvac_fan_mode_dp
)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF:
if hvac_mode == HVACMode.OFF:
await self._device.set_dp(False, self._dp_id)
return
if not self._state and self._conf_hvac_mode_dp != self._dp_id:
@@ -321,6 +411,18 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._conf_hvac_mode_set[hvac_mode], self._conf_hvac_mode_dp
)
async def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
if self._conf_hvac_swing_mode_dp is None:
_LOGGER.error("Swing mode unsupported (no DP)")
return
if swing_mode not in self._conf_hvac_swing_mode_set:
_LOGGER.error("Unsupported swing_mode: %s" % swing_mode)
return
await self._device.set_dp(
self._conf_hvac_swing_mode_set[swing_mode], self._conf_hvac_swing_mode_dp
)
async def async_turn_on(self) -> None:
"""Turn the entity on."""
await self._device.set_dp(True, self._dp_id)
@@ -332,6 +434,9 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode):
"""Set new target preset mode."""
if preset_mode == PRESET_ECO:
if self._conf_eco_value_bool:
await self._device.set_dp(True, self._conf_eco_dp)
else:
await self._device.set_dp(self._conf_eco_value, self._conf_eco_dp)
return
await self._device.set_dp(
@@ -343,14 +448,14 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
"""Return the minimum temperature."""
if self.has_config(CONF_MIN_TEMP_DP):
return self.dps_conf(CONF_MIN_TEMP_DP)
return DEFAULT_MIN_TEMP
return self._config[CONF_TEMP_MIN]
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.has_config(CONF_MAX_TEMP_DP):
return self.dps_conf(CONF_MAX_TEMP_DP)
return DEFAULT_MAX_TEMP
return self._config[CONF_TEMP_MAX]
def status_updated(self):
"""Device status was updated."""
@@ -383,7 +488,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
# Update the HVAC status
if self.has_config(CONF_HVAC_MODE_DP):
if not self._state:
self._hvac_mode = HVAC_MODE_OFF
self._hvac_mode = HVACMode.OFF
else:
for mode, value in self._conf_hvac_mode_set.items():
if self.dps_conf(CONF_HVAC_MODE_DP) == value:
@@ -391,7 +496,28 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
break
else:
# in case hvac mode and preset share the same dp
self._hvac_mode = HVAC_MODE_AUTO
self._hvac_mode = HVACMode.AUTO
# Update the fan status
if self.has_config(CONF_HVAC_FAN_MODE_DP):
for mode, value in self._conf_hvac_fan_mode_set.items():
if self.dps_conf(CONF_HVAC_FAN_MODE_DP) == value:
self._fan_mode = mode
break
else:
# in case fan mode and preset share the same dp
_LOGGER.debug("Unknown fan mode %s" % self.dps_conf(CONF_HVAC_FAN_MODE_DP))
self._fan_mode = FAN_AUTO
# Update the swing status
if self.has_config(CONF_HVAC_SWING_MODE_DP):
for mode, value in self._conf_hvac_swing_mode_set.items():
if self.dps_conf(CONF_HVAC_SWING_MODE_DP) == value:
self._swing_mode = mode
break
else:
_LOGGER.debug("Unknown swing mode %s" % self.dps_conf(CONF_HVAC_SWING_MODE_DP))
self._swing_mode = SWING_OFF
# Update the current action
for action, value in self._conf_hvac_action_set.items():

View File

@@ -101,7 +101,10 @@ class TuyaCloudApi:
async def async_get_access_token(self):
"""Obtain a valid access token."""
try:
resp = await self.async_make_request("GET", "/v1.0/token?grant_type=1")
except requests.exceptions.ConnectionError:
return "Request failed, status ConnectionError"
if not resp.ok:
return "Request failed, status " + str(resp.status)

View File

@@ -124,8 +124,10 @@ def async_config_entry_by_device_id(hass, device_id):
"""Look up config entry by device id."""
current_entries = hass.config_entries.async_entries(DOMAIN)
for entry in current_entries:
if device_id in entry.data[CONF_DEVICES]:
if device_id in entry.data.get(CONF_DEVICES, []):
return entry
else:
_LOGGER.debug(f"Missing device configuration for device_id {device_id}")
return None

View File

@@ -53,6 +53,7 @@ CONF_BRIGHTNESS_LOWER = "brightness_lower"
CONF_BRIGHTNESS_UPPER = "brightness_upper"
CONF_COLOR = "color"
CONF_COLOR_MODE = "color_mode"
CONF_COLOR_MODE_SET = "color_mode_set"
CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin"
CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin"
CONF_COLOR_TEMP_REVERSE = "color_temp_reverse"
@@ -91,10 +92,16 @@ CONF_CURRENT_TEMPERATURE_DP = "current_temperature_dp"
CONF_TEMPERATURE_STEP = "temperature_step"
CONF_MAX_TEMP_DP = "max_temperature_dp"
CONF_MIN_TEMP_DP = "min_temperature_dp"
CONF_TEMP_MAX = "max_temperature_const"
CONF_TEMP_MIN = "min_temperature_const"
CONF_PRECISION = "precision"
CONF_TARGET_PRECISION = "target_precision"
CONF_HVAC_MODE_DP = "hvac_mode_dp"
CONF_HVAC_MODE_SET = "hvac_mode_set"
CONF_HVAC_FAN_MODE_DP = "hvac_fan_mode_dp"
CONF_HVAC_FAN_MODE_SET = "hvac_fan_mode_set"
CONF_HVAC_SWING_MODE_DP = "hvac_swing_mode_dp"
CONF_HVAC_SWING_MODE_SET = "hvac_swing_mode_set"
CONF_PRESET_DP = "preset_dp"
CONF_PRESET_SET = "preset_set"
CONF_HEURISTIC_ACTION = "heuristic_action"
@@ -102,6 +109,7 @@ CONF_HVAC_ACTION_DP = "hvac_action_dp"
CONF_HVAC_ACTION_SET = "hvac_action_set"
CONF_ECO_DP = "eco_dp"
CONF_ECO_VALUE = "eco_value"
CONF_ECO_VALUE_BOOL = "eco_value_bool"
# vacuum
CONF_POWERGO_DP = "powergo_dp"

View File

@@ -8,11 +8,7 @@ import voluptuous as vol
from homeassistant.components.cover import (
ATTR_POSITION,
DOMAIN,
SUPPORT_CLOSE,
SUPPORT_OPEN,
SUPPORT_SET_POSITION,
SUPPORT_STOP,
CoverEntity,
CoverEntity, CoverEntityFeature,
)
from .common import LocalTuyaEntity, async_setup_entry
@@ -80,9 +76,9 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
if self._config[CONF_POSITIONING_MODE] != COVER_MODE_NONE:
supported_features = supported_features | SUPPORT_SET_POSITION
supported_features = supported_features | CoverEntityFeature.SET_POSITION
return supported_features
@property

View File

@@ -9,9 +9,7 @@ from homeassistant.components.fan import (
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN,
SUPPORT_DIRECTION,
SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
FanEntityFeature,
FanEntity,
)
from homeassistant.util.percentage import (
@@ -189,18 +187,21 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
self.schedule_update_ha_state()
@property
def supported_features(self) -> int:
def supported_features(self) -> FanEntityFeature:
"""Flag supported features."""
features = 0
features = FanEntityFeature(0)
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
features |= SUPPORT_OSCILLATE
features |= FanEntityFeature.OSCILLATE
if self.has_config(CONF_FAN_SPEED_CONTROL):
features |= SUPPORT_SET_SPEED
features |= FanEntityFeature.SET_SPEED
if self.has_config(CONF_FAN_DIRECTION):
features |= SUPPORT_DIRECTION
features |= FanEntityFeature.DIRECTION
features |= FanEntityFeature.TURN_OFF
features |= FanEntityFeature.TURN_ON
return features

View File

@@ -1,21 +1,19 @@
"""Platform to locally control Tuya-based light devices."""
import logging
import textwrap
from dataclasses import dataclass
from functools import partial
import homeassistant.util.color as color_util
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_HS_COLOR,
DOMAIN,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
LightEntity,
LightEntityFeature,
ColorMode,
)
from homeassistant.const import CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_SCENE
@@ -28,7 +26,7 @@ from .const import (
CONF_COLOR_TEMP_MAX_KELVIN,
CONF_COLOR_TEMP_MIN_KELVIN,
CONF_COLOR_TEMP_REVERSE,
CONF_MUSIC_MODE,
CONF_MUSIC_MODE, CONF_COLOR_MODE_SET,
)
_LOGGER = logging.getLogger(__name__)
@@ -41,6 +39,7 @@ DEFAULT_COLOR_TEMP_REVERSE = False
DEFAULT_LOWER_BRIGHTNESS = 29
DEFAULT_UPPER_BRIGHTNESS = 1000
MODE_MANUAL = "manual"
MODE_COLOR = "colour"
MODE_MUSIC = "music"
MODE_SCENE = "scene"
@@ -49,6 +48,8 @@ MODE_WHITE = "white"
SCENE_CUSTOM = "Custom"
SCENE_MUSIC = "Music"
MODES_SET = {"Colour, Music, Scene and White": 0, "Manual, Music, Scene and White": 1}
SCENE_LIST_RGBW_1000 = {
"Night": "000e0d0000000000000000c80000",
"Read": "010e0d0000000000000003e801f4",
@@ -91,6 +92,22 @@ SCENE_LIST_RGB_1000 = {
+ "0000000",
}
@dataclass(frozen=True)
class Mode:
color: str = MODE_COLOR
music: str = MODE_MUSIC
scene: str = MODE_SCENE
white: str = MODE_WHITE
def as_list(self) -> list:
return [self.color, self.music, self.scene, self.white]
def as_dict(self) -> dict[str, str]:
default = {"Default": self.white}
return {**default, "Mode Color": self.color, "Mode Scene": self.scene}
MAP_MODE_SET = {0: Mode(), 1: Mode(color=MODE_MANUAL)}
def map_range(value, from_lower, from_upper, to_lower, to_upper):
"""Map a value in one range to another."""
@@ -162,10 +179,12 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
self._color_temp_reverse = self._config.get(
CONF_COLOR_TEMP_REVERSE, DEFAULT_COLOR_TEMP_REVERSE
)
self._modes = MAP_MODE_SET[int(self._config.get(CONF_COLOR_MODE_SET, 0))]
self._hs = None
self._effect = None
self._effect_list = []
self._scenes = None
self._scenes = {}
if self.has_config(CONF_SCENE):
if self._config.get(CONF_SCENE) < 20:
self._scenes = SCENE_LIST_RGBW_255
@@ -174,6 +193,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
else:
self._scenes = SCENE_LIST_RGBW_1000
self._effect_list = list(self._scenes.keys())
if self._config.get(CONF_MUSIC_MODE):
self._effect_list.append(SCENE_MUSIC)
@@ -197,8 +217,8 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
if self.is_color_mode:
return self._hs
if (
self.supported_features & SUPPORT_COLOR
and not self.supported_features & SUPPORT_COLOR_TEMP
ColorMode.HS in self.supported_color_modes
and not ColorMode.COLOR_TEMP in self.supported_color_modes
):
return [0, 0]
return None
@@ -241,45 +261,76 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
@property
def effect_list(self):
"""Return the list of supported effects for this light."""
return self._effect_list
if self.is_scene_mode or self.is_music_mode:
return self._effect
elif (color_mode := self.__get_color_mode()) in self._scenes.values():
return self.__find_scene_by_scene_data(color_mode)
return None
@property
def supported_features(self):
"""Flag supported features."""
supports = 0
if self.has_config(CONF_BRIGHTNESS):
supports |= SUPPORT_BRIGHTNESS
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
"""Flag supported color modes."""
color_modes: set[ColorMode] = set()
if self.has_config(CONF_COLOR_TEMP):
supports |= SUPPORT_COLOR_TEMP
color_modes.add(ColorMode.COLOR_TEMP)
if self.has_config(CONF_COLOR):
supports |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS
color_modes.add(ColorMode.HS)
if not color_modes and self.has_config(CONF_BRIGHTNESS):
return {ColorMode.BRIGHTNESS}
if not color_modes:
return {ColorMode.ONOFF}
return color_modes
@property
def supported_features(self) -> LightEntityFeature:
"""Flag supported features."""
supports = LightEntityFeature(0)
if self.has_config(CONF_SCENE) or self.has_config(CONF_MUSIC_MODE):
supports |= SUPPORT_EFFECT
supports |= LightEntityFeature.EFFECT
return supports
@property
def color_mode(self) -> ColorMode:
"""Return the color_mode of the light."""
if len(self.supported_color_modes) == 1:
return next(iter(self.supported_color_modes))
if self.is_color_mode:
return ColorMode.HS
if self.is_white_mode:
return ColorMode.COLOR_TEMP
if self._brightness:
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF
@property
def is_white_mode(self):
"""Return true if the light is in white mode."""
color_mode = self.__get_color_mode()
return color_mode is None or color_mode == MODE_WHITE
return color_mode is None or color_mode == self._modes.white
@property
def is_color_mode(self):
"""Return true if the light is in color mode."""
color_mode = self.__get_color_mode()
return color_mode is not None and color_mode == MODE_COLOR
return color_mode is not None and color_mode == self._modes.color
@property
def is_scene_mode(self):
"""Return true if the light is in scene mode."""
color_mode = self.__get_color_mode()
return color_mode is not None and color_mode.startswith(MODE_SCENE)
return color_mode is not None and color_mode.startswith(self._modes.scene)
@property
def is_music_mode(self):
"""Return true if the light is in music mode."""
color_mode = self.__get_color_mode()
return color_mode is not None and color_mode == MODE_MUSIC
return color_mode is not None and color_mode == self._modes.music
def __is_color_rgb_encoded(self):
return len(self.dps_conf(CONF_COLOR)) > 12
@@ -294,7 +345,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
return (
self.dps_conf(CONF_COLOR_MODE)
if self.has_config(CONF_COLOR_MODE)
else MODE_WHITE
else self._modes.white
)
async def async_turn_on(self, **kwargs):
@@ -304,7 +355,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
states[self._dp_id] = True
features = self.supported_features
brightness = None
if ATTR_EFFECT in kwargs and (features & SUPPORT_EFFECT):
if ATTR_EFFECT in kwargs and (features & LightEntityFeature.EFFECT):
scene = self._scenes.get(kwargs[ATTR_EFFECT])
if scene is not None:
if scene.startswith(MODE_SCENE):
@@ -315,7 +366,11 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
elif kwargs[ATTR_EFFECT] == SCENE_MUSIC:
states[self._config.get(CONF_COLOR_MODE)] = MODE_MUSIC
if ATTR_BRIGHTNESS in kwargs and (features & SUPPORT_BRIGHTNESS):
if ATTR_BRIGHTNESS in kwargs and (
ColorMode.BRIGHTNESS in self.supported_color_modes
or self.has_config(CONF_BRIGHTNESS)
or self.has_config(CONF_COLOR)
):
brightness = map_range(
int(kwargs[ATTR_BRIGHTNESS]),
0,
@@ -347,7 +402,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
states[self._config.get(CONF_COLOR)] = color
states[self._config.get(CONF_COLOR_MODE)] = MODE_COLOR
if ATTR_HS_COLOR in kwargs and (features & SUPPORT_COLOR):
if ATTR_HS_COLOR in kwargs and ColorMode.HS in self.supported_color_modes:
if brightness is None:
brightness = self._brightness
hs = kwargs[ATTR_HS_COLOR]
@@ -374,10 +429,10 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
states[self._config.get(CONF_COLOR)] = color
states[self._config.get(CONF_COLOR_MODE)] = MODE_COLOR
if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP):
if ColorMode.COLOR_TEMP in kwargs and ColorMode.COLOR_TEMP in self.supported_color_modes:
if brightness is None:
brightness = self._brightness
mired = int(kwargs[ATTR_COLOR_TEMP])
mired = int(kwargs[ColorMode.COLOR_TEMP])
if self._color_temp_reverse:
mired = self._max_mired - (mired - self._min_mired)
if mired < self._min_mired:
@@ -403,10 +458,14 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
self._state = self.dps(self._dp_id)
supported = self.supported_features
self._effect = None
if supported & SUPPORT_BRIGHTNESS and self.has_config(CONF_BRIGHTNESS):
if (ColorMode.BRIGHTNESS in self.supported_color_modes
or self.has_config(CONF_BRIGHTNESS)
or self.has_config(CONF_COLOR)
):
self._brightness = self.dps_conf(CONF_BRIGHTNESS)
if supported & SUPPORT_COLOR:
if ColorMode.HS in self.supported_color_modes:
color = self.dps_conf(CONF_COLOR)
if color is not None and not self.is_white_mode:
if self.__is_color_rgb_encoded():
@@ -422,10 +481,10 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
self._hs = [hue, sat / 10.0]
self._brightness = value
if supported & SUPPORT_COLOR_TEMP:
if ColorMode.COLOR_TEMP in self.supported_color_modes:
self._color_temp = self.dps_conf(CONF_COLOR_TEMP)
if self.is_scene_mode and supported & SUPPORT_EFFECT:
if self.is_scene_mode and supported & LightEntityFeature.EFFECT:
if self.dps_conf(CONF_COLOR_MODE) != MODE_SCENE:
self._effect = self.__find_scene_by_scene_data(
self.dps_conf(CONF_COLOR_MODE)
@@ -440,7 +499,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
elif SCENE_CUSTOM in self._effect_list:
self._effect_list.remove(SCENE_CUSTOM)
if self.is_music_mode and supported & SUPPORT_EFFECT:
if self.is_music_mode and supported & LightEntityFeature.EFFECT:
self._effect = SCENE_MUSIC

View File

@@ -10,5 +10,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/rospogrigio/localtuya/issues",
"requirements": [],
"version": "5.2.1"
"version": "5.2.3"
}

View File

@@ -121,6 +121,7 @@
"preset_set": "Presets Set (optional)",
"eco_dp": "Eco DP (optional)",
"eco_value": "Eco value (optional)",
"eco_value_bool":"Eco value is a boolean type (true/false)",
"heuristic_action": "Enable heuristic action (optional)",
"dps_default_value": "Default value when un-initialised (optional)",
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",

View File

@@ -72,7 +72,13 @@
"title": "Edit a new device",
"description": "Pick the configured device you wish to edit.",
"data": {
"selected_device": "Configured Devices"
"selected_device": "Configured Devices",
"max_temperature_const": "Max Temperature Constant (optional)",
"min_temperature_const": "Min Temperature Constant (optional)",
"hvac_fan_mode_dp": "HVAC Fan Mode DP (optional)",
"hvac_fan_mode_set": "HVAC Fan Mode Set (optional)",
"hvac_swing_mode_dp": "HVAC Swing Mode DP (optional)",
"hvac_swing_mode_set": "HVAC Swing Mode Set (optional)"
}
},
"cloud_setup": {
@@ -174,19 +180,26 @@
"current_temperature_dp": "Current Temperature",
"target_temperature_dp": "Target Temperature",
"temperature_step": "Temperature Step (optional)",
"max_temperature_dp": "Max Temperature (optional)",
"min_temperature_dp": "Min Temperature (optional)",
"max_temperature_dp": "Max Temperature DP (optional)",
"min_temperature_dp": "Min Temperature DP (optional)",
"max_temperature_const": "Max Temperature Constant (optional)",
"min_temperature_const": "Min Temperature Constant (optional)",
"precision": "Precision (optional, for DPs values)",
"target_precision": "Target Precision (optional, for DPs values)",
"temperature_unit": "Temperature Unit (optional)",
"hvac_mode_dp": "HVAC Mode DP (optional)",
"hvac_mode_set": "HVAC Mode Set (optional)",
"hvac_fan_mode_dp": "HVAC Fan Mode DP (optional)",
"hvac_fan_mode_set": "HVAC Fan Mode Set (optional)",
"hvac_swing_mode_dp": "HVAC Swing Mode DP (optional)",
"hvac_swing_mode_set": "HVAC Swing Mode Set (optional)",
"hvac_action_dp": "HVAC Current Action DP (optional)",
"hvac_action_set": "HVAC Current Action Set (optional)",
"preset_dp": "Presets DP (optional)",
"preset_set": "Presets Set (optional)",
"eco_dp": "Eco DP (optional)",
"eco_value": "Eco value (optional)",
"eco_value_bool":"Eco value is a boolean type (true/false)",
"heuristic_action": "Enable heuristic action (optional)",
"dps_default_value": "Default value when un-initialised (optional)",
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",

View File

@@ -183,6 +183,7 @@
"preset_set": "Set di preset (opzionale)",
"eco_dp": "DP per Eco (opzionale)",
"eco_value": "Valore Eco (opzionale)",
"eco_value_bool":"Il valore ecologico è di tipo booleano (vero/falso)",
"heuristic_action": "Abilita azione euristica (opzionale)"
}
}

View File

@@ -183,6 +183,7 @@
"preset_set": "Conjunto de predefinições (opcional)",
"eco_dp": "Eco DP (opcional)",
"eco_value": "Valor eco (opcional)",
"eco_value_bool":"O valor ecológico é do tipo booleano (verdadeiro/falso)",
"heuristic_action": "Ativar ação heurística (opcional)"
}
}

View File

@@ -5,22 +5,7 @@ from functools import partial
import voluptuous as vol
from homeassistant.components.vacuum import (
DOMAIN,
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
SUPPORT_BATTERY,
SUPPORT_FAN_SPEED,
SUPPORT_LOCATE,
SUPPORT_PAUSE,
SUPPORT_RETURN_HOME,
SUPPORT_START,
SUPPORT_STATE,
SUPPORT_STATUS,
SUPPORT_STOP,
StateVacuumEntity,
StateVacuumEntity, VacuumActivity, VacuumEntityFeature,
)
from .common import LocalTuyaEntity, async_setup_entry
@@ -123,21 +108,21 @@ class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
def supported_features(self):
"""Flag supported features."""
supported_features = (
SUPPORT_START
| SUPPORT_PAUSE
| SUPPORT_STOP
| SUPPORT_STATUS
| SUPPORT_STATE
VacuumEntityFeature.START
| VacuumEntityFeature.PAUSE
| VacuumEntityFeature.STOP
| VacuumEntityFeature.STATUS
| VacuumEntityFeature.STATE
)
if self.has_config(CONF_RETURN_MODE):
supported_features = supported_features | SUPPORT_RETURN_HOME
supported_features = supported_features | VacuumEntityFeature.RETURN_HOME
if self.has_config(CONF_FAN_SPEED_DP):
supported_features = supported_features | SUPPORT_FAN_SPEED
supported_features = supported_features | VacuumEntityFeature.FAN_SPEED
if self.has_config(CONF_BATTERY_DP):
supported_features = supported_features | SUPPORT_BATTERY
supported_features = supported_features | VacuumEntityFeature.BATTERY
if self.has_config(CONF_LOCATE_DP):
supported_features = supported_features | SUPPORT_LOCATE
supported_features = supported_features | VacuumEntityFeature.LOCATE
return supported_features
@@ -216,15 +201,15 @@ class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
state_value = str(self.dps(self._dp_id))
if state_value in self._idle_status_list:
self._state = STATE_IDLE
self._state = VacuumActivity.IDLE
elif state_value in self._docked_status_list:
self._state = STATE_DOCKED
self._state = VacuumActivity.DOCKED
elif state_value == self._config[CONF_RETURNING_STATUS_VALUE]:
self._state = STATE_RETURNING
self._state = VacuumActivity.RETURNING
elif state_value == self._config[CONF_PAUSED_STATE]:
self._state = STATE_PAUSED
self._state = VacuumActivity.PAUSED
else:
self._state = STATE_CLEANING
self._state = VacuumActivity.CLEANING
if self.has_config(CONF_BATTERY_DP):
self._battery_level = self.dps_conf(CONF_BATTERY_DP)
@@ -250,7 +235,7 @@ class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
if self.has_config(CONF_FAULT_DP):
self._attrs[FAULT] = self.dps_conf(CONF_FAULT_DP)
if self._attrs[FAULT] != 0:
self._state = STATE_ERROR
self._state = VacuumActivity.ERROR
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaVacuum, flow_schema)

View File

@@ -1,4 +1,4 @@
{
"name": "Local Tuya",
"homeassistant": "0.116.0"
"homeassistant": "2024.1.0"
}

View File

@@ -1,5 +1,5 @@
[tool.black]
target-version = ["py38", "py39"]
target-version = ["py311"]
include = 'custom_components/localtuya/.*\.py'
# pylint config stolen from Home Assistant

View File

@@ -4,7 +4,7 @@ max-line-length = 120
ignore = E203, W503
[mypy]
python_version = 3.9
python_version = 3.11
ignore_errors = true
follow_imports = silent
ignore_missing_imports = true

View File

@@ -1,12 +1,12 @@
[tox]
skipsdist = true
envlist = py{38,39}, lint, typing
envlist = py{311}, lint, typing
skip_missing_interpreters = True
cs_exclude_words = hass,unvalid
[gh-actions]
python =
3.9: clean, py39, lint, typing
3.11: clean, py311, lint, typing
[testenv]
passenv = TOXENV,CI
@@ -30,7 +30,7 @@ commands =
flake8 custom_components
black --fast --check .
pydocstyle -v custom_components
pylint custom_components/localtuya --rcfile=pylint.rc
# pylint custom_components/localtuya --rcfile=pylint.rc
[testenv:typing]
commands =