Compare commits
44 Commits
23b0cfde6d
...
master
Author | SHA1 | Date | |
---|---|---|---|
79804e107e
|
|||
|
9eabc43da2 | ||
|
2217b09286 | ||
|
e6bcaa1127 | ||
|
5f2c027c1e | ||
|
01a6f0fa15 | ||
|
12d40f81af | ||
|
6202c74e39 | ||
|
1369245b15 | ||
|
096568bd20 | ||
|
65f3dafd04 | ||
|
b01861d53e | ||
|
42b6eb1fc1 | ||
|
d57932e8e4 | ||
|
e1fb0f2ccb | ||
|
1da7b313d4 | ||
|
87d61eea6f | ||
34ab80ad93
|
|||
f27de00793
|
|||
112fbeb0dd
|
|||
e5c98ab09d
|
|||
5beec4d43c
|
|||
6ea37e31c2
|
|||
65f61925c9
|
|||
f140290f13
|
|||
24e2887506
|
|||
a065a45764
|
|||
|
d2fa4dc2a0 | ||
|
7b37f07fd0 | ||
|
490ad9ed5a | ||
|
9c0e733804 | ||
|
e5009fd3f8 | ||
|
1c173d949a | ||
|
180b6645d2 | ||
|
bf8f2bb06c | ||
|
0c8fd9d0ec | ||
|
69774f574a | ||
|
137ff78874 | ||
|
fa8217cdfb | ||
|
b3afa14095 | ||
|
62011a9d6b | ||
|
2b3ade04c7 | ||
|
6024604f33 | ||
|
494d334e9e |
28
README.md
28
README.md
@@ -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:
|
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/
|
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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -155,6 +155,32 @@ You can obtain Energy monitoring (voltage, current) in two different ways:
|
|||||||
unit_of_measurement: 'W'
|
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
|
# 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:
|
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:
|
||||||
|
@@ -26,6 +26,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import DeviceEntry
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
from homeassistant.helpers.service import async_register_admin_service
|
||||||
|
|
||||||
from .cloud_api import TuyaCloudApi
|
from .cloud_api import TuyaCloudApi
|
||||||
from .common import TuyaDevice, async_config_entry_by_device_id
|
from .common import TuyaDevice, async_config_entry_by_device_id
|
||||||
@@ -139,15 +140,16 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
)
|
)
|
||||||
new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000))
|
new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000))
|
||||||
hass.config_entries.async_update_entry(entry, data=new_data)
|
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]
|
elif device_id in hass.data[DOMAIN][TUYA_DEVICES]:
|
||||||
if not device.connected:
|
_LOGGER.debug("Device %s found with IP %s", device_id, device_ip)
|
||||||
device.async_connect()
|
|
||||||
|
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):
|
def _shutdown(event):
|
||||||
"""Clean up resources when shutting down."""
|
"""Clean up resources when shutting down."""
|
||||||
@@ -161,13 +163,14 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
|
|
||||||
async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL)
|
async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL)
|
||||||
|
|
||||||
hass.helpers.service.async_register_admin_service(
|
async_register_admin_service(
|
||||||
|
hass,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_RELOAD,
|
SERVICE_RELOAD,
|
||||||
_handle_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
|
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()
|
res = await tuya_api.async_get_access_token()
|
||||||
if res != "ok":
|
if res != "ok":
|
||||||
_LOGGER.error("Cloud API connection failed: %s", res)
|
_LOGGER.error("Cloud API connection failed: %s", res)
|
||||||
_LOGGER.info("Cloud API connection succeeded.")
|
else:
|
||||||
res = await tuya_api.async_get_devices_list()
|
_LOGGER.info("Cloud API connection succeeded.")
|
||||||
|
res = await tuya_api.async_get_devices_list()
|
||||||
hass.data[DOMAIN][DATA_CLOUD] = tuya_api
|
hass.data[DOMAIN][DATA_CLOUD] = tuya_api
|
||||||
|
|
||||||
async def setup_entities(device_ids):
|
platforms = set()
|
||||||
platforms = set()
|
for dev_id in entry.data[CONF_DEVICES].keys():
|
||||||
for dev_id in device_ids:
|
entities = entry.data[CONF_DEVICES][dev_id][CONF_ENTITIES]
|
||||||
entities = entry.data[CONF_DEVICES][dev_id][CONF_ENTITIES]
|
platforms = platforms.union(
|
||||||
platforms = platforms.union(
|
set(entity[CONF_PLATFORM] for entity in entities)
|
||||||
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
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
hass.data[DOMAIN][TUYA_DEVICES][dev_id] = TuyaDevice(hass, entry, dev_id)
|
||||||
|
|
||||||
|
# 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:
|
for dev_id in device_ids:
|
||||||
hass.data[DOMAIN][TUYA_DEVICES][dev_id].async_connect()
|
hass.data[DOMAIN][TUYA_DEVICES][dev_id].async_connect()
|
||||||
|
|
||||||
|
@@ -11,18 +11,20 @@ from homeassistant.components.climate import (
|
|||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
CURRENT_HVAC_HEAT,
|
HVACAction,
|
||||||
CURRENT_HVAC_IDLE,
|
HVACMode,
|
||||||
HVAC_MODE_AUTO,
|
|
||||||
HVAC_MODE_HEAT,
|
|
||||||
HVAC_MODE_OFF,
|
|
||||||
PRESET_AWAY,
|
PRESET_AWAY,
|
||||||
PRESET_ECO,
|
PRESET_ECO,
|
||||||
PRESET_HOME,
|
PRESET_HOME,
|
||||||
PRESET_NONE,
|
PRESET_NONE,
|
||||||
SUPPORT_PRESET_MODE,
|
ClimateEntityFeature,
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
FAN_AUTO,
|
||||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
FAN_LOW,
|
||||||
|
FAN_MEDIUM,
|
||||||
|
FAN_HIGH,
|
||||||
|
FAN_TOP,
|
||||||
|
SWING_ON,
|
||||||
|
SWING_OFF,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
@@ -30,15 +32,17 @@ from homeassistant.const import (
|
|||||||
PRECISION_HALVES,
|
PRECISION_HALVES,
|
||||||
PRECISION_TENTHS,
|
PRECISION_TENTHS,
|
||||||
PRECISION_WHOLE,
|
PRECISION_WHOLE,
|
||||||
TEMP_CELSIUS,
|
UnitOfTemperature,
|
||||||
TEMP_FAHRENHEIT,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .common import LocalTuyaEntity, async_setup_entry
|
from .common import LocalTuyaEntity, async_setup_entry
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CURRENT_TEMPERATURE_DP,
|
CONF_CURRENT_TEMPERATURE_DP,
|
||||||
|
CONF_TEMP_MAX,
|
||||||
|
CONF_TEMP_MIN,
|
||||||
CONF_ECO_DP,
|
CONF_ECO_DP,
|
||||||
CONF_ECO_VALUE,
|
CONF_ECO_VALUE,
|
||||||
|
CONF_ECO_VALUE_BOOL,
|
||||||
CONF_HEURISTIC_ACTION,
|
CONF_HEURISTIC_ACTION,
|
||||||
CONF_HVAC_ACTION_DP,
|
CONF_HVAC_ACTION_DP,
|
||||||
CONF_HVAC_ACTION_SET,
|
CONF_HVAC_ACTION_SET,
|
||||||
@@ -52,52 +56,91 @@ from .const import (
|
|||||||
CONF_TARGET_PRECISION,
|
CONF_TARGET_PRECISION,
|
||||||
CONF_TARGET_TEMPERATURE_DP,
|
CONF_TARGET_TEMPERATURE_DP,
|
||||||
CONF_TEMPERATURE_STEP,
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
HVAC_MODE_SETS = {
|
HVAC_MODE_SETS = {
|
||||||
"manual/auto": {
|
"manual/auto": {
|
||||||
HVAC_MODE_HEAT: "manual",
|
HVACMode.HEAT: "manual",
|
||||||
HVAC_MODE_AUTO: "auto",
|
HVACMode.AUTO: "auto",
|
||||||
},
|
},
|
||||||
"Manual/Auto": {
|
"Manual/Auto": {
|
||||||
HVAC_MODE_HEAT: "Manual",
|
HVACMode.HEAT: "Manual",
|
||||||
HVAC_MODE_AUTO: "Auto",
|
HVACMode.AUTO: "Auto",
|
||||||
|
},
|
||||||
|
"MANUAL/AUTO": {
|
||||||
|
HVACMode.HEAT: "MANUAL",
|
||||||
|
HVACMode.AUTO: "AUTO",
|
||||||
},
|
},
|
||||||
"Manual/Program": {
|
"Manual/Program": {
|
||||||
HVAC_MODE_HEAT: "Manual",
|
HVACMode.HEAT: "Manual",
|
||||||
HVAC_MODE_AUTO: "Program",
|
HVACMode.AUTO: "Program",
|
||||||
},
|
},
|
||||||
"m/p": {
|
"m/p": {
|
||||||
HVAC_MODE_HEAT: "m",
|
HVACMode.HEAT: "m",
|
||||||
HVAC_MODE_AUTO: "p",
|
HVACMode.AUTO: "p",
|
||||||
},
|
},
|
||||||
"True/False": {
|
"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": {
|
"1/0": {
|
||||||
HVAC_MODE_HEAT: "1",
|
HVACMode.HEAT: "1",
|
||||||
HVAC_MODE_AUTO: "0",
|
HVACMode.AUTO: "0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
HVAC_ACTION_SETS = {
|
HVAC_ACTION_SETS = {
|
||||||
"True/False": {
|
"True/False": {
|
||||||
CURRENT_HVAC_HEAT: True,
|
HVACAction.HEATING: True,
|
||||||
CURRENT_HVAC_IDLE: False,
|
HVACAction.IDLE: False,
|
||||||
},
|
},
|
||||||
"open/close": {
|
"open/close": {
|
||||||
CURRENT_HVAC_HEAT: "open",
|
HVACAction.HEATING: "open",
|
||||||
CURRENT_HVAC_IDLE: "close",
|
HVACAction.IDLE: "close",
|
||||||
},
|
},
|
||||||
"heating/no_heating": {
|
"heating/no_heating": {
|
||||||
CURRENT_HVAC_HEAT: "heating",
|
HVACAction.HEATING: "heating",
|
||||||
CURRENT_HVAC_IDLE: "no_heating",
|
HVACAction.IDLE: "no_heating",
|
||||||
},
|
},
|
||||||
"Heat/Warming": {
|
"Heat/Warming": {
|
||||||
CURRENT_HVAC_HEAT: "Heat",
|
HVACAction.HEATING: "Heat",
|
||||||
CURRENT_HVAC_IDLE: "Warming",
|
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 = {
|
PRESET_SETS = {
|
||||||
"Manual/Holiday/Program": {
|
"Manual/Holiday/Program": {
|
||||||
@@ -105,6 +148,11 @@ PRESET_SETS = {
|
|||||||
PRESET_HOME: "Program",
|
PRESET_HOME: "Program",
|
||||||
PRESET_NONE: "Manual",
|
PRESET_NONE: "Manual",
|
||||||
},
|
},
|
||||||
|
"smart/holiday/hold": {
|
||||||
|
PRESET_AWAY: "holiday",
|
||||||
|
PRESET_HOME: "smart",
|
||||||
|
PRESET_NONE: "hold",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
TEMPERATURE_CELSIUS = "celsius"
|
TEMPERATURE_CELSIUS = "celsius"
|
||||||
@@ -121,26 +169,31 @@ def flow_schema(dps):
|
|||||||
return {
|
return {
|
||||||
vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
|
vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
|
||||||
vol.Optional(CONF_CURRENT_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]
|
[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_MAX_TEMP_DP): vol.In(dps),
|
||||||
vol.Optional(CONF_MIN_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]
|
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_HVAC_MODE_DP): vol.In(dps),
|
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_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_DP): vol.In(dps),
|
||||||
vol.Optional(CONF_HVAC_ACTION_SET): vol.In(list(HVAC_ACTION_SETS.keys())),
|
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_DP): vol.In(dps),
|
||||||
vol.Optional(CONF_ECO_VALUE): str,
|
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_DP): vol.In(dps),
|
||||||
vol.Optional(CONF_PRESET_SET): vol.In(list(PRESET_SETS.keys())),
|
vol.Optional(CONF_PRESET_SET): vol.In(list(PRESET_SETS.keys())),
|
||||||
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
|
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
|
||||||
[TEMPERATURE_CELSIUS, TEMPERATURE_FAHRENHEIT]
|
[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]
|
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_HEURISTIC_ACTION): bool,
|
vol.Optional(CONF_HEURISTIC_ACTION): bool,
|
||||||
@@ -163,6 +216,8 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._hvac_mode = None
|
self._hvac_mode = None
|
||||||
|
self._fan_mode = None
|
||||||
|
self._swing_mode = None
|
||||||
self._preset_mode = None
|
self._preset_mode = None
|
||||||
self._hvac_action = None
|
self._hvac_action = None
|
||||||
self._precision = self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
|
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._conf_hvac_mode_set = HVAC_MODE_SETS.get(
|
||||||
self._config.get(CONF_HVAC_MODE_SET), {}
|
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_dp = self._config.get(CONF_PRESET_DP)
|
||||||
self._conf_preset_set = PRESET_SETS.get(self._config.get(CONF_PRESET_SET), {})
|
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)
|
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_dp = self._config.get(CONF_ECO_DP)
|
||||||
self._conf_eco_value = self._config.get(CONF_ECO_VALUE, "ECO")
|
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(
|
self._has_presets = self.has_config(CONF_ECO_DP) or self.has_config(
|
||||||
CONF_PRESET_DP
|
CONF_PRESET_DP
|
||||||
)
|
)
|
||||||
@@ -189,13 +253,17 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
supported_features = 0
|
supported_features = ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
|
||||||
if self.has_config(CONF_TARGET_TEMPERATURE_DP):
|
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):
|
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):
|
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
|
return supported_features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -215,8 +283,8 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
self._config.get(CONF_TEMPERATURE_UNIT, DEFAULT_TEMPERATURE_UNIT)
|
self._config.get(CONF_TEMPERATURE_UNIT, DEFAULT_TEMPERATURE_UNIT)
|
||||||
== TEMPERATURE_FAHRENHEIT
|
== TEMPERATURE_FAHRENHEIT
|
||||||
):
|
):
|
||||||
return TEMP_FAHRENHEIT
|
return UnitOfTemperature.FAHRENHEIT
|
||||||
return TEMP_CELSIUS
|
return UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self):
|
def hvac_mode(self):
|
||||||
@@ -228,7 +296,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
if not self.has_config(CONF_HVAC_MODE_DP):
|
if not self.has_config(CONF_HVAC_MODE_DP):
|
||||||
return None
|
return None
|
||||||
return list(self._conf_hvac_mode_set) + [HVAC_MODE_OFF]
|
return list(self._conf_hvac_mode_set) + [HVACMode.OFF]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_action(self):
|
def hvac_action(self):
|
||||||
@@ -237,22 +305,22 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
Need to be one of CURRENT_HVAC_*.
|
Need to be one of CURRENT_HVAC_*.
|
||||||
"""
|
"""
|
||||||
if self._config.get(CONF_HEURISTIC_ACTION, False):
|
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 < (
|
if self._current_temperature < (
|
||||||
self._target_temperature - self._precision
|
self._target_temperature - self._precision
|
||||||
):
|
):
|
||||||
self._hvac_action = CURRENT_HVAC_HEAT
|
self._hvac_action = HVACAction.HEATING
|
||||||
if self._current_temperature == (
|
if self._current_temperature == (
|
||||||
self._target_temperature - self._precision
|
self._target_temperature - self._precision
|
||||||
):
|
):
|
||||||
if self._hvac_action == CURRENT_HVAC_HEAT:
|
if self._hvac_action == HVACAction.HEATING:
|
||||||
self._hvac_action = CURRENT_HVAC_HEAT
|
self._hvac_action = HVACAction.HEATING
|
||||||
if self._hvac_action == CURRENT_HVAC_IDLE:
|
if self._hvac_action == HVACAction.IDLE:
|
||||||
self._hvac_action = CURRENT_HVAC_IDLE
|
self._hvac_action = HVACAction.IDLE
|
||||||
if (
|
if (
|
||||||
self._current_temperature + self._precision
|
self._current_temperature + self._precision
|
||||||
) > self._target_temperature:
|
) > self._target_temperature:
|
||||||
self._hvac_action = CURRENT_HVAC_IDLE
|
self._hvac_action = HVACAction.IDLE
|
||||||
return self._hvac_action
|
return self._hvac_action
|
||||||
return self._hvac_action
|
return self._hvac_action
|
||||||
|
|
||||||
@@ -289,12 +357,26 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return NotImplementedError()
|
return self._fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_modes(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""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):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -304,13 +386,21 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
temperature, self._config[CONF_TARGET_TEMPERATURE_DP]
|
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."""
|
"""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):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target operation 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)
|
await self._device.set_dp(False, self._dp_id)
|
||||||
return
|
return
|
||||||
if not self._state and self._conf_hvac_mode_dp != self._dp_id:
|
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
|
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:
|
async def async_turn_on(self) -> None:
|
||||||
"""Turn the entity on."""
|
"""Turn the entity on."""
|
||||||
await self._device.set_dp(True, self._dp_id)
|
await self._device.set_dp(True, self._dp_id)
|
||||||
@@ -332,7 +434,10 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
async def async_set_preset_mode(self, preset_mode):
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
"""Set new target preset mode."""
|
"""Set new target preset mode."""
|
||||||
if preset_mode == PRESET_ECO:
|
if preset_mode == PRESET_ECO:
|
||||||
await self._device.set_dp(self._conf_eco_value, self._conf_eco_dp)
|
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
|
return
|
||||||
await self._device.set_dp(
|
await self._device.set_dp(
|
||||||
self._conf_preset_set[preset_mode], self._conf_preset_dp
|
self._conf_preset_set[preset_mode], self._conf_preset_dp
|
||||||
@@ -343,14 +448,14 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
if self.has_config(CONF_MIN_TEMP_DP):
|
if self.has_config(CONF_MIN_TEMP_DP):
|
||||||
return self.dps_conf(CONF_MIN_TEMP_DP)
|
return self.dps_conf(CONF_MIN_TEMP_DP)
|
||||||
return DEFAULT_MIN_TEMP
|
return self._config[CONF_TEMP_MIN]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
if self.has_config(CONF_MAX_TEMP_DP):
|
if self.has_config(CONF_MAX_TEMP_DP):
|
||||||
return self.dps_conf(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):
|
def status_updated(self):
|
||||||
"""Device status was updated."""
|
"""Device status was updated."""
|
||||||
@@ -383,7 +488,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
# Update the HVAC status
|
# Update the HVAC status
|
||||||
if self.has_config(CONF_HVAC_MODE_DP):
|
if self.has_config(CONF_HVAC_MODE_DP):
|
||||||
if not self._state:
|
if not self._state:
|
||||||
self._hvac_mode = HVAC_MODE_OFF
|
self._hvac_mode = HVACMode.OFF
|
||||||
else:
|
else:
|
||||||
for mode, value in self._conf_hvac_mode_set.items():
|
for mode, value in self._conf_hvac_mode_set.items():
|
||||||
if self.dps_conf(CONF_HVAC_MODE_DP) == value:
|
if self.dps_conf(CONF_HVAC_MODE_DP) == value:
|
||||||
@@ -391,7 +496,28 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# in case hvac mode and preset share the same dp
|
# 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
|
# Update the current action
|
||||||
for action, value in self._conf_hvac_action_set.items():
|
for action, value in self._conf_hvac_action_set.items():
|
||||||
|
@@ -101,7 +101,10 @@ class TuyaCloudApi:
|
|||||||
|
|
||||||
async def async_get_access_token(self):
|
async def async_get_access_token(self):
|
||||||
"""Obtain a valid access token."""
|
"""Obtain a valid access token."""
|
||||||
resp = await self.async_make_request("GET", "/v1.0/token?grant_type=1")
|
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:
|
if not resp.ok:
|
||||||
return "Request failed, status " + str(resp.status)
|
return "Request failed, status " + str(resp.status)
|
||||||
|
@@ -124,8 +124,10 @@ def async_config_entry_by_device_id(hass, device_id):
|
|||||||
"""Look up config entry by device id."""
|
"""Look up config entry by device id."""
|
||||||
current_entries = hass.config_entries.async_entries(DOMAIN)
|
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
for entry in current_entries:
|
for entry in current_entries:
|
||||||
if device_id in entry.data[CONF_DEVICES]:
|
if device_id in entry.data.get(CONF_DEVICES, []):
|
||||||
return entry
|
return entry
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(f"Missing device configuration for device_id {device_id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@@ -53,6 +53,7 @@ CONF_BRIGHTNESS_LOWER = "brightness_lower"
|
|||||||
CONF_BRIGHTNESS_UPPER = "brightness_upper"
|
CONF_BRIGHTNESS_UPPER = "brightness_upper"
|
||||||
CONF_COLOR = "color"
|
CONF_COLOR = "color"
|
||||||
CONF_COLOR_MODE = "color_mode"
|
CONF_COLOR_MODE = "color_mode"
|
||||||
|
CONF_COLOR_MODE_SET = "color_mode_set"
|
||||||
CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin"
|
CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin"
|
||||||
CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin"
|
CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin"
|
||||||
CONF_COLOR_TEMP_REVERSE = "color_temp_reverse"
|
CONF_COLOR_TEMP_REVERSE = "color_temp_reverse"
|
||||||
@@ -91,10 +92,16 @@ CONF_CURRENT_TEMPERATURE_DP = "current_temperature_dp"
|
|||||||
CONF_TEMPERATURE_STEP = "temperature_step"
|
CONF_TEMPERATURE_STEP = "temperature_step"
|
||||||
CONF_MAX_TEMP_DP = "max_temperature_dp"
|
CONF_MAX_TEMP_DP = "max_temperature_dp"
|
||||||
CONF_MIN_TEMP_DP = "min_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_PRECISION = "precision"
|
||||||
CONF_TARGET_PRECISION = "target_precision"
|
CONF_TARGET_PRECISION = "target_precision"
|
||||||
CONF_HVAC_MODE_DP = "hvac_mode_dp"
|
CONF_HVAC_MODE_DP = "hvac_mode_dp"
|
||||||
CONF_HVAC_MODE_SET = "hvac_mode_set"
|
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_DP = "preset_dp"
|
||||||
CONF_PRESET_SET = "preset_set"
|
CONF_PRESET_SET = "preset_set"
|
||||||
CONF_HEURISTIC_ACTION = "heuristic_action"
|
CONF_HEURISTIC_ACTION = "heuristic_action"
|
||||||
@@ -102,6 +109,7 @@ CONF_HVAC_ACTION_DP = "hvac_action_dp"
|
|||||||
CONF_HVAC_ACTION_SET = "hvac_action_set"
|
CONF_HVAC_ACTION_SET = "hvac_action_set"
|
||||||
CONF_ECO_DP = "eco_dp"
|
CONF_ECO_DP = "eco_dp"
|
||||||
CONF_ECO_VALUE = "eco_value"
|
CONF_ECO_VALUE = "eco_value"
|
||||||
|
CONF_ECO_VALUE_BOOL = "eco_value_bool"
|
||||||
|
|
||||||
# vacuum
|
# vacuum
|
||||||
CONF_POWERGO_DP = "powergo_dp"
|
CONF_POWERGO_DP = "powergo_dp"
|
||||||
|
@@ -8,11 +8,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SUPPORT_CLOSE,
|
CoverEntity, CoverEntityFeature,
|
||||||
SUPPORT_OPEN,
|
|
||||||
SUPPORT_SET_POSITION,
|
|
||||||
SUPPORT_STOP,
|
|
||||||
CoverEntity,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .common import LocalTuyaEntity, async_setup_entry
|
from .common import LocalTuyaEntity, async_setup_entry
|
||||||
@@ -80,9 +76,9 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""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:
|
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
|
return supported_features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -9,9 +9,7 @@ from homeassistant.components.fan import (
|
|||||||
DIRECTION_FORWARD,
|
DIRECTION_FORWARD,
|
||||||
DIRECTION_REVERSE,
|
DIRECTION_REVERSE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SUPPORT_DIRECTION,
|
FanEntityFeature,
|
||||||
SUPPORT_OSCILLATE,
|
|
||||||
SUPPORT_SET_SPEED,
|
|
||||||
FanEntity,
|
FanEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
@@ -189,18 +187,21 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
|
|||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> FanEntityFeature:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
features = 0
|
features = FanEntityFeature(0)
|
||||||
|
|
||||||
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
|
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
|
||||||
features |= SUPPORT_OSCILLATE
|
features |= FanEntityFeature.OSCILLATE
|
||||||
|
|
||||||
if self.has_config(CONF_FAN_SPEED_CONTROL):
|
if self.has_config(CONF_FAN_SPEED_CONTROL):
|
||||||
features |= SUPPORT_SET_SPEED
|
features |= FanEntityFeature.SET_SPEED
|
||||||
|
|
||||||
if self.has_config(CONF_FAN_DIRECTION):
|
if self.has_config(CONF_FAN_DIRECTION):
|
||||||
features |= SUPPORT_DIRECTION
|
features |= FanEntityFeature.DIRECTION
|
||||||
|
|
||||||
|
features |= FanEntityFeature.TURN_OFF
|
||||||
|
features |= FanEntityFeature.TURN_ON
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
|
@@ -1,21 +1,19 @@
|
|||||||
"""Platform to locally control Tuya-based light devices."""
|
"""Platform to locally control Tuya-based light devices."""
|
||||||
import logging
|
import logging
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SUPPORT_BRIGHTNESS,
|
|
||||||
SUPPORT_COLOR,
|
|
||||||
SUPPORT_COLOR_TEMP,
|
|
||||||
SUPPORT_EFFECT,
|
|
||||||
LightEntity,
|
LightEntity,
|
||||||
|
LightEntityFeature,
|
||||||
|
ColorMode,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_SCENE
|
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_MAX_KELVIN,
|
||||||
CONF_COLOR_TEMP_MIN_KELVIN,
|
CONF_COLOR_TEMP_MIN_KELVIN,
|
||||||
CONF_COLOR_TEMP_REVERSE,
|
CONF_COLOR_TEMP_REVERSE,
|
||||||
CONF_MUSIC_MODE,
|
CONF_MUSIC_MODE, CONF_COLOR_MODE_SET,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -41,6 +39,7 @@ DEFAULT_COLOR_TEMP_REVERSE = False
|
|||||||
DEFAULT_LOWER_BRIGHTNESS = 29
|
DEFAULT_LOWER_BRIGHTNESS = 29
|
||||||
DEFAULT_UPPER_BRIGHTNESS = 1000
|
DEFAULT_UPPER_BRIGHTNESS = 1000
|
||||||
|
|
||||||
|
MODE_MANUAL = "manual"
|
||||||
MODE_COLOR = "colour"
|
MODE_COLOR = "colour"
|
||||||
MODE_MUSIC = "music"
|
MODE_MUSIC = "music"
|
||||||
MODE_SCENE = "scene"
|
MODE_SCENE = "scene"
|
||||||
@@ -49,6 +48,8 @@ MODE_WHITE = "white"
|
|||||||
SCENE_CUSTOM = "Custom"
|
SCENE_CUSTOM = "Custom"
|
||||||
SCENE_MUSIC = "Music"
|
SCENE_MUSIC = "Music"
|
||||||
|
|
||||||
|
MODES_SET = {"Colour, Music, Scene and White": 0, "Manual, Music, Scene and White": 1}
|
||||||
|
|
||||||
SCENE_LIST_RGBW_1000 = {
|
SCENE_LIST_RGBW_1000 = {
|
||||||
"Night": "000e0d0000000000000000c80000",
|
"Night": "000e0d0000000000000000c80000",
|
||||||
"Read": "010e0d0000000000000003e801f4",
|
"Read": "010e0d0000000000000003e801f4",
|
||||||
@@ -91,6 +92,22 @@ SCENE_LIST_RGB_1000 = {
|
|||||||
+ "0000000",
|
+ "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):
|
def map_range(value, from_lower, from_upper, to_lower, to_upper):
|
||||||
"""Map a value in one range to another."""
|
"""Map a value in one range to another."""
|
||||||
@@ -162,10 +179,12 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
self._color_temp_reverse = self._config.get(
|
self._color_temp_reverse = self._config.get(
|
||||||
CONF_COLOR_TEMP_REVERSE, DEFAULT_COLOR_TEMP_REVERSE
|
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._hs = None
|
||||||
self._effect = None
|
self._effect = None
|
||||||
self._effect_list = []
|
self._effect_list = []
|
||||||
self._scenes = None
|
self._scenes = {}
|
||||||
|
|
||||||
if self.has_config(CONF_SCENE):
|
if self.has_config(CONF_SCENE):
|
||||||
if self._config.get(CONF_SCENE) < 20:
|
if self._config.get(CONF_SCENE) < 20:
|
||||||
self._scenes = SCENE_LIST_RGBW_255
|
self._scenes = SCENE_LIST_RGBW_255
|
||||||
@@ -174,6 +193,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
else:
|
else:
|
||||||
self._scenes = SCENE_LIST_RGBW_1000
|
self._scenes = SCENE_LIST_RGBW_1000
|
||||||
self._effect_list = list(self._scenes.keys())
|
self._effect_list = list(self._scenes.keys())
|
||||||
|
|
||||||
if self._config.get(CONF_MUSIC_MODE):
|
if self._config.get(CONF_MUSIC_MODE):
|
||||||
self._effect_list.append(SCENE_MUSIC)
|
self._effect_list.append(SCENE_MUSIC)
|
||||||
|
|
||||||
@@ -197,8 +217,8 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
if self.is_color_mode:
|
if self.is_color_mode:
|
||||||
return self._hs
|
return self._hs
|
||||||
if (
|
if (
|
||||||
self.supported_features & SUPPORT_COLOR
|
ColorMode.HS in self.supported_color_modes
|
||||||
and not self.supported_features & SUPPORT_COLOR_TEMP
|
and not ColorMode.COLOR_TEMP in self.supported_color_modes
|
||||||
):
|
):
|
||||||
return [0, 0]
|
return [0, 0]
|
||||||
return None
|
return None
|
||||||
@@ -241,45 +261,76 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
@property
|
@property
|
||||||
def effect_list(self):
|
def effect_list(self):
|
||||||
"""Return the list of supported effects for this light."""
|
"""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
|
@property
|
||||||
def supported_features(self):
|
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
|
||||||
"""Flag supported features."""
|
"""Flag supported color modes."""
|
||||||
supports = 0
|
color_modes: set[ColorMode] = set()
|
||||||
if self.has_config(CONF_BRIGHTNESS):
|
|
||||||
supports |= SUPPORT_BRIGHTNESS
|
|
||||||
if self.has_config(CONF_COLOR_TEMP):
|
if self.has_config(CONF_COLOR_TEMP):
|
||||||
supports |= SUPPORT_COLOR_TEMP
|
color_modes.add(ColorMode.COLOR_TEMP)
|
||||||
if self.has_config(CONF_COLOR):
|
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):
|
if self.has_config(CONF_SCENE) or self.has_config(CONF_MUSIC_MODE):
|
||||||
supports |= SUPPORT_EFFECT
|
supports |= LightEntityFeature.EFFECT
|
||||||
return supports
|
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
|
@property
|
||||||
def is_white_mode(self):
|
def is_white_mode(self):
|
||||||
"""Return true if the light is in white mode."""
|
"""Return true if the light is in white mode."""
|
||||||
color_mode = self.__get_color_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
|
@property
|
||||||
def is_color_mode(self):
|
def is_color_mode(self):
|
||||||
"""Return true if the light is in color mode."""
|
"""Return true if the light is in color mode."""
|
||||||
color_mode = self.__get_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
|
@property
|
||||||
def is_scene_mode(self):
|
def is_scene_mode(self):
|
||||||
"""Return true if the light is in scene mode."""
|
"""Return true if the light is in scene mode."""
|
||||||
color_mode = self.__get_color_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
|
@property
|
||||||
def is_music_mode(self):
|
def is_music_mode(self):
|
||||||
"""Return true if the light is in music mode."""
|
"""Return true if the light is in music mode."""
|
||||||
color_mode = self.__get_color_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):
|
def __is_color_rgb_encoded(self):
|
||||||
return len(self.dps_conf(CONF_COLOR)) > 12
|
return len(self.dps_conf(CONF_COLOR)) > 12
|
||||||
@@ -294,7 +345,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
return (
|
return (
|
||||||
self.dps_conf(CONF_COLOR_MODE)
|
self.dps_conf(CONF_COLOR_MODE)
|
||||||
if self.has_config(CONF_COLOR_MODE)
|
if self.has_config(CONF_COLOR_MODE)
|
||||||
else MODE_WHITE
|
else self._modes.white
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
@@ -304,7 +355,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
states[self._dp_id] = True
|
states[self._dp_id] = True
|
||||||
features = self.supported_features
|
features = self.supported_features
|
||||||
brightness = None
|
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])
|
scene = self._scenes.get(kwargs[ATTR_EFFECT])
|
||||||
if scene is not None:
|
if scene is not None:
|
||||||
if scene.startswith(MODE_SCENE):
|
if scene.startswith(MODE_SCENE):
|
||||||
@@ -315,7 +366,11 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
elif kwargs[ATTR_EFFECT] == SCENE_MUSIC:
|
elif kwargs[ATTR_EFFECT] == SCENE_MUSIC:
|
||||||
states[self._config.get(CONF_COLOR_MODE)] = MODE_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(
|
brightness = map_range(
|
||||||
int(kwargs[ATTR_BRIGHTNESS]),
|
int(kwargs[ATTR_BRIGHTNESS]),
|
||||||
0,
|
0,
|
||||||
@@ -347,7 +402,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
states[self._config.get(CONF_COLOR)] = color
|
states[self._config.get(CONF_COLOR)] = color
|
||||||
states[self._config.get(CONF_COLOR_MODE)] = MODE_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:
|
if brightness is None:
|
||||||
brightness = self._brightness
|
brightness = self._brightness
|
||||||
hs = kwargs[ATTR_HS_COLOR]
|
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)] = color
|
||||||
states[self._config.get(CONF_COLOR_MODE)] = MODE_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:
|
if brightness is None:
|
||||||
brightness = self._brightness
|
brightness = self._brightness
|
||||||
mired = int(kwargs[ATTR_COLOR_TEMP])
|
mired = int(kwargs[ColorMode.COLOR_TEMP])
|
||||||
if self._color_temp_reverse:
|
if self._color_temp_reverse:
|
||||||
mired = self._max_mired - (mired - self._min_mired)
|
mired = self._max_mired - (mired - self._min_mired)
|
||||||
if mired < self._min_mired:
|
if mired < self._min_mired:
|
||||||
@@ -403,10 +458,14 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
self._state = self.dps(self._dp_id)
|
self._state = self.dps(self._dp_id)
|
||||||
supported = self.supported_features
|
supported = self.supported_features
|
||||||
self._effect = None
|
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)
|
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)
|
color = self.dps_conf(CONF_COLOR)
|
||||||
if color is not None and not self.is_white_mode:
|
if color is not None and not self.is_white_mode:
|
||||||
if self.__is_color_rgb_encoded():
|
if self.__is_color_rgb_encoded():
|
||||||
@@ -422,10 +481,10 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
self._hs = [hue, sat / 10.0]
|
self._hs = [hue, sat / 10.0]
|
||||||
self._brightness = value
|
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)
|
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:
|
if self.dps_conf(CONF_COLOR_MODE) != MODE_SCENE:
|
||||||
self._effect = self.__find_scene_by_scene_data(
|
self._effect = self.__find_scene_by_scene_data(
|
||||||
self.dps_conf(CONF_COLOR_MODE)
|
self.dps_conf(CONF_COLOR_MODE)
|
||||||
@@ -440,7 +499,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
elif SCENE_CUSTOM in self._effect_list:
|
elif SCENE_CUSTOM in self._effect_list:
|
||||||
self._effect_list.remove(SCENE_CUSTOM)
|
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
|
self._effect = SCENE_MUSIC
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,5 +10,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"issue_tracker": "https://github.com/rospogrigio/localtuya/issues",
|
"issue_tracker": "https://github.com/rospogrigio/localtuya/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "5.2.1"
|
"version": "5.2.3"
|
||||||
}
|
}
|
||||||
|
@@ -121,6 +121,7 @@
|
|||||||
"preset_set": "Presets Set (optional)",
|
"preset_set": "Presets Set (optional)",
|
||||||
"eco_dp": "Eco DP (optional)",
|
"eco_dp": "Eco DP (optional)",
|
||||||
"eco_value": "Eco value (optional)",
|
"eco_value": "Eco value (optional)",
|
||||||
|
"eco_value_bool":"Eco value is a boolean type (true/false)",
|
||||||
"heuristic_action": "Enable heuristic action (optional)",
|
"heuristic_action": "Enable heuristic action (optional)",
|
||||||
"dps_default_value": "Default value when un-initialised (optional)",
|
"dps_default_value": "Default value when un-initialised (optional)",
|
||||||
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",
|
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",
|
||||||
|
@@ -72,7 +72,13 @@
|
|||||||
"title": "Edit a new device",
|
"title": "Edit a new device",
|
||||||
"description": "Pick the configured device you wish to edit.",
|
"description": "Pick the configured device you wish to edit.",
|
||||||
"data": {
|
"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": {
|
"cloud_setup": {
|
||||||
@@ -174,19 +180,26 @@
|
|||||||
"current_temperature_dp": "Current Temperature",
|
"current_temperature_dp": "Current Temperature",
|
||||||
"target_temperature_dp": "Target Temperature",
|
"target_temperature_dp": "Target Temperature",
|
||||||
"temperature_step": "Temperature Step (optional)",
|
"temperature_step": "Temperature Step (optional)",
|
||||||
"max_temperature_dp": "Max Temperature (optional)",
|
"max_temperature_dp": "Max Temperature DP (optional)",
|
||||||
"min_temperature_dp": "Min Temperature (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)",
|
"precision": "Precision (optional, for DPs values)",
|
||||||
"target_precision": "Target Precision (optional, for DPs values)",
|
"target_precision": "Target Precision (optional, for DPs values)",
|
||||||
"temperature_unit": "Temperature Unit (optional)",
|
"temperature_unit": "Temperature Unit (optional)",
|
||||||
"hvac_mode_dp": "HVAC Mode DP (optional)",
|
"hvac_mode_dp": "HVAC Mode DP (optional)",
|
||||||
"hvac_mode_set": "HVAC Mode Set (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_dp": "HVAC Current Action DP (optional)",
|
||||||
"hvac_action_set": "HVAC Current Action Set (optional)",
|
"hvac_action_set": "HVAC Current Action Set (optional)",
|
||||||
"preset_dp": "Presets DP (optional)",
|
"preset_dp": "Presets DP (optional)",
|
||||||
"preset_set": "Presets Set (optional)",
|
"preset_set": "Presets Set (optional)",
|
||||||
"eco_dp": "Eco DP (optional)",
|
"eco_dp": "Eco DP (optional)",
|
||||||
"eco_value": "Eco value (optional)",
|
"eco_value": "Eco value (optional)",
|
||||||
|
"eco_value_bool":"Eco value is a boolean type (true/false)",
|
||||||
"heuristic_action": "Enable heuristic action (optional)",
|
"heuristic_action": "Enable heuristic action (optional)",
|
||||||
"dps_default_value": "Default value when un-initialised (optional)",
|
"dps_default_value": "Default value when un-initialised (optional)",
|
||||||
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",
|
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",
|
||||||
|
@@ -183,6 +183,7 @@
|
|||||||
"preset_set": "Set di preset (opzionale)",
|
"preset_set": "Set di preset (opzionale)",
|
||||||
"eco_dp": "DP per Eco (opzionale)",
|
"eco_dp": "DP per Eco (opzionale)",
|
||||||
"eco_value": "Valore Eco (opzionale)",
|
"eco_value": "Valore Eco (opzionale)",
|
||||||
|
"eco_value_bool":"Il valore ecologico è di tipo booleano (vero/falso)",
|
||||||
"heuristic_action": "Abilita azione euristica (opzionale)"
|
"heuristic_action": "Abilita azione euristica (opzionale)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -183,6 +183,7 @@
|
|||||||
"preset_set": "Conjunto de predefinições (opcional)",
|
"preset_set": "Conjunto de predefinições (opcional)",
|
||||||
"eco_dp": "Eco DP (opcional)",
|
"eco_dp": "Eco DP (opcional)",
|
||||||
"eco_value": "Valor eco (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)"
|
"heuristic_action": "Ativar ação heurística (opcional)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,22 +5,7 @@ from functools import partial
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from homeassistant.components.vacuum import (
|
from homeassistant.components.vacuum import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
STATE_CLEANING,
|
StateVacuumEntity, VacuumActivity, VacuumEntityFeature,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .common import LocalTuyaEntity, async_setup_entry
|
from .common import LocalTuyaEntity, async_setup_entry
|
||||||
@@ -123,21 +108,21 @@ class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
|
|||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
supported_features = (
|
supported_features = (
|
||||||
SUPPORT_START
|
VacuumEntityFeature.START
|
||||||
| SUPPORT_PAUSE
|
| VacuumEntityFeature.PAUSE
|
||||||
| SUPPORT_STOP
|
| VacuumEntityFeature.STOP
|
||||||
| SUPPORT_STATUS
|
| VacuumEntityFeature.STATUS
|
||||||
| SUPPORT_STATE
|
| VacuumEntityFeature.STATE
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.has_config(CONF_RETURN_MODE):
|
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):
|
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):
|
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):
|
if self.has_config(CONF_LOCATE_DP):
|
||||||
supported_features = supported_features | SUPPORT_LOCATE
|
supported_features = supported_features | VacuumEntityFeature.LOCATE
|
||||||
|
|
||||||
return supported_features
|
return supported_features
|
||||||
|
|
||||||
@@ -216,15 +201,15 @@ class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
|
|||||||
state_value = str(self.dps(self._dp_id))
|
state_value = str(self.dps(self._dp_id))
|
||||||
|
|
||||||
if state_value in self._idle_status_list:
|
if state_value in self._idle_status_list:
|
||||||
self._state = STATE_IDLE
|
self._state = VacuumActivity.IDLE
|
||||||
elif state_value in self._docked_status_list:
|
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]:
|
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]:
|
elif state_value == self._config[CONF_PAUSED_STATE]:
|
||||||
self._state = STATE_PAUSED
|
self._state = VacuumActivity.PAUSED
|
||||||
else:
|
else:
|
||||||
self._state = STATE_CLEANING
|
self._state = VacuumActivity.CLEANING
|
||||||
|
|
||||||
if self.has_config(CONF_BATTERY_DP):
|
if self.has_config(CONF_BATTERY_DP):
|
||||||
self._battery_level = self.dps_conf(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):
|
if self.has_config(CONF_FAULT_DP):
|
||||||
self._attrs[FAULT] = self.dps_conf(CONF_FAULT_DP)
|
self._attrs[FAULT] = self.dps_conf(CONF_FAULT_DP)
|
||||||
if self._attrs[FAULT] != 0:
|
if self._attrs[FAULT] != 0:
|
||||||
self._state = STATE_ERROR
|
self._state = VacuumActivity.ERROR
|
||||||
|
|
||||||
|
|
||||||
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaVacuum, flow_schema)
|
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaVacuum, flow_schema)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "Local Tuya",
|
"name": "Local Tuya",
|
||||||
"homeassistant": "0.116.0"
|
"homeassistant": "2024.1.0"
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = ["py38", "py39"]
|
target-version = ["py311"]
|
||||||
include = 'custom_components/localtuya/.*\.py'
|
include = 'custom_components/localtuya/.*\.py'
|
||||||
|
|
||||||
# pylint config stolen from Home Assistant
|
# pylint config stolen from Home Assistant
|
||||||
|
@@ -4,7 +4,7 @@ max-line-length = 120
|
|||||||
ignore = E203, W503
|
ignore = E203, W503
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
python_version = 3.9
|
python_version = 3.11
|
||||||
ignore_errors = true
|
ignore_errors = true
|
||||||
follow_imports = silent
|
follow_imports = silent
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
6
tox.ini
6
tox.ini
@@ -1,12 +1,12 @@
|
|||||||
[tox]
|
[tox]
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
envlist = py{38,39}, lint, typing
|
envlist = py{311}, lint, typing
|
||||||
skip_missing_interpreters = True
|
skip_missing_interpreters = True
|
||||||
cs_exclude_words = hass,unvalid
|
cs_exclude_words = hass,unvalid
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
3.9: clean, py39, lint, typing
|
3.11: clean, py311, lint, typing
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv = TOXENV,CI
|
passenv = TOXENV,CI
|
||||||
@@ -30,7 +30,7 @@ commands =
|
|||||||
flake8 custom_components
|
flake8 custom_components
|
||||||
black --fast --check .
|
black --fast --check .
|
||||||
pydocstyle -v custom_components
|
pydocstyle -v custom_components
|
||||||
pylint custom_components/localtuya --rcfile=pylint.rc
|
# pylint custom_components/localtuya --rcfile=pylint.rc
|
||||||
|
|
||||||
[testenv:typing]
|
[testenv:typing]
|
||||||
commands =
|
commands =
|
||||||
|
Reference in New Issue
Block a user