Merge branch 'rospogrigio-master'

This commit is contained in:
sibowler
2022-05-29 06:55:46 +10:00
23 changed files with 1316 additions and 116 deletions

View File

@@ -44,6 +44,8 @@ localtuya:
local_key: xxxxx
friendly_name: Tuya Device
protocol_version: "3.3"
scan_interval: # optional, only needed if energy monitoring values are not updating
seconds: 30 # Values less than 10 seconds may cause stability issues
entities:
- platform: binary_sensor
friendly_name: Plug Status
@@ -63,7 +65,16 @@ localtuya:
- platform: fan
friendly_name: Device Fan
id: 3
id: 3 # dps for on/off state
fan_direction: 4 # Optional, dps for fan direction
fan_direction_fwd: forward # String for the forward direction
fan_direction_rev: reverse # String for the reverse direction
fan_ordered_list: low,medium,high,auto # Optional, If this is used it will not use the min and max integers.
fan_oscilating_control: 4 # Optional, dps for fan osciallation
fan_speed_control: 3 # Optional, if ordered list not used, dps for speed control
fan_speed_min: 1 # Optional, if ordered list not used, minimum integer for speed range
fan_speed_max: 10 # Optional, if ordered list not used, maximum integer for speed range
- platform: light
friendly_name: Device Light
@@ -71,6 +82,7 @@ localtuya:
color_mode: 21 # Optional, usually 2 or 21, default: "none"
brightness: 22 # Optional, usually 3 or 22, default: "none"
color_temp: 23 # Optional, usually 4 or 23, default: "none"
color_temp_reverse: false # Optional, default: false
color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none"
brightness_lower: 29 # Optional, usually 0 or 29, default: 29
brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000
@@ -79,7 +91,6 @@ localtuya:
scene: 25 # Optional, usually 6 (RGB_HSV) or 25 (HSV), default: "none"
music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False"
- platform: sensor
friendly_name: Plug Voltage
id: 20
@@ -112,9 +123,13 @@ select one of these, or manually input all the parameters.
![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png)
If you have selected one entry, you only need to input the device's Friendly Name and the localKey.
Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues.
Once you press "Submit", the connection is tested to check that everything works.
![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png)
![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png)
Then, it's time to add the entities: this step will take place several times. First, select the entity type from the drop-down menu to set it up.
After you have defined all the needed entities, leave the "Do not add more entities" checkbox checked: this will complete the procedure.
@@ -140,6 +155,7 @@ You can obtain Energy monitoring (voltage, current) in two different ways:
Note: Voltage and Consumption usually include the first decimal. You will need to scale the parament by 0.1 to get the correct values.
1) Access the voltage/current/current_consumption attributes of a switch, and define template sensors
Note: these values are already divided by 10 for Voltage and Consumption
1) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended.
```
sensor:
@@ -190,3 +206,6 @@ TradeFace, for being the only one to provide the correct code for communication
sean6541, for the working (standard) Python Handler for Tuya devices.
postlund, for the ideas, for coding 95% of the refactoring and boosting the quality of this repo to levels hard to imagine (by me, at least) and teaching me A LOT of how things work in Home Assistant.
<a href="https://www.buymeacoffee.com/rospogrigio" target="_blank"><img src="https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
<a href="https://paypal.me/rospogrigio" target="_blank"><img src="https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_37x23.jpg" border="0" alt="PayPal Logo" style="height: auto !important;width: auto !important;"></a>

View File

@@ -53,6 +53,20 @@ 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

@@ -0,0 +1,394 @@
"""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,6 +1,7 @@
"""Code shared between all platforms."""
import asyncio
import logging
from datetime import timedelta
from homeassistant.const import (
CONF_DEVICE_ID,
@@ -9,8 +10,10 @@ 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,
@@ -117,6 +120,7 @@ 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
@@ -166,6 +170,16 @@ 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:
@@ -173,6 +187,10 @@ 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
@@ -223,7 +241,9 @@ 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")
@@ -253,12 +273,12 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
def _update_handler(status):
"""Update entity state when status was updated."""
if status is not None:
self._status = status
if status is None:
status = {}
if self._status != status:
self._status = status.copy()
if 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,6 +14,7 @@ from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
@@ -44,6 +45,7 @@ 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,
}
)
@@ -55,6 +57,7 @@ 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,
}
)
@@ -90,6 +93,7 @@ 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,6 +16,7 @@ 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
@@ -34,13 +35,53 @@ CONF_SPAN_TIME = "span_time"
# fan
CONF_FAN_SPEED_CONTROL = "fan_speed_control"
CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control"
CONF_FAN_SPEED_LOW = "fan_speed_low"
CONF_FAN_SPEED_MEDIUM = "fan_speed_medium"
CONF_FAN_SPEED_HIGH = "fan_speed_high"
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"
# 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"
@@ -48,6 +89,7 @@ DOMAIN = "localtuya"
# Platforms in this list must support config flows
PLATFORMS = [
"binary_sensor",
"climate",
"cover",
"fan",
"light",
@@ -55,6 +97,7 @@ PLATFORMS = [
"select",
"sensor",
"switch",
"vacuum",
]
TUYA_DEVICE = "tuya_device"

View File

@@ -1,26 +1,37 @@
"""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,
SPEED_HIGH,
SPEED_LOW,
SPEED_MEDIUM,
SPEED_OFF,
SUPPORT_DIRECTION,
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_HIGH,
CONF_FAN_SPEED_LOW,
CONF_FAN_SPEED_MEDIUM,
CONF_FAN_SPEED_MAX,
CONF_FAN_SPEED_MIN,
)
_LOGGER = logging.getLogger(__name__)
@@ -31,15 +42,12 @@ 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_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"]
),
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,
}
@@ -56,28 +64,46 @@ 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 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]
def percentage(self):
"""Return the current percentage."""
return self._percentage
async def async_turn_on(
self,
@@ -87,76 +113,143 @@ 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 speed is not None:
await self.async_set_speed(speed)
if percentage is not None:
await self.async_set_percentage(percentage)
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_speed(self, speed: str) -> None:
async def async_set_percentage(self, percentage):
"""Set the speed of the fan."""
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),
}
_LOGGER.debug("Fan async_set_percentage: %s", percentage)
if speed == SPEED_OFF:
await self._device.set_dp(False, self._dp_id)
else:
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(
mapping.get(speed), self._config.get(CONF_FAN_SPEED_CONTROL)
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),
)
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()
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."""
supports = 0
features = 0
if self.has_config(CONF_FAN_OSCILLATING_CONTROL):
supports |= SUPPORT_OSCILLATE
if self.has_config(CONF_FAN_SPEED_CONTROL):
supports |= SUPPORT_SET_SPEED
features |= SUPPORT_OSCILLATE
return supports
if self.has_config(CONF_FAN_SPEED_CONTROL):
features |= 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
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)
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),
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,
)
self._speed = None
if current_speed is not None:
self._percentage = ordered_list_item_to_percentage(
self._ordered_list, current_speed
)
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)
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,15 +27,17 @@ 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
@@ -117,6 +119,11 @@ 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}
@@ -146,13 +153,14 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
CONF_BRIGHTNESS_UPPER, DEFAULT_UPPER_BRIGHTNESS
)
self._upper_color_temp = self._upper_brightness
self._max_mired = round(
MIRED_TO_KELVIN_CONST
/ self._config.get(CONF_COLOR_TEMP_MIN_KELVIN, DEFAULT_MIN_KELVIN)
self._max_mired = color_util.color_temperature_kelvin_to_mired(
self._config.get(CONF_COLOR_TEMP_MIN_KELVIN, DEFAULT_MIN_KELVIN)
)
self._min_mired = round(
MIRED_TO_KELVIN_CONST
/ self._config.get(CONF_COLOR_TEMP_MAX_KELVIN, DEFAULT_MAX_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._hs = None
self._effect = None
@@ -199,11 +207,16 @@ 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)
* self._color_temp
* color_temp_value
)
)
return None
@@ -364,10 +377,17 @@ 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))
* (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired)
* (mired - self._min_mired)
)
states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE
states[self._config.get(CONF_BRIGHTNESS)] = brightness

View File

@@ -4,10 +4,7 @@ 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,6 +21,7 @@ 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.
@@ -61,6 +62,7 @@ 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"
@@ -76,6 +78,9 @@ 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
@@ -90,11 +95,13 @@ 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]}},
},
}
@@ -292,6 +299,8 @@ 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)
@@ -478,6 +487,26 @@ 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.
@@ -582,6 +611,9 @@ 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
elif command_hb == 0x0D:
json_data["dps"] = self.dps_to_request
@@ -591,7 +623,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
if self.version == 3.3:
payload = self.cipher.encrypt(payload, False)
if command_hb != 0x0A:
if command_hb not in [0x0A, 0x12]:
# add the 3.3 header
payload = PROTOCOL_33_HEADER + payload
elif command == SET:

View File

@@ -4,10 +4,7 @@ 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,6 +20,7 @@
"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"
}
},

View File

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

View File

@@ -29,7 +29,8 @@
"host": "Host",
"device_id": "Device ID",
"local_key": "Local key",
"protocol_version": "Protocol Version"
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)"
}
},
"pick_entity_type": {
@@ -60,25 +61,61 @@
"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 ;"
"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)"
}
}
}
@@ -93,6 +130,7 @@
"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)"
}
},
@@ -116,25 +154,61 @@
"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 ;"
"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)"
}
},
"yaml_import": {

View File

@@ -0,0 +1,217 @@
{
"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

@@ -0,0 +1,258 @@
"""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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
img/11-config_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
img/6-project_date.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
img/7-auth_keys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
img/8-user_id.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
img/9-cloud_setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

25
info.md
View File

@@ -43,6 +43,8 @@ localtuya:
local_key: xxxxx
friendly_name: Tuya Device
protocol_version: "3.3"
scan_interval: # optional, only needed if energy monitoring values are not updating
seconds: 30 # Values less than 10 seconds may cause stability issues
entities:
- platform: binary_sensor
friendly_name: Plug Status
@@ -66,7 +68,18 @@ localtuya:
- platform: light
friendly_name: Device Light
id: 4
id: 4 # Usually 1 or 20
color_mode: 21 # Optional, usually 2 or 21, default: "none"
brightness: 22 # Optional, usually 3 or 22, default: "none"
color_temp: 23 # Optional, usually 4 or 23, default: "none"
color_temp_reverse: false # Optional, default: false
color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none"
brightness_lower: 29 # Optional, usually 0 or 29, default: 29
brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000
color_temp_min_kelvin: 2700 # Optional, default: 2700
color_temp_max_kelvin: 6500 # Optional, default: 6500
scene: 25 # Optional, usually 6 (RGB_HSV) or 25 (HSV), default: "none"
music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False"
- platform: sensor
friendly_name: Plug Voltage
@@ -98,9 +111,12 @@ select one of these, or manually input all the parameters.
![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png)
If you have selected one entry, you just have to input the Friendly Name of the Device, and the localKey.
Once you press "Submit", the connection will be tested to check that everything works, in order to proceed.
![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png)
Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues.
Once you press "Submit", the connection is tested to check that everything works.
![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png)
Then, it's time to add the entities: this step will take place several times. Select the entity type from the drop-down menu to set it up.
After you have defined all the needed entities leave the "Do not add more entities" checkbox checked: this will complete the procedure.
@@ -122,7 +138,8 @@ After all the entities have been configured, the procedure is complete, and the
Energy monitoring (voltage, current...) values can be obtained in two different ways:
1) creating individual sensors, each one with the desired name. Note: Voltage and Consumption usually include the first decimal, so 0.1 as "scaling" parameter shall be used in order to get the correct values.
2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption):
2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption)
3) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended.
```
sensor: