Merge branch 'master' into vacuum_platform_new

This commit is contained in:
Rikman
2022-01-13 12:25:49 +01:00
committed by GitHub
16 changed files with 520 additions and 101 deletions

View File

@@ -44,6 +44,8 @@ localtuya:
local_key: xxxxx local_key: xxxxx
friendly_name: Tuya Device friendly_name: Tuya Device
protocol_version: "3.3" 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: entities:
- platform: binary_sensor - platform: binary_sensor
friendly_name: Plug Status friendly_name: Plug Status
@@ -63,7 +65,16 @@ localtuya:
- platform: fan - platform: fan
friendly_name: Device 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 - platform: light
friendly_name: Device Light friendly_name: Device Light
@@ -71,6 +82,7 @@ localtuya:
color_mode: 21 # Optional, usually 2 or 21, default: "none" color_mode: 21 # Optional, usually 2 or 21, default: "none"
brightness: 22 # Optional, usually 3 or 22, default: "none" brightness: 22 # Optional, usually 3 or 22, default: "none"
color_temp: 23 # Optional, usually 4 or 23, 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" color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none"
brightness_lower: 29 # Optional, usually 0 or 29, default: 29 brightness_lower: 29 # Optional, usually 0 or 29, default: 29
brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000 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" 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" music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False"
- platform: sensor - platform: sensor
friendly_name: Plug Voltage friendly_name: Plug Voltage
id: 20 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) ![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. 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. 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. 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. 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. 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 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 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: sensor:

View File

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

View File

@@ -14,6 +14,7 @@ from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_ID, CONF_ID,
CONF_PLATFORM, CONF_PLATFORM,
CONF_SCAN_INTERVAL,
) )
from homeassistant.core import callback from homeassistant.core import callback
@@ -44,6 +45,7 @@ BASIC_INFO_SCHEMA = vol.Schema(
vol.Required(CONF_HOST): str, vol.Required(CONF_HOST): str,
vol.Required(CONF_DEVICE_ID): str, vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), 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_LOCAL_KEY): cv.string,
vol.Required(CONF_FRIENDLY_NAME): cv.string, vol.Required(CONF_FRIENDLY_NAME): cv.string,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), 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_HOST): str,
vol.Required(CONF_LOCAL_KEY): str, vol.Required(CONF_LOCAL_KEY): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Required( vol.Required(
CONF_ENTITIES, description={"suggested_value": entity_names} CONF_ENTITIES, description={"suggested_value": entity_names}
): cv.multi_select(entity_names), ): cv.multi_select(entity_names),

View File

@@ -16,6 +16,7 @@ CONF_COLOR = "color"
CONF_COLOR_MODE = "color_mode" CONF_COLOR_MODE = "color_mode"
CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin" CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin"
CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin" CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin"
CONF_COLOR_TEMP_REVERSE = "color_temp_reverse"
CONF_MUSIC_MODE = "music_mode" CONF_MUSIC_MODE = "music_mode"
# switch # switch
@@ -34,9 +35,12 @@ CONF_SPAN_TIME = "span_time"
# fan # fan
CONF_FAN_SPEED_CONTROL = "fan_speed_control" CONF_FAN_SPEED_CONTROL = "fan_speed_control"
CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control" CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control"
CONF_FAN_SPEED_LOW = "fan_speed_low" CONF_FAN_SPEED_MIN = "fan_speed_min"
CONF_FAN_SPEED_MEDIUM = "fan_speed_medium" CONF_FAN_SPEED_MAX = "fan_speed_max"
CONF_FAN_SPEED_HIGH = "fan_speed_high" 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 # sensor
CONF_SCALING = "scaling" CONF_SCALING = "scaling"
@@ -65,6 +69,16 @@ DATA_DISCOVERY = "discovery"
DOMAIN = "localtuya" DOMAIN = "localtuya"
# Platforms in this list must support config flows # Platforms in this list must support config flows
PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch", "vacuum"] PLATFORMS = [
"binary_sensor",
"cover",
"fan",
"light",
"number",
"select",
"sensor",
"switch",
"vacuum"
]
TUYA_DEVICE = "tuya_device" TUYA_DEVICE = "tuya_device"

