Rebase from upstream

This commit is contained in:
sibowler
2022-07-13 20:35:42 +10:00
parent 54dbc3a359
commit 364569bad8
10 changed files with 235 additions and 18 deletions

View File

@@ -52,6 +52,8 @@ class LocaltuyaBinarySensor(LocalTuyaEntity, BinarySensorEntity):
return self._config.get(CONF_DEVICE_CLASS) return self._config.get(CONF_DEVICE_CLASS)
def status_updated(self): def status_updated(self):
super().status_updated()
"""Device status was updated.""" """Device status was updated."""
state = str(self.dps(self._dp_id)).lower() state = str(self.dps(self._dp_id)).lower()
if state == self._config[CONF_STATE_ON].lower(): if state == self._config[CONF_STATE_ON].lower():
@@ -63,6 +65,11 @@ class LocaltuyaBinarySensor(LocalTuyaEntity, BinarySensorEntity):
"State for entity %s did not match state patterns", self.entity_id "State for entity %s did not match state patterns", self.entity_id
) )
# No need to restore state for a sensor
async def restore_state_when_connected(self):
"""Do nothing for a sensor"""
return
async_setup_entry = partial( async_setup_entry = partial(
async_setup_entry, DOMAIN, LocaltuyaBinarySensor, flow_schema async_setup_entry, DOMAIN, LocaltuyaBinarySensor, flow_schema

View File

@@ -13,6 +13,7 @@ from homeassistant.const import (
CONF_ID, CONF_ID,
CONF_PLATFORM, CONF_PLATFORM,
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
STATE_UNKNOWN,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
@@ -31,6 +32,9 @@ from .const import (
DATA_CLOUD, DATA_CLOUD,
DOMAIN, DOMAIN,
TUYA_DEVICES, TUYA_DEVICES,
CONF_DEFAULT_VALUE,
ATTR_STATE,
CONF_RESTORE_ON_RECONNECT,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -91,6 +95,8 @@ async def async_setup_entry(
entity_config[CONF_ID], entity_config[CONF_ID],
) )
) )
#Once the entities have been created, add to the TuyaDevice instance
tuyainterface.add_entities(entities)
async_add_entities(entities) async_add_entities(entities)
@@ -135,6 +141,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._connect_task = None self._connect_task = None
self._disconnect_task = None self._disconnect_task = None
self._unsub_interval = None self._unsub_interval = None
self._entities = []
self._local_key = self._dev_config_entry[CONF_LOCAL_KEY] self._local_key = self._dev_config_entry[CONF_LOCAL_KEY]
self.set_logger(_LOGGER, self._dev_config_entry[CONF_DEVICE_ID]) self.set_logger(_LOGGER, self._dev_config_entry[CONF_DEVICE_ID])
@@ -142,6 +149,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
for entity in self._dev_config_entry[CONF_ENTITIES]: for entity in self._dev_config_entry[CONF_ENTITIES]:
self.dps_to_request[entity[CONF_ID]] = None self.dps_to_request[entity[CONF_ID]] = None
def add_entities(self, entities):
"""Set the entities associated with this device"""
self._entities.extend(entities)
@property @property
def connected(self): def connected(self):
"""Return if connected to device.""" """Return if connected to device."""
@@ -173,6 +184,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self.status_updated(status) self.status_updated(status)
# Attempt to restore status for all entites that need to first set the DPS value before the device will respond with status.
for entity in self._entities:
await entity.restore_state_when_connected()
def _new_entity_handler(entity_id): def _new_entity_handler(entity_id):
self.debug( self.debug(
"New entity %s was added to %s", "New entity %s was added to %s",
@@ -254,7 +269,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
try: try:
await self._interface.set_dp(state, dp_index) await self._interface.set_dp(state, dp_index)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
self.exception("Failed to set DP %d to %d", dp_index, state) self.exception("Failed to set DP %d to %s", dp_index, str(state))
else: else:
self.error( self.error(
"Not connected to device %s", self._dev_config_entry[CONF_FRIENDLY_NAME] "Not connected to device %s", self._dev_config_entry[CONF_FRIENDLY_NAME]
@@ -305,6 +320,16 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
self._config = get_entity_config(config_entry, dp_id) self._config = get_entity_config(config_entry, dp_id)
self._dp_id = dp_id self._dp_id = dp_id
self._status = {} self._status = {}
self._state = None
self._last_state = None
#Default value is available to be provided by Platform entities if required
self._default_value = self._config.get(CONF_DEFAULT_VALUE)
#Restore on connect setting is available to be provided by Platform entities if required
self._restore_on_reconnect = (
self._config.get(CONF_RESTORE_ON_RECONNECT) or False
)
self.set_logger(logger, self._dev_config_entry[CONF_DEVICE_ID]) self.set_logger(logger, self._dev_config_entry[CONF_DEVICE_ID])
async def async_added_to_hass(self): async def async_added_to_hass(self):
@@ -325,6 +350,8 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
self._status = status.copy() self._status = status.copy()
if status: if status:
self.status_updated() self.status_updated()
# Update HA
self.schedule_update_ha_state() self.schedule_update_ha_state()
signal = f"localtuya_{self._dev_config_entry[CONF_DEVICE_ID]}" signal = f"localtuya_{self._dev_config_entry[CONF_DEVICE_ID]}"
@@ -336,6 +363,20 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
signal = f"localtuya_entity_{self._dev_config_entry[CONF_DEVICE_ID]}" signal = f"localtuya_entity_{self._dev_config_entry[CONF_DEVICE_ID]}"
async_dispatcher_send(self.hass, signal, self.entity_id) async_dispatcher_send(self.hass, signal, self.entity_id)
@property
def extra_state_attributes(self):
"""Return entity specific state attributes to be saved & then available for restore
when the entity is restored at startup.
"""
attributes = {}
if self._state is not None:
attributes[ATTR_STATE] = self._state
elif self._last_state is not None:
attributes[ATTR_STATE] = self._last_state
self.debug("Entity %s - Additional attributes: %s", self.name, attributes)
return attributes
@property @property
def device_info(self): def device_info(self):
"""Return device information for the device registry.""" """Return device information for the device registry."""
@@ -408,9 +449,83 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
Override in subclasses and update entity specific state. Override in subclasses and update entity specific state.
""" """
state = self.dps(self._dp_id)
self._state = state
# Keep record in last_state as long as not during connection/re-connection,
# as last state will be used to restore the previous state
if (state is not None) and (self._device._connect_task is None):
self._last_state = state
def status_restored(self, stored_state): def status_restored(self, stored_state):
"""Device status was restored. """Device status was restored.
Override in subclasses and update entity specific state. Override in subclasses and update entity specific state.
""" """
raw_state = stored_state.attributes.get(ATTR_STATE)
if raw_state is not None:
# (stored_state.state == "unavailable") | (stored_state.state == "unknown")
# ):
self._last_state = raw_state
self.debug(
"Restoring state for entity: %s - state: %s",
self.name,
str(self._last_state),
)
def default_value(self):
"""Default value of this entity
Override in subclasses to specify the default value for the entity.
"""
# Check if default value has been set - if not, default to the entity defaults.
if self._default_value is None:
self._default_value = self.entity_default_value()
return self._default_value
def entity_default_value(self):
"""Default value of the entity type
Override in subclasses to specify the default value for the entity.
"""
return 0
@property
def restore_on_reconnect(self):
"""Returns whether the last state should be restored on a reconnect - useful where the device loses settings if powered off"""
return self._restore_on_reconnect
async def restore_state_when_connected(self):
"""Restore if restore_on_reconnect is set, or if no status has been yet found - which indicates a DPS that needs to be set before it starts returning status"""
if not self.restore_on_reconnect and (str(self._dp_id) in self._status):
self.debug(
"Entity %s (DP %d) - Not restoring as restore on reconnect is disabled for this entity and the entity has an initial status",
self.name,
self._dp_id,
)
return
self.debug("Attempting to restore state for entity: %s", self.name)
# Attempt to restore the current state - in case reset.
restore_state = self._state
# If no state stored in the entity currently, go from last saved state
if (restore_state == STATE_UNKNOWN) | (restore_state is None):
self.debug("No current state for entity")
restore_state = self._last_state
# If no current or saved state, then use the default value
if restore_state is None:
self.debug("No last restored state - using default")
restore_state = self.default_value()
self.debug(
"Entity %s (DP %d) - Restoring state: %s",
self.name,
self._dp_id,
str(restore_state),
)
# Manually initialise
await self._device.set_dp(restore_state, self._dp_id)

View File

@@ -44,6 +44,7 @@ from .const import (
DATA_DISCOVERY, DATA_DISCOVERY,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
CONF_MANUAL_DPS,
) )
from .discovery import discover from .discovery import discover
@@ -88,6 +89,7 @@ CONFIGURE_DEVICE_SCHEMA = vol.Schema(
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, vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Optional(CONF_MANUAL_DPS): str,
} }
) )
@@ -99,6 +101,7 @@ DEVICE_SCHEMA = vol.Schema(
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, vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Optional(CONF_MANUAL_DPS): cv.string,
} }
) )
@@ -140,6 +143,7 @@ def options_schema(entities):
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.Optional(CONF_SCAN_INTERVAL): int,
vol.Optional(CONF_MANUAL_DPS): str,
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),
@@ -240,6 +244,22 @@ async def validate_input(hass: core.HomeAssistant, data):
) )
detected_dps = await interface.detect_available_dps() detected_dps = await interface.detect_available_dps()
# if manual DPs are set, merge these.
_LOGGER.debug("Detected DPS: %s", detected_dps)
if CONF_MANUAL_DPS in data:
manual_dps_list = data[CONF_MANUAL_DPS].split(",")
_LOGGER.debug(
"Manual DPS Setting: %s (%s)", data[CONF_MANUAL_DPS], manual_dps_list
)
# merge the lists
for new_dps in manual_dps_list:
# trim off any whitespace
new_dps = new_dps.strip()
if new_dps not in detected_dps:
detected_dps[new_dps] = -1
except (ConnectionRefusedError, ConnectionResetError) as ex: except (ConnectionRefusedError, ConnectionResetError) as ex:
raise CannotConnect from ex raise CannotConnect from ex
except ValueError as ex: except ValueError as ex:
@@ -253,6 +273,8 @@ async def validate_input(hass: core.HomeAssistant, data):
if not detected_dps: if not detected_dps:
raise EmptyDpsList raise EmptyDpsList
_LOGGER.debug("Total DPS: %s", detected_dps)
return dps_string_list(detected_dps) return dps_string_list(detected_dps)

