Compare commits

...

17 Commits

Author SHA1 Message Date
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
14 changed files with 246 additions and 109 deletions

View File

@@ -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:

View File

@@ -139,16 +139,17 @@ 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 = 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() device.async_connect()
def _shutdown(event): def _shutdown(event):
"""Clean up resources when shutting down.""" """Clean up resources when shutting down."""
discovery.close() discovery.close()
@@ -167,7 +168,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
_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,6 +256,7 @@ 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)
else:
_LOGGER.info("Cloud API connection succeeded.") _LOGGER.info("Cloud API connection succeeded.")
res = await tuya_api.async_get_devices_list() res = await tuya_api.async_get_devices_list()
hass.data[DOMAIN][DATA_CLOUD] = tuya_api hass.data[DOMAIN][DATA_CLOUD] = tuya_api

View File

@@ -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,13 +32,14 @@ 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_HEURISTIC_ACTION, CONF_HEURISTIC_ACTION,
@@ -52,53 +55,79 @@ 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/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",
}, },
"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",
}, },
} }
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": {
PRESET_AWAY: "Holiday", PRESET_AWAY: "Holiday",
@@ -121,16 +150,20 @@ 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),
@@ -140,7 +173,7 @@ def flow_schema(dps):
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 +196,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 +208,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)
@@ -189,13 +232,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 +262,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 +275,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 +284,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 +336,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 +365,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 +390,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)
@@ -343,14 +424,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 +464,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 +472,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():

View File

@@ -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."""
try:
resp = await self.async_make_request("GET", "/v1.0/token?grant_type=1") 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)

View File

@@ -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.warning(f"Missing device configuration for device_id {device_id}")
return None return None

View File

@@ -91,10 +91,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"

View File

@@ -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

View File

@@ -9,10 +9,7 @@ from homeassistant.components.fan import (
DIRECTION_FORWARD, DIRECTION_FORWARD,
DIRECTION_REVERSE, DIRECTION_REVERSE,
DOMAIN, DOMAIN,
SUPPORT_DIRECTION, FanEntity, FanEntityFeature,
SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
FanEntity,
) )
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range, int_states_in_range,
@@ -194,13 +191,13 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
features = 0 features = 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
return features return features

View File

@@ -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,13 +180,19 @@
"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)",

View File

@@ -11,16 +11,7 @@ from homeassistant.components.vacuum import (
STATE_IDLE, STATE_IDLE,
STATE_PAUSED, STATE_PAUSED,
STATE_RETURNING, STATE_RETURNING,
SUPPORT_BATTERY, StateVacuumEntity, VacuumEntityFeature,
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 +114,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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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 =