View File

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

View File

@@ -27,6 +27,7 @@ from .const import (
CONF_COLOR_MODE, CONF_COLOR_MODE,
CONF_COLOR_TEMP_MAX_KELVIN, CONF_COLOR_TEMP_MAX_KELVIN,
CONF_COLOR_TEMP_MIN_KELVIN, CONF_COLOR_TEMP_MIN_KELVIN,
CONF_COLOR_TEMP_REVERSE,
CONF_MUSIC_MODE, CONF_MUSIC_MODE,
) )
@@ -36,6 +37,8 @@ MIRED_TO_KELVIN_CONST = 1000000
DEFAULT_MIN_KELVIN = 2700 # MIRED 370 DEFAULT_MIN_KELVIN = 2700 # MIRED 370
DEFAULT_MAX_KELVIN = 6500 # MIRED 153 DEFAULT_MAX_KELVIN = 6500 # MIRED 153
DEFAULT_COLOR_TEMP_REVERSE = False
DEFAULT_LOWER_BRIGHTNESS = 29 DEFAULT_LOWER_BRIGHTNESS = 29
DEFAULT_UPPER_BRIGHTNESS = 1000 DEFAULT_UPPER_BRIGHTNESS = 1000
@@ -117,6 +120,11 @@ def flow_schema(dps):
vol.Optional(CONF_COLOR_TEMP_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( vol.Optional(CONF_COLOR_TEMP_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1500, max=8000) 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_SCENE): vol.In(dps),
vol.Optional( vol.Optional(
CONF_MUSIC_MODE, default=False, description={"suggested_value": False} CONF_MUSIC_MODE, default=False, description={"suggested_value": False}
@@ -154,6 +162,9 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
MIRED_TO_KELVIN_CONST MIRED_TO_KELVIN_CONST
/ self._config.get(CONF_COLOR_TEMP_MAX_KELVIN, DEFAULT_MAX_KELVIN) / 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._hs = None
self._effect = None self._effect = None
self._effect_list = [] self._effect_list = []
@@ -199,11 +210,16 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
def color_temp(self): def color_temp(self):
"""Return the color_temp of the light.""" """Return the color_temp of the light."""
if self.has_config(CONF_COLOR_TEMP) and self.is_white_mode: 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( return int(
self._max_mired self._max_mired
- ( - (
((self._max_mired - self._min_mired) / self._upper_color_temp) ((self._max_mired - self._min_mired) / self._upper_color_temp)
* self._color_temp * color_temp_value
) )
) )
return None return None
@@ -364,10 +380,16 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP): if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP):
if brightness is None: if brightness is None:
brightness = self._brightness brightness = self._brightness
color_temp_value = (
(self._max_mired - self._min_mired)
- (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired)
if self._color_temp_reverse
else (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired)
)
color_temp = int( color_temp = int(
self._upper_color_temp self._upper_color_temp
- (self._upper_color_temp / (self._max_mired - self._min_mired)) - (self._upper_color_temp / (self._max_mired - self._min_mired))
* (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) * color_temp_value
) )
states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE
states[self._config.get(CONF_BRIGHTNESS)] = brightness states[self._config.get(CONF_BRIGHTNESS)] = brightness

View File

@@ -0,0 +1,84 @@
"""Platform to present any Tuya DP as a number."""
import logging
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 .common import LocalTuyaEntity, async_setup_entry
_LOGGER = logging.getLogger(__name__)
CONF_MIN_VALUE = "min_value"
CONF_MAX_VALUE = "max_value"
DEFAULT_MIN = 0
DEFAULT_MAX = 100000
def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Optional(CONF_MIN_VALUE, default=DEFAULT_MIN): vol.All(
vol.Coerce(float),
vol.Range(min=-1000000.0, max=1000000.0),
),
vol.Required(CONF_MAX_VALUE, default=DEFAULT_MAX): vol.All(
vol.Coerce(float),
vol.Range(min=-1000000.0, max=1000000.0),
),
}
class LocaltuyaNumber(LocalTuyaEntity, NumberEntity):
"""Representation of a Tuya Number."""
def __init__(
self,
device,
config_entry,
sensorid,
**kwargs,
):
"""Initialize the Tuya sensor."""
super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs)
self._state = STATE_UNKNOWN
self._min_value = DEFAULT_MIN
if CONF_MIN_VALUE in self._config:
self._min_value = self._config.get(CONF_MIN_VALUE)
self._max_value = self._config.get(CONF_MAX_VALUE)
@property
def value(self) -> float:
"""Return sensor state."""
return self._state
@property
def min_value(self) -> float:
"""Return the minimum value."""
return self._min_value
@property
def max_value(self) -> float:
"""Return the maximum value."""
return self._max_value
@property
def device_class(self):
"""Return the class of this device."""
return self._config.get(CONF_DEVICE_CLASS)
async def async_set_value(self, value: float) -> None:
"""Update the current value."""
await self._device.set_dp(value, self._dp_id)
def status_updated(self):
"""Device status was updated."""
state = self.dps(self._dp_id)
self._state = state
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaNumber, flow_schema)