View File

@@ -35,11 +35,14 @@ CONF_PRODUCT_KEY = "product_key"
CONF_PRODUCT_NAME = "product_name" CONF_PRODUCT_NAME = "product_name"
CONF_USER_ID = "user_id" CONF_USER_ID = "user_id"
CONF_ACTION = "action" CONF_ACTION = "action"
CONF_ADD_DEVICE = "add_device" CONF_ADD_DEVICE = "add_device"
CONF_EDIT_DEVICE = "edit_device" CONF_EDIT_DEVICE = "edit_device"
CONF_SETUP_CLOUD = "setup_cloud" CONF_SETUP_CLOUD = "setup_cloud"
CONF_NO_CLOUD = "no_cloud" CONF_NO_CLOUD = "no_cloud"
CONF_MANUAL_DPS = "manual_dps_strings"
CONF_DEFAULT_VALUE = "dps_default_value"
# light # light
CONF_BRIGHTNESS_LOWER = "brightness_lower" CONF_BRIGHTNESS_LOWER = "brightness_lower"
@@ -113,3 +116,16 @@ CONF_FAULT_DP = "fault_dp"
CONF_PAUSED_STATE = "paused_state" CONF_PAUSED_STATE = "paused_state"
CONF_RETURN_MODE = "return_mode" CONF_RETURN_MODE = "return_mode"
CONF_STOP_STATUS = "stop_status" CONF_STOP_STATUS = "stop_status"
# number
CONF_MIN_VALUE = "min_value"
CONF_MAX_VALUE = "max_value"
CONF_STEPSIZE_VALUE = "step_size"
# select
CONF_OPTIONS = "select_options"
CONF_OPTIONS_FRIENDLY = "select_options_friendly"
# States
ATTR_STATE = "raw_state"
CONF_RESTORE_ON_RECONNECT = "restore_on_reconnect"

