Revert "Rebase from upstream"

This commit is contained in:
sibowler
2022-05-29 06:57:37 +10:00
committed by GitHub
parent 87d89733ab
commit 73be0c8a45
23 changed files with 115 additions and 1315 deletions

View File

@@ -53,20 +53,6 @@ localtuya:
current: 18 # Optional
current_consumption: 19 # Optional
voltage: 20 # Optional
- platform: vacuum
friendly_name: Vacuum
id: 28
idle_status_value: "standby,sleep"
returning_status_value: "docking"
docked_status_value: "charging,chargecompleted"
battery_dp: 14
mode_dp: 27
modes: "smart,standby,chargego,wall_follow,spiral,single"
fan_speed_dp: 30
fan_speeds: "low,normal,high"
clean_time_dp: 33
clean_area_dp: 32
"""
import asyncio
import logging

View File

@@ -1,394 +0,0 @@
"""Platform to locally control Tuya-based climate devices."""
import asyncio
import logging
from functools import partial
import voluptuous as vol
from homeassistant.components.climate import (
DEFAULT_MAX_TEMP,
DEFAULT_MIN_TEMP,
DOMAIN,
ClimateEntity,
)
from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
CURRENT_HVAC_IDLE,
CURRENT_HVAC_HEAT,
PRESET_NONE,
PRESET_ECO,
PRESET_AWAY,
PRESET_HOME,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
CONF_TEMPERATURE_UNIT,
PRECISION_HALVES,
PRECISION_TENTHS,
PRECISION_WHOLE,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from .common import LocalTuyaEntity, async_setup_entry
from .const import (
CONF_CURRENT_TEMPERATURE_DP,
CONF_MAX_TEMP_DP,
CONF_MIN_TEMP_DP,
CONF_PRECISION,
CONF_TARGET_PRECISION,
CONF_TARGET_TEMPERATURE_DP,
CONF_TEMPERATURE_STEP,
CONF_HVAC_MODE_DP,
CONF_HVAC_MODE_SET,
CONF_HEURISTIC_ACTION,
CONF_HVAC_ACTION_DP,
CONF_HVAC_ACTION_SET,
CONF_ECO_DP,
CONF_ECO_VALUE,
CONF_PRESET_DP,
CONF_PRESET_SET,
)
_LOGGER = logging.getLogger(__name__)
HVAC_MODE_SETS = {
"manual/auto": {
HVAC_MODE_HEAT: "manual",
HVAC_MODE_AUTO: "auto",
},
"Manual/Auto": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Auto",
},
"Manual/Program": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Program",
},
"True/False": {
HVAC_MODE_HEAT: True,
},
}
HVAC_ACTION_SETS = {
"True/False": {
CURRENT_HVAC_HEAT: True,
CURRENT_HVAC_IDLE: False,
},
"open/close": {
CURRENT_HVAC_HEAT: "open",
CURRENT_HVAC_IDLE: "close",
},
"heating/no_heating": {
CURRENT_HVAC_HEAT: "heating",
CURRENT_HVAC_IDLE: "no_heating",
},
"Heat/Warming": {
CURRENT_HVAC_HEAT: "Heat",
CURRENT_HVAC_IDLE: "Warming",
},
}
PRESET_SETS = {
"Manual/Holiday/Program": {
PRESET_AWAY: "Holiday",
PRESET_HOME: "Program",
PRESET_NONE: "Manual",
},
}
TEMPERATURE_CELSIUS = "celsius"
TEMPERATURE_FAHRENHEIT = "fahrenheit"
DEFAULT_TEMPERATURE_UNIT = TEMPERATURE_CELSIUS
DEFAULT_PRECISION = PRECISION_TENTHS
DEFAULT_TEMPERATURE_STEP = PRECISION_HALVES
# Empirically tested to work for AVATTO thermostat
MODE_WAIT = 0.1
def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_CURRENT_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_TEMPERATURE_STEP): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_MAX_TEMP_DP): vol.In(dps),
vol.Optional(CONF_MIN_TEMP_DP): vol.In(dps),
vol.Optional(CONF_PRECISION): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HVAC_MODE_DP): vol.In(dps),
vol.Optional(CONF_HVAC_MODE_SET): vol.In(list(HVAC_MODE_SETS.keys())),
vol.Optional(CONF_HVAC_ACTION_DP): vol.In(dps),
vol.Optional(CONF_HVAC_ACTION_SET): vol.In(list(HVAC_ACTION_SETS.keys())),
vol.Optional(CONF_ECO_DP): vol.In(dps),
vol.Optional(CONF_ECO_VALUE): str,
vol.Optional(CONF_PRESET_DP): vol.In(dps),
vol.Optional(CONF_PRESET_SET): vol.In(list(PRESET_SETS.keys())),
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
[TEMPERATURE_CELSIUS, TEMPERATURE_FAHRENHEIT]
),
vol.Optional(CONF_TARGET_PRECISION): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HEURISTIC_ACTION): bool,
}
class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
"""Tuya climate device."""
def __init__(
self,
device,
config_entry,
switchid,
**kwargs,
):
"""Initialize a new LocaltuyaClimate."""
super().__init__(device, config_entry, switchid, _LOGGER, **kwargs)
self._state = None
self._target_temperature = None
self._current_temperature = None
self._hvac_mode = None
self._preset_mode = None
self._hvac_action = None
self._precision = self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
self._target_precision = self._config.get(
CONF_TARGET_PRECISION, self._precision
)
self._conf_hvac_mode_dp = self._config.get(CONF_HVAC_MODE_DP)
self._conf_hvac_mode_set = HVAC_MODE_SETS.get(
self._config.get(CONF_HVAC_MODE_SET), {}
)
self._conf_preset_dp = self._config.get(CONF_PRESET_DP)
self._conf_preset_set = PRESET_SETS.get(self._config.get(CONF_PRESET_SET), {})
self._conf_hvac_action_dp = self._config.get(CONF_HVAC_ACTION_DP)
self._conf_hvac_action_set = HVAC_ACTION_SETS.get(
self._config.get(CONF_HVAC_ACTION_SET), {}
)
self._conf_eco_dp = self._config.get(CONF_ECO_DP)
self._conf_eco_value = self._config.get(CONF_ECO_VALUE, "ECO")
self._has_presets = self.has_config(CONF_ECO_DP) or self.has_config(
CONF_PRESET_DP
)
print("Initialized climate [{}]".format(self.name))
@property
def supported_features(self):
"""Flag supported features."""
supported_features = 0
if self.has_config(CONF_TARGET_TEMPERATURE_DP):
supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE
if self.has_config(CONF_MAX_TEMP_DP):
supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE_RANGE
if self.has_config(CONF_PRESET_DP) or self.has_config(CONF_ECO_DP):
supported_features = supported_features | SUPPORT_PRESET_MODE
return supported_features
@property
def precision(self):
"""Return the precision of the system."""
return self._precision
@property
def target_precision(self):
"""Return the precision of the target."""
return self._target_precision
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
if (
self._config.get(CONF_TEMPERATURE_UNIT, DEFAULT_TEMPERATURE_UNIT)
== TEMPERATURE_FAHRENHEIT
):
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
@property
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._hvac_mode
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
if not self.has_config(CONF_HVAC_MODE_DP):
return None
return list(self._conf_hvac_mode_set) + [HVAC_MODE_OFF]
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._config.get(CONF_HEURISTIC_ACTION, False):
if self._hvac_mode == HVAC_MODE_HEAT:
if self._current_temperature < (
self._target_temperature - self._precision
):
self._hvac_action = CURRENT_HVAC_HEAT
if self._current_temperature == (
self._target_temperature - self._precision
):
if self._hvac_action == CURRENT_HVAC_HEAT:
self._hvac_action = CURRENT_HVAC_HEAT
if self._hvac_action == CURRENT_HVAC_IDLE:
self._hvac_action = CURRENT_HVAC_IDLE
if (
self._current_temperature + self._precision
) > self._target_temperature:
self._hvac_action = CURRENT_HVAC_IDLE
return self._hvac_action
return self._hvac_action
@property
def preset_mode(self):
"""Return current preset."""
return self._preset_mode
@property
def preset_modes(self):
"""Return the list of available presets modes."""
if not self._has_presets:
return None
presets = list(self._conf_preset_set)
if self._conf_eco_dp:
presets.append(PRESET_ECO)
return presets
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self._config.get(CONF_TEMPERATURE_STEP, DEFAULT_TEMPERATURE_STEP)
@property
def fan_mode(self):
"""Return the fan setting."""
return NotImplementedError()
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return NotImplementedError()
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_TEMPERATURE in kwargs and self.has_config(CONF_TARGET_TEMPERATURE_DP):
temperature = round(kwargs[ATTR_TEMPERATURE] / self._target_precision)
await self._device.set_dp(
temperature, self._config[CONF_TARGET_TEMPERATURE_DP]
)
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
return NotImplementedError()
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF:
await self._device.set_dp(False, self._dp_id)
return
if not self._state and self._conf_hvac_mode_dp != self._dp_id:
await self._device.set_dp(True, self._dp_id)
# Some thermostats need a small wait before sending another update
await asyncio.sleep(MODE_WAIT)
await self._device.set_dp(
self._conf_hvac_mode_set[hvac_mode], self._conf_hvac_mode_dp
)
async def async_turn_on(self) -> None:
"""Turn the entity on."""
await self._device.set_dp(True, self._dp_id)
async def async_turn_off(self) -> None:
"""Turn the entity off."""
await self._device.set_dp(False, self._dp_id)
async def async_set_preset_mode(self, preset_mode):
"""Set new target preset mode."""
if preset_mode == PRESET_ECO:
await self._device.set_dp(self._conf_eco_value, self._conf_eco_dp)
return
await self._device.set_dp(
self._conf_preset_set[preset_mode], self._conf_preset_dp
)
@property
def min_temp(self):
"""Return the minimum temperature."""
if self.has_config(CONF_MIN_TEMP_DP):
return self.dps_conf(CONF_MIN_TEMP_DP)
return DEFAULT_MIN_TEMP
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.has_config(CONF_MAX_TEMP_DP):
return self.dps_conf(CONF_MAX_TEMP_DP)
return DEFAULT_MAX_TEMP
def status_updated(self):
"""Device status was updated."""
self._state = self.dps(self._dp_id)
if self.has_config(CONF_TARGET_TEMPERATURE_DP):
self._target_temperature = (
self.dps_conf(CONF_TARGET_TEMPERATURE_DP) * self._target_precision
)
if self.has_config(CONF_CURRENT_TEMPERATURE_DP):
self._current_temperature = (
self.dps_conf(CONF_CURRENT_TEMPERATURE_DP) * self._precision
)
if self._has_presets:
if (
self.has_config(CONF_ECO_DP)
and self.dps_conf(CONF_ECO_DP) == self._conf_eco_value
):
self._preset_mode = PRESET_ECO
else:
for preset, value in self._conf_preset_set.items(): # todo remove
if self.dps_conf(CONF_PRESET_DP) == value:
self._preset_mode = preset
break
else:
self._preset_mode = PRESET_NONE
# Update the HVAC status
if self.has_config(CONF_HVAC_MODE_DP):
if not self._state:
self._hvac_mode = HVAC_MODE_OFF
else:
for mode, value in self._conf_hvac_mode_set.items():
if self.dps_conf(CONF_HVAC_MODE_DP) == value:
self._hvac_mode = mode
break
else:
# in case hvac mode and preset share the same dp
self._hvac_mode = HVAC_MODE_AUTO
# Update the current action
for action, value in self._conf_hvac_action_set.items():
if self.dps_conf(CONF_HVAC_ACTION_DP) == value:
self._hvac_action = action
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaClimate, flow_schema)

View File

@@ -1,7 +1,6 @@
"""Code shared between all platforms."""
import asyncio
import logging
from datetime import timedelta
from homeassistant.const import (
CONF_DEVICE_ID,
@@ -10,10 +9,8 @@ from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
@@ -120,7 +117,6 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._is_closing = False
self._connect_task = None
self._disconnect_task = None
self._unsub_interval = None
self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID])
# This has to be done in case the device type is type_0d
@@ -170,16 +166,6 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._disconnect_task = async_dispatcher_connect(
self._hass, signal, _new_entity_handler
)
if (
CONF_SCAN_INTERVAL in self._config_entry
and self._config_entry[CONF_SCAN_INTERVAL] > 0
):
self._unsub_interval = async_track_time_interval(
self._hass,
self._async_refresh,
timedelta(seconds=self._config_entry[CONF_SCAN_INTERVAL]),
)
except Exception: # pylint: disable=broad-except
self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed")
if self._interface is not None:
@@ -187,10 +173,6 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._interface = None
self._connect_task = None
async def _async_refresh(self, _now):
if self._interface is not None:
await self._interface.update_dps()
async def close(self):
"""Close connection and stop re-connect loop."""
self._is_closing = True
@@ -241,9 +223,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
"""Device disconnected."""
signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}"
async_dispatcher_send(self._hass, signal, None)
if self._unsub_interval is not None:
self._unsub_interval()
self._unsub_interval = None
self._interface = None
self.debug("Disconnected - waiting for discovery broadcast")
@@ -273,13 +253,13 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
def _update_handler(status):
"""Update entity state when status was updated."""
if status is None:
status = {}
if self._status != status:
self._status = status.copy()
if status:
self.status_updated()
self.schedule_update_ha_state()
if status is not None:
self._status = status
self.status_updated()
else:
self._status = {}
self.schedule_update_ha_state()
signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}"

View File

@@ -14,7 +14,6 @@ from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
@@ -45,7 +44,6 @@ BASIC_INFO_SCHEMA = vol.Schema(
vol.Required(CONF_HOST): str,
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
}
)
@@ -57,7 +55,6 @@ DEVICE_SCHEMA = vol.Schema(
vol.Required(CONF_LOCAL_KEY): cv.string,
vol.Required(CONF_FRIENDLY_NAME): cv.string,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
}
)
@@ -93,7 +90,6 @@ def options_schema(entities):
vol.Required(CONF_HOST): str,
vol.Required(CONF_LOCAL_KEY): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Required(
CONF_ENTITIES, description={"suggested_value": entity_names}
): cv.multi_select(entity_names),

View File

@@ -16,7 +16,6 @@ CONF_COLOR = "color"
CONF_COLOR_MODE = "color_mode"
CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin"
CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin"
CONF_COLOR_TEMP_REVERSE = "color_temp_reverse"
CONF_MUSIC_MODE = "music_mode"
# switch
@@ -35,53 +34,13 @@ CONF_SPAN_TIME = "span_time"
# fan
CONF_FAN_SPEED_CONTROL = "fan_speed_control"
CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control"
CONF_FAN_SPEED_MIN = "fan_speed_min"
CONF_FAN_SPEED_MAX = "fan_speed_max"
CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list"
CONF_FAN_DIRECTION = "fan_direction"
CONF_FAN_DIRECTION_FWD = "fan_direction_forward"
CONF_FAN_DIRECTION_REV = "fan_direction_reverse"
CONF_FAN_SPEED_LOW = "fan_speed_low"
CONF_FAN_SPEED_MEDIUM = "fan_speed_medium"
CONF_FAN_SPEED_HIGH = "fan_speed_high"
# sensor
CONF_SCALING = "scaling"
# climate
CONF_TARGET_TEMPERATURE_DP = "target_temperature_dp"
CONF_CURRENT_TEMPERATURE_DP = "current_temperature_dp"
CONF_TEMPERATURE_STEP = "temperature_step"
CONF_MAX_TEMP_DP = "max_temperature_dp"
CONF_MIN_TEMP_DP = "min_temperature_dp"
CONF_PRECISION = "precision"
CONF_TARGET_PRECISION = "target_precision"
CONF_HVAC_MODE_DP = "hvac_mode_dp"
CONF_HVAC_MODE_SET = "hvac_mode_set"
CONF_PRESET_DP = "preset_dp"
CONF_PRESET_SET = "preset_set"
CONF_HEURISTIC_ACTION = "heuristic_action"
CONF_HVAC_ACTION_DP = "hvac_action_dp"
CONF_HVAC_ACTION_SET = "hvac_action_set"
CONF_ECO_DP = "eco_dp"
CONF_ECO_VALUE = "eco_value"
# vacuum
CONF_POWERGO_DP = "powergo_dp"
CONF_IDLE_STATUS_VALUE = "idle_status_value"
CONF_RETURNING_STATUS_VALUE = "returning_status_value"
CONF_DOCKED_STATUS_VALUE = "docked_status_value"
CONF_BATTERY_DP = "battery_dp"
CONF_MODE_DP = "mode_dp"
CONF_MODES = "modes"
CONF_FAN_SPEED_DP = "fan_speed_dp"
CONF_FAN_SPEEDS = "fan_speeds"
CONF_CLEAN_TIME_DP = "clean_time_dp"
CONF_CLEAN_AREA_DP = "clean_area_dp"
CONF_CLEAN_RECORD_DP = "clean_record_dp"
CONF_LOCATE_DP = "locate_dp"
CONF_FAULT_DP = "fault_dp"
CONF_PAUSED_STATE = "paused_state"
CONF_RETURN_MODE = "return_mode"
CONF_STOP_STATUS = "stop_status"
DATA_DISCOVERY = "discovery"
DOMAIN = "localtuya"
@@ -89,7 +48,6 @@ DOMAIN = "localtuya"
# Platforms in this list must support config flows
PLATFORMS = [
"binary_sensor",
"climate",
"cover",
"fan",
"light",
@@ -97,7 +55,6 @@ PLATFORMS = [
"select",
"sensor",
"switch",
"vacuum",
]
TUYA_DEVICE = "tuya_device"

View File

@@ -1,37 +1,26 @@
"""Platform to locally control Tuya-based fan devices."""
import logging
import math
from functools import partial
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.fan import (
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN,
SUPPORT_DIRECTION,
SPEED_HIGH,
SPEED_LOW,
SPEED_MEDIUM,
SPEED_OFF,
SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
FanEntity,
)
from homeassistant.util.percentage import (
int_states_in_range,
ordered_list_item_to_percentage,
percentage_to_ordered_list_item,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from .common import LocalTuyaEntity, async_setup_entry
from .const import (
CONF_FAN_DIRECTION,
CONF_FAN_DIRECTION_FWD,
CONF_FAN_DIRECTION_REV,
CONF_FAN_ORDERED_LIST,
CONF_FAN_OSCILLATING_CONTROL,
CONF_FAN_SPEED_CONTROL,
CONF_FAN_SPEED_MAX,
CONF_FAN_SPEED_MIN,
CONF_FAN_SPEED_HIGH,
CONF_FAN_SPEED_LOW,
CONF_FAN_SPEED_MEDIUM,
)
_LOGGER = logging.getLogger(__name__)
@@ -42,12 +31,15 @@ def flow_schema(dps):
return {
vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps),
vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps),
vol.Optional(CONF_FAN_DIRECTION): vol.In(dps),
vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string,
vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string,
vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int,
vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int,
vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string,
vol.Optional(CONF_FAN_SPEED_LOW, default=SPEED_LOW): vol.In(
[SPEED_LOW, "1", "2", "small"]
),
vol.Optional(CONF_FAN_SPEED_MEDIUM, default=SPEED_MEDIUM): vol.In(
[SPEED_MEDIUM, "mid", "2", "3"]
),
vol.Optional(CONF_FAN_SPEED_HIGH, default=SPEED_HIGH): vol.In(
[SPEED_HIGH, "auto", "3", "4", "large", "big"]
),
}
@@ -64,46 +56,28 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
"""Initialize the entity."""
super().__init__(device, config_entry, fanid, _LOGGER, **kwargs)
self._is_on = False
self._speed = None
self._oscillating = None
self._direction = None
self._percentage = None
self._speed_range = (
self._config.get(CONF_FAN_SPEED_MIN),
self._config.get(CONF_FAN_SPEED_MAX),
)
self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",")
self._ordered_list_mode = None
if isinstance(self._ordered_list, list) and len(self._ordered_list) > 1:
self._use_ordered_list = True
_LOGGER.debug(
"Fan _use_ordered_list: %s > %s",
self._use_ordered_list,
self._ordered_list,
)
else:
self._use_ordered_list = False
_LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list)
@property
def oscillating(self):
"""Return current oscillating status."""
return self._oscillating
@property
def current_direction(self):
"""Return the current direction of the fan."""
return self._direction
@property
def is_on(self):
"""Check if Tuya fan is on."""
return self._is_on
@property
def percentage(self):
"""Return the current percentage."""
return self._percentage
def speed(self) -> str:
"""Return the current speed."""
return self._speed
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
async def async_turn_on(
self,
@@ -113,143 +87,76 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
**kwargs,
) -> None:
"""Turn on the entity."""
_LOGGER.debug("Fan async_turn_on")
await self._device.set_dp(True, self._dp_id)
if percentage is not None:
await self.async_set_percentage(percentage)
if speed is not None:
await self.async_set_speed(speed)
else:
self.schedule_update_ha_state()
async def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity."""
_LOGGER.debug("Fan async_turn_off")
await self._device.set_dp(False, self._dp_id)
self.schedule_update_ha_state()
async def async_set_percentage(self, percentage):
async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
_LOGGER.debug("Fan async_set_percentage: %s", percentage)
mapping = {
SPEED_LOW: self._config.get(CONF_FAN_SPEED_LOW),
SPEED_MEDIUM: self._config.get(CONF_FAN_SPEED_MEDIUM),
SPEED_HIGH: self._config.get(CONF_FAN_SPEED_HIGH),
}
if percentage is not None:
if percentage == 0:
return await self.async_turn_off()
if not self.is_on:
await self.async_turn_on()
if self._use_ordered_list:
await self._device.set_dp(
str(
percentage_to_ordered_list_item(self._ordered_list, percentage)
),
self._config.get(CONF_FAN_SPEED_CONTROL),
)
_LOGGER.debug(
"Fan async_set_percentage: %s > %s",
percentage,
percentage_to_ordered_list_item(self._ordered_list, percentage),
)
if speed == SPEED_OFF:
await self._device.set_dp(False, self._dp_id)
else:
await self._device.set_dp(
mapping.get(speed), self._config.get(CONF_FAN_SPEED_CONTROL)
)
else:
await self._device.set_dp(
str(
math.ceil(
percentage_to_ranged_value(self._speed_range, percentage)
)
),
self._config.get(CONF_FAN_SPEED_CONTROL),
)
_LOGGER.debug(
"Fan async_set_percentage: %s > %s",
percentage,
percentage_to_ranged_value(self._speed_range, percentage),
)
self.schedule_update_ha_state()
self.schedule_update_ha_state()
async def async_oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
_LOGGER.debug("Fan async_oscillate: %s", oscillating)
await self._device.set_dp(
oscillating, self._config.get(CONF_FAN_OSCILLATING_CONTROL)
)
self.schedule_update_ha_state()
async def async_set_direction(self, direction):
"""Set the direction of the fan."""
_LOGGER.debug("Fan async_set_direction: %s", direction)
if direction == DIRECTION_FORWARD:
value = self._config.get(CONF_FAN_DIRECTION_FWD)
if direction == DIRECTION_REVERSE:
value = self._config.get(CONF_FAN_DIRECTION_REV)
await self._device.set_dp(value, self._config.get(CONF_FAN_DIRECTION))
self.schedule_update_ha_state()
@property
def supported_features(self) -> int:
"""Flag supported features."""
features = 0
supports = 0
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
features |= SUPPORT_OSCILLATE
supports |= SUPPORT_OSCILLATE
if self.has_config(CONF_FAN_SPEED_CONTROL):
features |= SUPPORT_SET_SPEED
supports |= SUPPORT_SET_SPEED
if self.has_config(CONF_FAN_DIRECTION):
features |= SUPPORT_DIRECTION
return features
@property
def speed_count(self) -> int:
"""Speed count for the fan."""
speed_count = int_states_in_range(self._speed_range)
_LOGGER.debug("Fan speed_count: %s", speed_count)
return speed_count
return supports
def status_updated(self):
"""Get state of Tuya fan."""
mappings = {
self._config.get(CONF_FAN_SPEED_LOW): SPEED_LOW,
self._config.get(CONF_FAN_SPEED_MEDIUM): SPEED_MEDIUM,
self._config.get(CONF_FAN_SPEED_HIGH): SPEED_HIGH,
}
self._is_on = self.dps(self._dp_id)
current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL)
if self._use_ordered_list:
_LOGGER.debug(
"Fan current_speed ordered_list_item_to_percentage: %s from %s",
current_speed,
self._ordered_list,
)
if current_speed is not None:
self._percentage = ordered_list_item_to_percentage(
self._ordered_list, current_speed
if self.has_config(CONF_FAN_SPEED_CONTROL):
self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL))
if self.speed is None:
self.warning(
"%s/%s: Ignoring unknown fan controller state: %s",
self.name,
self.entity_id,
self.dps_conf(CONF_FAN_SPEED_CONTROL),
)
else:
_LOGGER.debug(
"Fan current_speed ranged_value_to_percentage: %s from %s",
current_speed,
self._speed_range,
)
if current_speed is not None:
self._percentage = ranged_value_to_percentage(
self._speed_range, int(current_speed)
)
_LOGGER.debug("Fan current_percentage: %s", self._percentage)
self._speed = None
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL)
_LOGGER.debug("Fan current_oscillating : %s", self._oscillating)
if self.has_config(CONF_FAN_DIRECTION):
value = self.dps_conf(CONF_FAN_DIRECTION)
if value is not None:
if value == self._config.get(CONF_FAN_DIRECTION_FWD):
self._direction = DIRECTION_FORWARD
if value == self._config.get(CONF_FAN_DIRECTION_REV):
self._direction = DIRECTION_REVERSE
_LOGGER.debug("Fan current_direction : %s > %s", value, self._direction)
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema)