View File

@@ -21,6 +21,7 @@ Functions
json = status() # returns json payload json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3 set_version(version) # 3.1 [default] or 3.3
detect_available_dps() # returns a list of available dps provided by the device 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 add_dps_to_request(dp_index) # adds dp_index to the list of dps used by the
# device (to be queried in the payload) # device (to be queried in the payload)
set_dp(on, dp_index) # Set value of any dps index. 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" SET = "set"
STATUS = "status" STATUS = "status"
HEARTBEAT = "heartbeat" HEARTBEAT = "heartbeat"
UPDATEDPS = "updatedps" # Request refresh of DPS
PROTOCOL_VERSION_BYTES_31 = b"3.1" PROTOCOL_VERSION_BYTES_31 = b"3.1"
PROTOCOL_VERSION_BYTES_33 = b"3.3" PROTOCOL_VERSION_BYTES_33 = b"3.3"
@@ -76,6 +78,9 @@ SUFFIX_VALUE = 0x0000AA55
HEARTBEAT_INTERVAL = 10 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 # This is intended to match requests.json payload at
# https://github.com/codetheweb/tuyapi : # https://github.com/codetheweb/tuyapi :
# type_0a devices require the 0a command as the status request # type_0a devices require the 0a command as the status request
@@ -90,11 +95,13 @@ PAYLOAD_DICT = {
STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}}, STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}}, HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
}, },
"type_0d": { "type_0d": {
STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}}, STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}}, 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] sem = self.listeners[self.HEARTBEAT_SEQNO]
self.listeners[self.HEARTBEAT_SEQNO] = msg self.listeners[self.HEARTBEAT_SEQNO] = msg
sem.release() sem.release()
elif msg.cmd == 0x12:
self.debug("Got normal updatedps response")
elif msg.cmd == 0x08: elif msg.cmd == 0x08:
self.debug("Got status update") self.debug("Got status update")
self.listener(msg) self.listener(msg)
@@ -478,6 +487,26 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
"""Send a heartbeat message.""" """Send a heartbeat message."""
return await self.exchange(HEARTBEAT) 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): async def set_dp(self, value, dp_index):
""" """
Set value (may be any type: bool, int or string) of any dps 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())) json_data["t"] = str(int(time.time()))
if data is not None: 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: elif command_hb == 0x0D:
json_data["dps"] = self.dps_to_request json_data["dps"] = self.dps_to_request
@@ -591,7 +623,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
if self.version == 3.3: if self.version == 3.3:
payload = self.cipher.encrypt(payload, False) payload = self.cipher.encrypt(payload, False)
if command_hb != 0x0A: if command_hb not in [0x0A, 0x12]:
# add the 3.3 header # add the 3.3 header
payload = PROTOCOL_33_HEADER + payload payload = PROTOCOL_33_HEADER + payload
elif command == SET: elif command == SET:

View File

@@ -0,0 +1,100 @@
"""Platform to present any Tuya DP as an enumeration."""
import logging
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 .common import LocalTuyaEntity, async_setup_entry
_LOGGER = logging.getLogger(__name__)
CONF_OPTIONS = "select_options"
CONF_OPTIONS_FRIENDLY = "select_options_friendly"
def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Required(CONF_OPTIONS): str,
vol.Optional(CONF_OPTIONS_FRIENDLY): str,
}
class LocaltuyaSelect(LocalTuyaEntity, SelectEntity):
"""Representation of a Tuya Enumeration."""
def __init__(
self,
device,
config_entry,
sensorid,
**kwargs,
):
"""Initialize the Tuya sensor."""
super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs)
self._state = STATE_UNKNOWN
self._state_friendly = ""
self._valid_options = self._config.get(CONF_OPTIONS).split(";")
# Set Display options
self._display_options = []
display_options_str = ""
if CONF_OPTIONS_FRIENDLY in self._config:
display_options_str = self._config.get(CONF_OPTIONS_FRIENDLY).strip()
_LOGGER.debug("Display Options Configured: %s", display_options_str)
if display_options_str.find(";") >= 0:
self._display_options = display_options_str.split(";")
elif len(display_options_str.strip()) > 0:
self._display_options.append(display_options_str)
else:
# Default display string to raw string
_LOGGER.debug("No Display options configured - defaulting to raw values")
self._display_options = self._valid_options
_LOGGER.debug(
"Total Raw Options: %s - Total Display Options: %s",
str(len(self._valid_options)),
str(len(self._display_options)),
)
if len(self._valid_options) > len(self._display_options):
# If list of display items smaller than list of valid items,
# then default remaining items to be the raw value
_LOGGER.debug(
"Valid options is larger than display options - \
filling up with raw values"
)
for i in range(len(self._display_options), len(self._valid_options)):
self._display_options.append(self._valid_options[i])
@property
def current_option(self) -> str:
"""Return the current value."""
return self._state_friendly
@property
def options(self) -> list:
"""Return the list of values."""
return self._display_options
@property
def device_class(self):
"""Return the class of this device."""
return self._config.get(CONF_DEVICE_CLASS)
async def async_select_option(self, option: str) -> None:
"""Update the current value."""
option_value = self._valid_options[self._display_options.index(option)]
_LOGGER.debug("Sending Option: " + option + " -> " + option_value)
await self._device.set_dp(option_value, self._dp_id)
def status_updated(self):
"""Device status was updated."""
state = self.dps(self._dp_id)
self._state_friendly = self._display_options[self._valid_options.index(state)]
self._state = state
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSelect, flow_schema)

View File

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

View File

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

View File