View File

@@ -189,6 +189,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
self.debug("Restored cover position %s", self._current_cover_position) self.debug("Restored cover position %s", self._current_cover_position)
def status_updated(self): def status_updated(self):
super.status_updated(self)
"""Device status was updated.""" """Device status was updated."""
self._previous_state = self._state self._previous_state = self._state
self._state = self.dps(self._dp_id) self._state = self.dps(self._dp_id)

View File

@@ -5,13 +5,19 @@ from functools import partial
import voluptuous as vol import voluptuous as vol
from homeassistant.components.number import DOMAIN, NumberEntity 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 homeassistant.helpers.restore_state import RestoreEntity
from .common import LocalTuyaEntity, async_setup_entry from .common import LocalTuyaEntity, async_setup_entry
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_MIN_VALUE = "min_value" from .const import (
CONF_MAX_VALUE = "max_value" CONF_DEFAULT_VALUE,
CONF_MIN_VALUE,
CONF_MAX_VALUE,
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
)
DEFAULT_MIN = 0 DEFAULT_MIN = 0
DEFAULT_MAX = 100000 DEFAULT_MAX = 100000
@@ -28,10 +34,12 @@ def flow_schema(dps):
vol.Coerce(float), vol.Coerce(float),
vol.Range(min=-1000000.0, max=1000000.0), vol.Range(min=-1000000.0, max=1000000.0),
), ),
vol.Optional(CONF_DEFAULT_VALUE): str,
vol.Required(CONF_RESTORE_ON_RECONNECT): bool,
} }
class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): class LocaltuyaNumber(LocalTuyaEntity, NumberEntity, RestoreEntity):
"""Representation of a Tuya Number.""" """Representation of a Tuya Number."""
def __init__( def __init__(
@@ -51,6 +59,11 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity):
self._max_value = self._config.get(CONF_MAX_VALUE) self._max_value = self._config.get(CONF_MAX_VALUE)
#Override standard default value handling to cast to a float
default_value = self._config.get(CONF_DEFAULT_VALUE)
if default_value is not None:
self._default_value = float(default_value)
@property @property
def value(self) -> float: def value(self) -> float:
"""Return sensor state.""" """Return sensor state."""
@@ -75,10 +88,9 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity):
"""Update the current value.""" """Update the current value."""
await self._device.set_dp(value, self._dp_id) await self._device.set_dp(value, self._dp_id)
def status_updated(self): # Default value is the minimum value
"""Device status was updated.""" def entity_default_value(self):
state = self.dps(self._dp_id) return self._min_value
self._state = state
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaNumber, flow_schema) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaNumber, flow_schema)