View File

@@ -27,17 +27,15 @@ from .const import (
CONF_COLOR_MODE,
CONF_COLOR_TEMP_MAX_KELVIN,
CONF_COLOR_TEMP_MIN_KELVIN,
CONF_COLOR_TEMP_REVERSE,
CONF_MUSIC_MODE,
)
_LOGGER = logging.getLogger(__name__)
MIRED_TO_KELVIN_CONST = 1000000
DEFAULT_MIN_KELVIN = 2700 # MIRED 370
DEFAULT_MAX_KELVIN = 6500 # MIRED 153
DEFAULT_COLOR_TEMP_REVERSE = False
DEFAULT_LOWER_BRIGHTNESS = 29
DEFAULT_UPPER_BRIGHTNESS = 1000
@@ -119,11 +117,6 @@ def flow_schema(dps):
vol.Optional(CONF_COLOR_TEMP_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1500, max=8000)
),
vol.Optional(
CONF_COLOR_TEMP_REVERSE,
default=DEFAULT_COLOR_TEMP_REVERSE,
description={"suggested_value": DEFAULT_COLOR_TEMP_REVERSE},
): bool,
vol.Optional(CONF_SCENE): vol.In(dps),
vol.Optional(
CONF_MUSIC_MODE, default=False, description={"suggested_value": False}
@@ -153,14 +146,13 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
CONF_BRIGHTNESS_UPPER, DEFAULT_UPPER_BRIGHTNESS
)
self._upper_color_temp = self._upper_brightness
self._max_mired = color_util.color_temperature_kelvin_to_mired(
self._config.get(CONF_COLOR_TEMP_MIN_KELVIN, DEFAULT_MIN_KELVIN)
self._max_mired = round(
MIRED_TO_KELVIN_CONST
/ self._config.get(CONF_COLOR_TEMP_MIN_KELVIN, DEFAULT_MIN_KELVIN)
)
self._min_mired = color_util.color_temperature_kelvin_to_mired(
self._config.get(CONF_COLOR_TEMP_MAX_KELVIN, DEFAULT_MAX_KELVIN)
)
self._color_temp_reverse = self._config.get(
CONF_COLOR_TEMP_REVERSE, DEFAULT_COLOR_TEMP_REVERSE
self._min_mired = round(
MIRED_TO_KELVIN_CONST
/ self._config.get(CONF_COLOR_TEMP_MAX_KELVIN, DEFAULT_MAX_KELVIN)
)
self._hs = None
self._effect = None
@@ -207,16 +199,11 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
def color_temp(self):
"""Return the color_temp of the light."""
if self.has_config(CONF_COLOR_TEMP) and self.is_white_mode:
color_temp_value = (
self._upper_color_temp - self._color_temp
if self._color_temp_reverse
else self._color_temp
)
return int(
self._max_mired
- (
((self._max_mired - self._min_mired) / self._upper_color_temp)
* color_temp_value
* self._color_temp
)
)
return None
@@ -377,17 +364,10 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP):
if brightness is None:
brightness = self._brightness
mired = int(kwargs[ATTR_COLOR_TEMP])
if self._color_temp_reverse:
mired = self._max_mired - (mired - self._min_mired)
if mired < self._min_mired:
mired = self._min_mired
elif mired > self._max_mired:
mired = self._max_mired
color_temp = int(
self._upper_color_temp
- (self._upper_color_temp / (self._max_mired - self._min_mired))
* (mired - self._min_mired)
* (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired)
)
states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE
states[self._config.get(CONF_BRIGHTNESS)] = brightness

View File

@@ -4,7 +4,10 @@ from functools import partial
import voluptuous as vol
from homeassistant.components.number import DOMAIN, NumberEntity
from homeassistant.const import CONF_DEVICE_CLASS, STATE_UNKNOWN
from homeassistant.const import (
CONF_DEVICE_CLASS,
STATE_UNKNOWN,
)
from .common import LocalTuyaEntity, async_setup_entry

View File

@@ -21,7 +21,6 @@ Functions
json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3
detect_available_dps() # returns a list of available dps provided by the device
update_dps(dps) # sends update dps command
add_dps_to_request(dp_index) # adds dp_index to the list of dps used by the
# device (to be queried in the payload)
set_dp(on, dp_index) # Set value of any dps index.
@@ -62,7 +61,6 @@ TuyaMessage = namedtuple("TuyaMessage", "seqno cmd retcode payload crc")
SET = "set"
STATUS = "status"
HEARTBEAT = "heartbeat"
UPDATEDPS = "updatedps" # Request refresh of DPS
PROTOCOL_VERSION_BYTES_31 = b"3.1"
PROTOCOL_VERSION_BYTES_33 = b"3.3"
@@ -78,9 +76,6 @@ SUFFIX_VALUE = 0x0000AA55
HEARTBEAT_INTERVAL = 10
# DPS that are known to be safe to use with update_dps (0x12) command
UPDATE_DPS_WHITELIST = [18, 19, 20] # Socket (Wi-Fi)
# This is intended to match requests.json payload at
# https://github.com/codetheweb/tuyapi :
# type_0a devices require the 0a command as the status request
@@ -95,13 +90,11 @@ PAYLOAD_DICT = {
STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
},
"type_0d": {
STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
},
}
@@ -299,8 +292,6 @@ class MessageDispatcher(ContextualLogger):
sem = self.listeners[self.HEARTBEAT_SEQNO]
self.listeners[self.HEARTBEAT_SEQNO] = msg
sem.release()
elif msg.cmd == 0x12:
self.debug("Got normal updatedps response")
elif msg.cmd == 0x08:
self.debug("Got status update")
self.listener(msg)
@@ -487,26 +478,6 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
"""Send a heartbeat message."""
return await self.exchange(HEARTBEAT)
async def update_dps(self, dps=None):
"""
Request device to update index.
Args:
dps([int]): list of dps to update, default=detected&whitelisted
"""
if self.version == 3.3:
if dps is None:
if not self.dps_cache:
await self.detect_available_dps()
if self.dps_cache:
dps = [int(dp) for dp in self.dps_cache]
# filter non whitelisted dps
dps = list(set(dps).intersection(set(UPDATE_DPS_WHITELIST)))
self.debug("updatedps() entry (dps %s, dps_cache %s)", dps, self.dps_cache)
payload = self._generate_payload(UPDATEDPS, dps)
self.transport.write(payload)
return True
async def set_dp(self, value, dp_index):
"""
Set value (may be any type: bool, int or string) of any dps index.
@@ -611,10 +582,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
json_data["t"] = str(int(time.time()))
if data is not None:
if "dpId" in json_data:
json_data["dpId"] = data
else:
json_data["dps"] = data
json_data["dps"] = data
elif command_hb == 0x0D:
json_data["dps"] = self.dps_to_request
@@ -623,7 +591,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
if self.version == 3.3:
payload = self.cipher.encrypt(payload, False)
if command_hb not in [0x0A, 0x12]:
if command_hb != 0x0A:
# add the 3.3 header
payload = PROTOCOL_33_HEADER + payload
elif command == SET:

View File

@@ -4,7 +4,10 @@ from functools import partial
import voluptuous as vol
from homeassistant.components.select import DOMAIN, SelectEntity
from homeassistant.const import CONF_DEVICE_CLASS, STATE_UNKNOWN
from homeassistant.const import (
CONF_DEVICE_CLASS,
STATE_UNKNOWN,
)
from .common import LocalTuyaEntity, async_setup_entry

View File

@@ -20,7 +20,6 @@
"device_id": "Device ID",
"local_key": "Local key",
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)",
"device_type": "Device type"
}
},
@@ -40,4 +39,4 @@
}
},
"title": "LocalTuya"
}
}

View File

@@ -48,7 +48,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
return self._state
@property
def extra_state_attributes(self):
def device_state_attributes(self):
"""Return device state attributes."""
attrs = {}
if self.has_config(CONF_CURRENT):

View File

@@ -29,8 +29,7 @@
"host": "Host",
"device_id": "Device ID",
"local_key": "Local key",
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)"
"protocol_version": "Protocol Version"
}
},
"pick_entity_type": {
@@ -61,61 +60,25 @@
"scaling": "Scaling Factor",
"state_on": "On Value",
"state_off": "Off Value",
"powergo_dp": "Power DP (Usually 25 or 2)",
"idle_status_value": "Idle Status (comma-separated)",
"returning_status_value": "Returning Status",
"docked_status_value": "Docked Status (comma-separated)",
"fault_dp": "Fault DP (Usually 11)",
"battery_dp": "Battery status DP (Usually 14)",
"mode_dp": "Mode DP (Usually 27)",
"modes": "Modes list",
"return_mode": "Return home mode",
"fan_speed_dp": "Fan speeds DP (Usually 30)",
"fan_speeds": "Fan speeds list (comma-separated)",
"clean_time_dp": "Clean Time DP (Usually 33)",
"clean_area_dp": "Clean Area DP (Usually 32)",
"clean_record_dp": "Clean Record DP (Usually 34)",
"locate_dp": "Locate DP (Usually 31)",
"paused_state": "Pause state (pause, paused, etc)",
"stop_status": "Stop status",
"brightness": "Brightness (only for white color)",
"brightness_lower": "Brightness Lower Value",
"brightness_upper": "Brightness Upper Value",
"color_temp": "Color Temperature",
"color_temp_reverse": "Color Temperature Reverse",
"color": "Color",
"color_mode": "Color Mode",
"color_temp_min_kelvin": "Minimum Color Temperature in K",
"color_temp_max_kelvin": "Maximum Color Temperature in K",
"music_mode": "Music mode available",
"scene": "Scene",
"fan_speed_control": "Fan Speed Control",
"fan_oscillating_control": "Fan Oscillating Control",
"fan_speed_low": "Fan Low Speed Setting",
"fan_speed_medium": "Fan Medium Speed Setting",
"fan_speed_high": "Fan High Speed Setting",
"max_value": "Maximum Value",
"min_value": "Minimum Value",
"select_options": "Valid entries, separate entries by a ;",
"select_options_friendly": "User Friendly options, separate entries by a ;",
"fan_speed_control": "Fan Speed Control dps",
"fan_oscillating_control": "Fan Oscillating Control dps",
"fan_speed_min": "minimum fan speed integer",
"fan_speed_max": "maximum fan speed integer",
"fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)",
"fan_direction":"fan direction dps",
"fan_direction_forward": "forward dps string",
"fan_direction_reverse": "reverse dps string",
"current_temperature_dp": "Current Temperature",
"target_temperature_dp": "Target Temperature",
"temperature_step": "Temperature Step (optional)",
"max_temperature_dp": "Max Temperature (optional)",
"min_temperature_dp": "Min Temperature (optional)",
"precision": "Precision (optional, for DPs values)",
"target_precision": "Target Precision (optional, for DPs values)",
"temperature_unit": "Temperature Unit (optional)",
"hvac_mode_dp": "HVAC Mode DP (optional)",
"hvac_mode_set": "HVAC Mode Set (optional)",
"hvac_action_dp": "HVAC Current Action DP (optional)",
"hvac_action_set": "HVAC Current Action Set (optional)",
"preset_dp": "Presets DP (optional)",
"preset_set": "Presets Set (optional)",
"eco_dp": "Eco DP (optional)",
"eco_value": "Eco value (optional)",
"heuristic_action": "Enable heuristic action (optional)"
"select_options_friendly": "User Friendly options, separate entries by a ;"
}
}
}
@@ -130,7 +93,6 @@
"host": "Host",
"local_key": "Local key",
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)",
"entities": "Entities (uncheck an entity to remove it)"
}
},
@@ -154,61 +116,25 @@
"scaling": "Scaling Factor",
"state_on": "On Value",
"state_off": "Off Value",
"powergo_dp": "Power DP (Usually 25 or 2)",
"idle_status_value": "Idle Status (comma-separated)",
"returning_status_value": "Returning Status",
"docked_status_value": "Docked Status (comma-separated)",
"fault_dp": "Fault DP (Usually 11)",
"battery_dp": "Battery status DP (Usually 14)",
"mode_dp": "Mode DP (Usually 27)",
"modes": "Modes list",
"return_mode": "Return home mode",
"fan_speed_dp": "Fan speeds DP (Usually 30)",
"fan_speeds": "Fan speeds list (comma-separated)",
"clean_time_dp": "Clean Time DP (Usually 33)",
"clean_area_dp": "Clean Area DP (Usually 32)",
"clean_record_dp": "Clean Record DP (Usually 34)",
"locate_dp": "Locate DP (Usually 31)",
"paused_state": "Pause state (pause, paused, etc)",
"stop_status": "Stop status",
"brightness": "Brightness (only for white color)",
"brightness_lower": "Brightness Lower Value",
"brightness_upper": "Brightness Upper Value",
"color_temp": "Color Temperature",
"color_temp_reverse": "Color Temperature Reverse",
"color": "Color",
"color_mode": "Color Mode",
"color_temp_min_kelvin": "Minimum Color Temperature in K",
"color_temp_max_kelvin": "Maximum Color Temperature in K",
"music_mode": "Music mode available",
"scene": "Scene",
"fan_speed_control": "Fan Speed Control",
"fan_oscillating_control": "Fan Oscillating Control",
"fan_speed_low": "Fan Low Speed Setting",
"fan_speed_medium": "Fan Medium Speed Setting",
"fan_speed_high": "Fan High Speed Setting",
"max_value": "Maximum Value",
"min_value": "Minimum Value",
"select_options": "Valid entries, separate entries by a ;",
"select_options_friendly": "User Friendly options, separate entries by a ;",
"fan_speed_control": "Fan Speed Control dps",
"fan_oscillating_control": "Fan Oscillating Control dps",
"fan_speed_min": "minimum fan speed integer",
"fan_speed_max": "maximum fan speed integer",
"fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)",
"fan_direction":"fan direction dps",
"fan_direction_forward": "forward dps string",
"fan_direction_reverse": "reverse dps string",
"current_temperature_dp": "Current Temperature",
"target_temperature_dp": "Target Temperature",
"temperature_step": "Temperature Step (optional)",
"max_temperature_dp": "Max Temperature (optional)",
"min_temperature_dp": "Min Temperature (optional)",
"precision": "Precision (optional, for DPs values)",
"target_precision": "Target Precision (optional, for DPs values)",
"temperature_unit": "Temperature Unit (optional)",
"hvac_mode_dp": "HVAC Mode DP (optional)",
"hvac_mode_set": "HVAC Mode Set (optional)",
"hvac_action_dp": "HVAC Current Action DP (optional)",
"hvac_action_set": "HVAC Current Action Set (optional)",
"preset_dp": "Presets DP (optional)",
"preset_set": "Presets Set (optional)",
"eco_dp": "Eco DP (optional)",
"eco_value": "Eco value (optional)",
"heuristic_action": "Enable heuristic action (optional)"
"select_options_friendly": "User Friendly options, separate entries by a ;"
}
},
"yaml_import": {

View File

@@ -1,217 +0,0 @@
{
"config": {
"abort": {
"already_configured": "O dispositivo já foi configurado.",
"device_updated": "A configuração do dispositivo foi atualizada!"
},
"error": {
"cannot_connect": "Não é possível se conectar ao dispositivo. Verifique se o endereço está correto e tente novamente.",
"invalid_auth": "Falha ao autenticar com o dispositivo. Verifique se o ID do dispositivo e a chave local estão corretos.",
"unknown": "Ocorreu um erro desconhecido. Consulte o registro para obter detalhes.",
"entity_already_configured": "A entidade com este ID já foi configurada.",
"address_in_use": "O endereço usado para descoberta já está em uso. Certifique-se de que nenhum outro aplicativo o esteja usando (porta TCP 6668).",
"discovery_failed": "Algo falhou ao descobrir dispositivos. Consulte o registro para obter detalhes.",
"empty_dps": "A conexão com o dispositivo foi bem-sucedida, mas nenhum ponto de dados foi encontrado. Tente novamente. Crie um novo issue e inclua os logs de depuração se o problema persistir."
},
"step": {
"user": {
"title": "Descoberta de dispositivo",
"description": "Escolha um dos dispositivos descobertos automaticamente ou clique em `...` para adicionar um dispositivo manualmente.",
"data": {
"discovered_device": "Dispositivo descoberto"
}
},
"basic_info": {
"title": "Adicionar dispositivo Tuya",
"description": "Preencha os detalhes básicos do dispositivo. O nome inserido aqui será usado para identificar a própria integração (como visto na página `Integrations`). Você adicionará entidades e dará nomes a elas nas etapas a seguir.",
"data": {
"friendly_name": "Nome",
"host": "Host",
"device_id": "ID do dispositivo",
"local_key": "Local key",
"protocol_version": "Versão do protocolo",
"scan_interval": "Intervalo do escaneamento (segundos, somente quando não estiver atualizando automaticamente)"
}
},
"pick_entity_type": {
"title": "Seleção do tipo de entidade",
"description": "Escolha o tipo de entidade que deseja adicionar.",
"data": {
"platform_to_add": "Platforma",
"no_additional_platforms": "Não adicione mais entidades"
}
},
"add_entity": {
"title": "Adicionar nova entidade",
"description": "Por favor, preencha os detalhes de uma entidade com o tipo `{platform}`. Todas as configurações, exceto `ID`, podem ser alteradas na página Opções posteriormente.",
"data": {
"id": "ID",
"friendly_name": "Name fantasia",
"current": "Atual",
"current_consumption": "Consumo atual",
"voltage": "Voltagem",
"commands_set": "Conjunto de comandos Open_Close_Stop",
"positioning_mode": "Modo de posicão",
"current_position_dp": "Posição atual (somente para o modo de posição)",
"set_position_dp": "Definir posição (somente para o modo de posição)",
"position_inverted": "Inverter posição 0-100 (somente para o modo de posição)",
"span_time": "Tempo de abertura completo, em segundos. (somente para o modo temporizado)",
"unit_of_measurement": "Unidade de medida",
"device_class": "Classe do dispositivo",
"scaling": "Fator de escala",
"state_on": "Valor On",
"state_off": "Valor Off",
"powergo_dp": "Potência DP (Geralmente 25 ou 2)",
"idle_status_value": "Status ocioso (separado por vírgula)",
"returning_status_value": "Status de retorno",
"docked_status_value": "Status docked (separado por vírgula)",
"fault_dp": "Falha DP (Geralmente 11)",
"battery_dp": "Status da bateria DP (normalmente 14)",
"mode_dp": "Modo DP (Geralmente 27)",
"modes": "Lista de modos",
"return_mode": "Modo de retorno para base",
"fan_speed_dp": "Velocidades do ventilador DP (normalmente 30)",
"fan_speeds": "Lista de velocidades do ventilador (separadas por vírgulas)",
"clean_time_dp": "Tempo de Limpeza DP (Geralmente 33)",
"clean_area_dp": "Área Limpa DP (Geralmente 32)",
"clean_record_dp": "Limpar Registro DP (Geralmente 34)",
"locate_dp": "Localize DP (Geralmente 31)",
"paused_state": "Estado de pausa (pausa, pausado, etc)",
"stop_status": "Status de parada",
"brightness": "Brilho (somente para cor branca)",
"brightness_lower": "Valor mais baixo do brilho",
"brightness_upper": "Valor mais alto do brilho",
"color_temp": "Temperatura da cor",
"color_temp_reverse": "Temperatura da cor reversa",
"color": "Cor",
"color_mode": "Modo de cor",
"color_temp_min_kelvin": "Minima temperatura de cor em K",
"color_temp_max_kelvin": "Máxima temperatura de cor em K",
"music_mode": "Modo de música disponível",
"scene": "Cena",
"fan_speed_control": "dps de controle de velocidade do ventilador",
"fan_oscillating_control": "dps de controle oscilante do ventilador",
"fan_speed_min": "velocidade mínima do ventilador inteiro",
"fan_speed_max": "velocidade máxima do ventilador inteiro",
"fan_speed_ordered_list": "Lista de modos de velocidade do ventilador (substitui a velocidade min/max)",
"fan_direction":"direção do ventilador dps",
"fan_direction_forward": "string de dps para frente",
"fan_direction_reverse": "string dps reversa",
"current_temperature_dp": "Temperatura atual",
"target_temperature_dp": "Temperatura alvo",
"temperature_step": "Etapa de temperatura (opcional)",
"max_temperature_dp": "Max Temperatura (opcional)",
"min_temperature_dp": "Min Temperatura (opcional)",
"precision": "Precisão (opcional, para valores de DPs)",
"target_precision": "Precisão do alvo (opcional, para valores de DPs)",
"temperature_unit": "Unidade de Temperatura (opcional)",
"hvac_mode_dp": "Modo HVAC DP (opcional)",
"hvac_mode_set": "Conjunto de modo HVAC (opcional)",
"hvac_action_dp": "Ação atual de HVAC DP (opcional)",
"hvac_action_set": "Conjunto de ação atual de HVAC (opcional)",
"preset_dp": "Predefinições DP (opcional)",
"preset_set": "Conjunto de predefinições (opcional)",
"eco_dp": "Eco DP (opcional)",
"eco_value": "Valor ECO (opcional)",
"heuristic_action": "Ativar ação heurística (opcional)"
}
}
}
},
"options": {
"step": {
"init": {
"title": "Configurar dispositivo Tuya",
"description": "Configuração básica para o ID do dispositivo `{device_id}`.",
"data": {
"friendly_name": "Nome fantasia",
"host": "Host",
"local_key": "Local key",
"protocol_version": "Versão do protocolo",
"scan_interval": "Intervalo de escaneamento (segundos, somente quando não estiver atualizando automaticamente)",
"entities": "Entidades (desmarque uma entidade para removê-la)"
}
},
"entity": {
"title": "Adicionar nova entidade",
"description": "Por favor, preencha os detalhes de uma entidade com o tipo `{platform}`. Todas as configurações, exceto `ID`, podem ser alteradas na página Opções posteriormente.",
"data": {
"id": "ID",
"friendly_name": "Name fantasia",
"current": "Atual",
"current_consumption": "Consumo atual",
"voltage": "Voltagem",
"commands_set": "Conjunto de comandos Open_Close_Stop",
"positioning_mode": "Modo de posicão",
"current_position_dp": "Posição atual (somente para o modo de posição)",
"set_position_dp": "Definir posição (somente para o modo de posição)",
"position_inverted": "Inverter posição 0-100 (somente para o modo de posição)",
"span_time": "Tempo de abertura completo, em segundos. (somente para o modo temporizado)",
"unit_of_measurement": "Unidade de medida",
"device_class": "Classe do dispositivo",
"scaling": "Fator de escala",
"state_on": "Valor On",
"state_off": "Valor Off",
"powergo_dp": "Potência DP (Geralmente 25 ou 2)",
"idle_status_value": "Status ocioso (separado por vírgula)",
"returning_status_value": "Status de retorno",
"docked_status_value": "Status docked (separado por vírgula)",
"fault_dp": "Falha DP (Geralmente 11)",
"battery_dp": "Status da bateria DP (normalmente 14)",
"mode_dp": "Modo DP (Geralmente 27)",
"modes": "Lista de modos",
"return_mode": "Modo de retorno para base",
"fan_speed_dp": "Velocidades do ventilador DP (normalmente 30)",
"fan_speeds": "Lista de velocidades do ventilador (separadas por vírgulas)",
"clean_time_dp": "Tempo de Limpeza DP (Geralmente 33)",
"clean_area_dp": "Área Limpa DP (Geralmente 32)",
"clean_record_dp": "Limpar Registro DP (Geralmente 34)",
"locate_dp": "Localize DP (Geralmente 31)",
"paused_state": "Estado de pausa (pausa, pausado, etc)",
"stop_status": "Status de parada",
"brightness": "Brilho (somente para cor branca)",
"brightness_lower": "Valor mais baixo do brilho",
"brightness_upper": "Valor mais alto do brilho",
"color_temp": "Temperatura da cor",
"color_temp_reverse": "Temperatura da cor reversa",
"color": "Cor",
"color_mode": "Modo de cor",
"color_temp_min_kelvin": "Minima temperatura de cor em K",
"color_temp_max_kelvin": "Máxima temperatura de cor em K",
"music_mode": "Modo de música disponível",
"scene": "Cena",
"fan_speed_control": "dps de controle de velocidade do ventilador",
"fan_oscillating_control": "dps de controle oscilante do ventilador",
"fan_speed_min": "velocidade mínima do ventilador inteiro",
"fan_speed_max": "velocidade máxima do ventilador inteiro",
"fan_speed_ordered_list": "Lista de modos de velocidade do ventilador (substitui a velocidade min/max)",
"fan_direction":"direção do ventilador dps",
"fan_direction_forward": "string de dps para frente",
"fan_direction_reverse": "string dps reversa",
"current_temperature_dp": "Temperatura atual",
"target_temperature_dp": "Temperatura alvo",
"temperature_step": "Etapa de temperatura (opcional)",
"max_temperature_dp": "Max Temperatura (opcional)",
"min_temperature_dp": "Min Temperatura (opcional)",
"precision": "Precisão (opcional, para valores de DPs)",
"target_precision": "Precisão do alvo (opcional, para valores de DPs)",
"temperature_unit": "Unidade de Temperatura (opcional)",
"hvac_mode_dp": "Modo HVAC DP (opcional)",
"hvac_mode_set": "Conjunto de modo HVAC (opcional)",
"hvac_action_dp": "Ação atual de HVAC DP (opcional)",
"hvac_action_set": "Conjunto de ação atual de HVAC (opcional)",
"preset_dp": "Predefinições DP (opcional)",
"preset_set": "Conjunto de predefinições (opcional)",
"eco_dp": "Eco DP (opcional)",
"eco_value": "Valor ECO (opcional)",
"heuristic_action": "Ativar ação heurística (opcional)"
}
},
"yaml_import": {
"title": "Não suportado",
"description": "As opções não podem ser editadas quando configuradas via YAML."
}
}
},
"title": "LocalTuya"
}

View File

@@ -1,258 +0,0 @@
"""Platform to locally control Tuya-based vacuum devices."""
import logging
from functools import partial
import voluptuous as vol
from homeassistant.components.vacuum import (
DOMAIN,
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_RETURNING,
STATE_PAUSED,
STATE_ERROR,
SUPPORT_BATTERY,
SUPPORT_FAN_SPEED,
SUPPORT_PAUSE,
SUPPORT_RETURN_HOME,
SUPPORT_START,
SUPPORT_STATE,
SUPPORT_STATUS,
SUPPORT_STOP,
SUPPORT_LOCATE,
StateVacuumEntity,
)
from .common import LocalTuyaEntity, async_setup_entry
from .const import (
CONF_POWERGO_DP,
CONF_IDLE_STATUS_VALUE,
CONF_RETURNING_STATUS_VALUE,
CONF_DOCKED_STATUS_VALUE,
CONF_BATTERY_DP,
CONF_MODE_DP,
CONF_MODES,
CONF_FAN_SPEED_DP,
CONF_FAN_SPEEDS,
CONF_CLEAN_TIME_DP,
CONF_CLEAN_AREA_DP,
CONF_CLEAN_RECORD_DP,
CONF_LOCATE_DP,
CONF_FAULT_DP,
CONF_PAUSED_STATE,
CONF_RETURN_MODE,
CONF_STOP_STATUS,
)
_LOGGER = logging.getLogger(__name__)
CLEAN_TIME = "clean_time"
CLEAN_AREA = "clean_area"
CLEAN_RECORD = "clean_record"
MODES_LIST = "cleaning_mode_list"
MODE = "cleaning_mode"
FAULT = "fault"
DEFAULT_IDLE_STATUS = "standby,sleep"
DEFAULT_RETURNING_STATUS = "docking"
DEFAULT_DOCKED_STATUS = "charging,chargecompleted"
DEFAULT_MODES = "smart,wall_follow,spiral,single"
DEFAULT_FAN_SPEEDS = "low,normal,high"
DEFAULT_PAUSED_STATE = "paused"
DEFAULT_RETURN_MODE = "chargego"
DEFAULT_STOP_STATUS = "standby"
def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Required(CONF_IDLE_STATUS_VALUE, default=DEFAULT_IDLE_STATUS): str,
vol.Required(CONF_POWERGO_DP): vol.In(dps),
vol.Required(CONF_DOCKED_STATUS_VALUE, default=DEFAULT_DOCKED_STATUS): str,
vol.Optional(
CONF_RETURNING_STATUS_VALUE, default=DEFAULT_RETURNING_STATUS
): str,
vol.Optional(CONF_BATTERY_DP): vol.In(dps),
vol.Optional(CONF_MODE_DP): vol.In(dps),
vol.Optional(CONF_MODES, default=DEFAULT_MODES): str,
vol.Optional(CONF_RETURN_MODE, default=DEFAULT_RETURN_MODE): str,
vol.Optional(CONF_FAN_SPEED_DP): vol.In(dps),
vol.Optional(CONF_FAN_SPEEDS, default=DEFAULT_FAN_SPEEDS): str,
vol.Optional(CONF_CLEAN_TIME_DP): vol.In(dps),
vol.Optional(CONF_CLEAN_AREA_DP): vol.In(dps),
vol.Optional(CONF_CLEAN_RECORD_DP): vol.In(dps),
vol.Optional(CONF_LOCATE_DP): vol.In(dps),
vol.Optional(CONF_FAULT_DP): vol.In(dps),
vol.Optional(CONF_PAUSED_STATE, default=DEFAULT_PAUSED_STATE): str,
vol.Optional(CONF_STOP_STATUS, default=DEFAULT_STOP_STATUS): str,
}
class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity):
"""Tuya vacuum device."""
def __init__(self, device, config_entry, switchid, **kwargs):
"""Initialize a new LocaltuyaVacuum."""
super().__init__(device, config_entry, switchid, _LOGGER, **kwargs)
self._state = None
self._battery_level = None
self._attrs = {}
self._idle_status_list = []
if self.has_config(CONF_IDLE_STATUS_VALUE):
self._idle_status_list = self._config[CONF_IDLE_STATUS_VALUE].split(",")
self._modes_list = []
if self.has_config(CONF_MODES):
self._modes_list = self._config[CONF_MODES].split(",")
self._attrs[MODES_LIST] = self._modes_list
self._docked_status_list = []
if self.has_config(CONF_DOCKED_STATUS_VALUE):
self._docked_status_list = self._config[CONF_DOCKED_STATUS_VALUE].split(",")
self._fan_speed_list = []
if self.has_config(CONF_FAN_SPEEDS):
self._fan_speed_list = self._config[CONF_FAN_SPEEDS].split(",")
self._fan_speed = ""
self._cleaning_mode = ""
print("Initialized vacuum [{}]".format(self.name))
@property
def supported_features(self):
"""Flag supported features."""
supported_features = (
SUPPORT_START
| SUPPORT_PAUSE
| SUPPORT_STOP
| SUPPORT_STATUS
| SUPPORT_STATE
)
if self.has_config(CONF_RETURN_MODE):
supported_features = supported_features | SUPPORT_RETURN_HOME
if self.has_config(CONF_FAN_SPEED_DP):
supported_features = supported_features | SUPPORT_FAN_SPEED
if self.has_config(CONF_BATTERY_DP):
supported_features = supported_features | SUPPORT_BATTERY
if self.has_config(CONF_LOCATE_DP):
supported_features = supported_features | SUPPORT_LOCATE
return supported_features
@property
def state(self):
"""Return the vacuum state."""
return self._state
@property
def battery_level(self):
"""Return the current battery level."""
return self._battery_level
@property
def extra_state_attributes(self):
"""Return the specific state attributes of this vacuum cleaner."""
return self._attrs
@property
def fan_speed(self):
"""Return the current fan speed."""
return self._fan_speed
@property
def fan_speed_list(self) -> list:
"""Return the list of available fan speeds."""
return self._fan_speed_list
async def async_start(self, **kwargs):
"""Turn the vacuum on and start cleaning."""
await self._device.set_dp(True, self._config[CONF_POWERGO_DP])
async def async_pause(self, **kwargs):
"""Stop the vacuum cleaner, do not return to base."""
await self._device.set_dp(False, self._config[CONF_POWERGO_DP])
async def async_return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
if self.has_config(CONF_RETURN_MODE):
await self._device.set_dp(
self._config[CONF_RETURN_MODE], self._config[CONF_MODE_DP]
)
else:
_LOGGER.error("Missing command for return home in commands set.")
async def async_stop(self, **kwargs):
"""Turn the vacuum off stopping the cleaning."""
if self.has_config(CONF_STOP_STATUS):
await self._device.set_dp(
self._config[CONF_STOP_STATUS], self._config[CONF_MODE_DP]
)
else:
_LOGGER.error("Missing command for stop in commands set.")
async def async_clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
return None
async def async_locate(self, **kwargs):
"""Locate the vacuum cleaner."""
if self.has_config(CONF_LOCATE_DP):
await self._device.set_dp("", self._config[CONF_LOCATE_DP])
async def async_set_fan_speed(self, fan_speed, **kwargs):
"""Set the fan speed."""
await self._device.set_dp(fan_speed, self._config[CONF_FAN_SPEED_DP])
async def async_send_command(self, command, params=None, **kwargs):
"""Send a command to a vacuum cleaner."""
if command == "set_mode" and "mode" in params:
mode = params["mode"]
await self._device.set_dp(mode, self._config[CONF_MODE_DP])
def status_updated(self):
"""Device status was updated."""
state_value = str(self.dps(self._dp_id))
if state_value in self._idle_status_list:
self._state = STATE_IDLE
elif state_value in self._docked_status_list:
self._state = STATE_DOCKED
elif state_value == self._config[CONF_RETURNING_STATUS_VALUE]:
self._state = STATE_RETURNING
elif state_value == self._config[CONF_PAUSED_STATE]:
self._state = STATE_PAUSED
else:
self._state = STATE_CLEANING
if self.has_config(CONF_BATTERY_DP):
self._battery_level = self.dps_conf(CONF_BATTERY_DP)
self._cleaning_mode = ""
if self.has_config(CONF_MODES):
self._cleaning_mode = self.dps_conf(CONF_MODE_DP)
self._attrs[MODE] = self._cleaning_mode
self._fan_speed = ""
if self.has_config(CONF_FAN_SPEEDS):
self._fan_speed = self.dps_conf(CONF_FAN_SPEED_DP)
if self.has_config(CONF_CLEAN_TIME_DP):
self._attrs[CLEAN_TIME] = self.dps_conf(CONF_CLEAN_TIME_DP)
if self.has_config(CONF_CLEAN_AREA_DP):
self._attrs[CLEAN_AREA] = self.dps_conf(CONF_CLEAN_AREA_DP)
if self.has_config(CONF_CLEAN_RECORD_DP):
self._attrs[CLEAN_RECORD] = self.dps_conf(CONF_CLEAN_RECORD_DP)
if self.has_config(CONF_FAULT_DP):
self._attrs[FAULT] = self.dps_conf(CONF_FAULT_DP)
if self._attrs[FAULT] != 0:
self._state = STATE_ERROR
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaVacuum, flow_schema)