@@ -29,7 +29,8 @@
"host": "Host", "host": "Host",
"device_id": "Device ID", "device_id": "Device ID",
"local_key": "Local key", "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": { "pick_entity_type": {
@@ -81,17 +82,21 @@
"brightness_lower": "Brightness Lower Value", "brightness_lower": "Brightness Lower Value",
"brightness_upper": "Brightness Upper Value", "brightness_upper": "Brightness Upper Value",
"color_temp": "Color Temperature", "color_temp": "Color Temperature",
"color_temp_reverse": "Color Temperature Reverse",
"color": "Color", "color": "Color",
"color_mode": "Color Mode", "color_mode": "Color Mode",
"color_temp_min_kelvin": "Minimum Color Temperature in K", "color_temp_min_kelvin": "Minimum Color Temperature in K",
"color_temp_max_kelvin": "Maximum Color Temperature in K", "color_temp_max_kelvin": "Maximum Color Temperature in K",
"music_mode": "Music mode available", "music_mode": "Music mode available",
"scene": "Scene", "scene": "Scene",
"fan_speed_control": "Fan Speed Control", "fan_speed_control": "Fan Speed Control dps",
"fan_oscillating_control": "Fan Oscillating Control", "fan_oscillating_control": "Fan Oscillating Control dps",
"fan_speed_low": "Fan Low Speed Setting", "fan_speed_min": "minimum fan speed integer",
"fan_speed_medium": "Fan Medium Speed Setting", "fan_speed_max": "maximum fan speed integer",
"fan_speed_high": "Fan High Speed Setting" "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"
} }
} }
} }
@@ -106,6 +111,7 @@
"host": "Host", "host": "Host",
"local_key": "Local key", "local_key": "Local key",
"protocol_version": "Protocol Version", "protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)",
"entities": "Entities (uncheck an entity to remove it)" "entities": "Entities (uncheck an entity to remove it)"
} }
}, },
@@ -150,17 +156,21 @@
"brightness_lower": "Brightness Lower Value", "brightness_lower": "Brightness Lower Value",
"brightness_upper": "Brightness Upper Value", "brightness_upper": "Brightness Upper Value",
"color_temp": "Color Temperature", "color_temp": "Color Temperature",
"color_temp_reverse": "Color Temperature Reverse",
"color": "Color", "color": "Color",
"color_mode": "Color Mode", "color_mode": "Color Mode",
"color_temp_min_kelvin": "Minimum Color Temperature in K", "color_temp_min_kelvin": "Minimum Color Temperature in K",
"color_temp_max_kelvin": "Maximum Color Temperature in K", "color_temp_max_kelvin": "Maximum Color Temperature in K",
"music_mode": "Music mode available", "music_mode": "Music mode available",
"scene": "Scene", "scene": "Scene",
"fan_speed_control": "Fan Speed Control", "fan_speed_control": "Fan Speed Control dps",
"fan_oscillating_control": "Fan Oscillating Control", "fan_oscillating_control": "Fan Oscillating Control dps",
"fan_speed_low": "Fan Low Speed Setting", "fan_speed_min": "minimum fan speed integer",
"fan_speed_medium": "Fan Medium Speed Setting", "fan_speed_max": "maximum fan speed integer",
"fan_speed_high": "Fan High Speed Setting" "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"
} }
}, },
"yaml_import": { "yaml_import": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "Local Tuya", "name": "Local Tuya",
"domains": ["climate", "cover", "fan", "light", "sensor", "switch"], "domains": ["climate", "cover", "fan", "light", "number", "select", "sensor", "switch"],
"homeassistant": "0.116.0", "homeassistant": "0.116.0",
"iot_class": ["Local Push"] "iot_class": ["Local Push"]
} }

25
info.md
View File

@@ -43,6 +43,8 @@ localtuya:
local_key: xxxxx local_key: xxxxx
friendly_name: Tuya Device friendly_name: Tuya Device
protocol_version: "3.3" 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: entities:
- platform: binary_sensor - platform: binary_sensor
friendly_name: Plug Status friendly_name: Plug Status
@@ -66,7 +68,18 @@ localtuya:
- platform: light - platform: light
friendly_name: Device 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 - platform: sensor
friendly_name: Plug Voltage 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) ![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. 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. 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. 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: 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. 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: sensor:

View File

@@ -3,7 +3,7 @@ codespell==2.0.0
flake8==3.9.2 flake8==3.9.2
mypy==0.901 mypy==0.901
pydocstyle==6.1.1 pydocstyle==6.1.1
cryptography==3.2 cryptography==3.3.2
pylint==2.8.2 pylint==2.8.2
pylint-strict-informational==0.1 pylint-strict-informational==0.1
homeassistant==2021.1.4 homeassistant==2021.7.1

View File

@@ -12,7 +12,7 @@ warn_incomplete_stub = true
warn_redundant_casts = true warn_redundant_casts = true
warn_unused_configs = true warn_unused_configs = true
[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] [mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,homeassistant.components.select.*,homeassistant.components.number.*]
strict = true strict = true
ignore_errors = false ignore_errors = false
warn_unreachable = true warn_unreachable = true