View File

@@ -4,14 +4,21 @@ from functools import partial
import voluptuous as vol import voluptuous as vol
from homeassistant.components.select import DOMAIN, SelectEntity 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 from .common import LocalTuyaEntity, async_setup_entry
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_OPTIONS = "select_options" from .const import (
CONF_OPTIONS_FRIENDLY = "select_options_friendly" CONF_OPTIONS,
CONF_OPTIONS_FRIENDLY,
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
)
def flow_schema(dps): def flow_schema(dps):
@@ -19,6 +26,8 @@ def flow_schema(dps):
return { return {
vol.Required(CONF_OPTIONS): str, vol.Required(CONF_OPTIONS): str,
vol.Optional(CONF_OPTIONS_FRIENDLY): str, vol.Optional(CONF_OPTIONS_FRIENDLY): str,
vol.Optional(CONF_DEFAULT_VALUE): str,
vol.Required(CONF_RESTORE_ON_RECONNECT): bool,
} }
@@ -91,10 +100,23 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity):
await self._device.set_dp(option_value, self._dp_id) await self._device.set_dp(option_value, self._dp_id)
def status_updated(self): def status_updated(self):
super().status_updated()
"""Device status was updated.""" """Device status was updated."""
state = self.dps(self._dp_id) state = self.dps(self._dp_id)
self._state_friendly = self._display_options[self._valid_options.index(state)]
self._state = state # Check that received status update for this entity.
if state is not None:
try:
self._state_friendly = self._display_options[
self._valid_options.index(state)
]
except Exception: # pylint: disable=broad-except
# Friendly value couldn't be mapped
self._state_friendly = state
# Default value is the first option
def entity_default_value(self):
return self._valid_options[0]
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSelect, flow_schema) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSelect, flow_schema)

View File

@@ -66,5 +66,10 @@ class LocaltuyaSensor(LocalTuyaEntity):
state = round(state * scale_factor, DEFAULT_PRECISION) state = round(state * scale_factor, DEFAULT_PRECISION)
self._state = state self._state = state
# No need to restore state for a sensor
async def restore_state_when_connected(self):
"""Do nothing for a sensor"""
return
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSensor, flow_schema) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSensor, flow_schema)

View File

@@ -10,9 +10,12 @@ from .const import (
ATTR_CURRENT, ATTR_CURRENT,
ATTR_CURRENT_CONSUMPTION, ATTR_CURRENT_CONSUMPTION,
ATTR_VOLTAGE, ATTR_VOLTAGE,
ATTR_STATE,
CONF_CURRENT, CONF_CURRENT,
CONF_CURRENT_CONSUMPTION, CONF_CURRENT_CONSUMPTION,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -24,6 +27,8 @@ def flow_schema(dps):
vol.Optional(CONF_CURRENT): vol.In(dps), vol.Optional(CONF_CURRENT): vol.In(dps),
vol.Optional(CONF_CURRENT_CONSUMPTION): vol.In(dps), vol.Optional(CONF_CURRENT_CONSUMPTION): vol.In(dps),
vol.Optional(CONF_VOLTAGE): vol.In(dps), vol.Optional(CONF_VOLTAGE): vol.In(dps),
vol.Optional(CONF_DEFAULT_VALUE): str,
vol.Required(CONF_RESTORE_ON_RECONNECT): bool,
} }
@@ -59,6 +64,12 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
) )
if self.has_config(CONF_VOLTAGE): if self.has_config(CONF_VOLTAGE):
attrs[ATTR_VOLTAGE] = self.dps(self._config[CONF_VOLTAGE]) / 10 attrs[ATTR_VOLTAGE] = self.dps(self._config[CONF_VOLTAGE]) / 10
# Store the state
if self._state is not None:
attrs[ATTR_STATE] = self._state
elif self._last_state is not None:
attrs[ATTR_STATE] = self._last_state
return attrs return attrs
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
@@ -69,9 +80,9 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
"""Turn Tuya switch off.""" """Turn Tuya switch off."""
await self._device.set_dp(False, self._dp_id) await self._device.set_dp(False, self._dp_id)
def status_updated(self): # Default value is the "OFF" state
"""Device status was updated.""" def entity_default_value(self):
self._state = self.dps(self._dp_id) return False
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSwitch, flow_schema) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSwitch, flow_schema)

View File

@@ -96,7 +96,8 @@
"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)", "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)",
"manual_dps_strings": "Manual DPS to add (separated by commas ',') - used when detection is not working (optional)"
} }
}, },
"pick_entity_type": { "pick_entity_type": {
@@ -181,7 +182,12 @@
"preset_set": "Presets Set (optional)", "preset_set": "Presets Set (optional)",
"eco_dp": "Eco DP (optional)", "eco_dp": "Eco DP (optional)",
"eco_value": "Eco value (optional)", "eco_value": "Eco value (optional)",
"heuristic_action": "Enable heuristic action (optional)" "heuristic_action": "Enable heuristic action (optional)",
"dps_default_value": "Default value when un-initialised (optional)",
"restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection",
"min_value": "Minimum Value",
"max_value": "Maximum Value",
"step_size": "Minimum increment between numbers"
} }
} }
} }