Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce auto configure device #33

Merged
merged 60 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
3b0d5f5
Use HA Consts for platforms and change to dict
xZetsubou Sep 30, 2023
f88f077
Merge branch 'auto_configure_device' of https://github.com/xZetsubou/…
xZetsubou Sep 30, 2023
32e58ea
Refresh the token if expired, when request made
xZetsubou Sep 30, 2023
69c7693
Clean up and remove get_access_token from config_flow.py
xZetsubou Sep 30, 2023
56dbe56
revert obtain access token if config_flow installtion.
xZetsubou Sep 30, 2023
82dc83f
Add icon support and entity names now follow device_name.
xZetsubou Oct 3, 2023
ecd6551
add notice for entity friendly name
xZetsubou Oct 3, 2023
30b9d45
Apply device class for all entites.
xZetsubou Oct 3, 2023
0e982da
pushed wrong common on last commit.
xZetsubou Oct 3, 2023
827fe81
Remove OFF STATE relay only on STATE_ON.
xZetsubou Oct 4, 2023
24f9c47
use true as default on state always.
xZetsubou Oct 4, 2023
12c4f20
configure config_entry on migrating
xZetsubou Oct 4, 2023
a9ef261
Scenes and Select Values now separate by comma
xZetsubou Oct 4, 2023
460c0ff
adjust expectaion msgs only shows when device debug true
xZetsubou Oct 4, 2023
08eb35d
adjust name entity
xZetsubou Oct 4, 2023
a2327a3
Sensor: Now Support STATE_CLASS
xZetsubou Oct 4, 2023
88f0627
add context infos for sensors
xZetsubou Oct 4, 2023
024cc5b
Move CONF_STATE TO Const.py
xZetsubou Oct 5, 2023
822e39b
config flow reconnect and passive to optional
xZetsubou Oct 5, 2023
6502026
Climate now MIN AND MAX temp manually setted
xZetsubou Oct 5, 2023
6ee65a3
update climate.py
xZetsubou Oct 5, 2023
57eeacf
add device_class for [switch, cover, numbers]
xZetsubou Oct 5, 2023
4b8500e
restore icon if entity has one [auto_feature]
xZetsubou Oct 5, 2023
1ef4622
add note in climate
xZetsubou Oct 5, 2023
889a4fa
ADD Siren platform
xZetsubou Oct 5, 2023
1d83d7c
ADD auto configure support and tuya data files, new folder
xZetsubou Oct 5, 2023
67c8e1f
Label cover position values
xZetsubou Oct 6, 2023
bfa8177
auto configure: sort entites by id.
xZetsubou Oct 6, 2023
973adcc
adjust parse function for enum
xZetsubou Oct 6, 2023
632e243
parse enum before export template
xZetsubou Oct 6, 2023
ff5bf26
Optimize the token refresh method.
xZetsubou Oct 7, 2023
e5aa96b
refresh token `30 secs before` it expires
xZetsubou Oct 7, 2023
91a24db
just preparing for numbers to support native units
xZetsubou Oct 7, 2023
780f9c8
Adjust tuya_device data by adding localtuya to define configs
xZetsubou Oct 7, 2023
bff5388
add select function to tuya_data selects
xZetsubou Oct 8, 2023
1061663
remove native comments for now
xZetsubou Oct 8, 2023
a06618a
events states_update -> device_updated
xZetsubou Oct 8, 2023
1ccfdc2
add RELAY_STATUS posssibilty to "kg"
xZetsubou Oct 8, 2023
e39e25c
Revert event to states_update
xZetsubou Oct 8, 2023
f34fcb5
Fix: when auto detect fails always return same msg
xZetsubou Oct 8, 2023
89a6577
Auto Detect: Add more codes, entity.
xZetsubou Oct 8, 2023
be0533a
update en.json
xZetsubou Oct 8, 2023
fb87b8e
en.json typo
xZetsubou Oct 8, 2023
427edc1
Auto Detect: Add function for sensors for localtuya configs.
xZetsubou Oct 8, 2023
1c6f443
Add unit of measurement for numbers.
xZetsubou Oct 8, 2023
11f90bd
add more codes
xZetsubou Oct 9, 2023
7d6dc19
Add some stuff for future
xZetsubou Oct 9, 2023
22c9615
update en.json
xZetsubou Oct 9, 2023
602d194
typo ms -> mc
xZetsubou Oct 9, 2023
290cc78
rename configure device flow title
xZetsubou Oct 9, 2023
e98ac40
small adjustments
xZetsubou Oct 9, 2023
ce530a2
Handle tuple if codes isn't in detected dps
xZetsubou Oct 9, 2023
008198c
Dimmer support up to 4 LED
xZetsubou Oct 9, 2023
adbf53b
Auto detect: Fix tuple check and lights brightness typo
xZetsubou Oct 9, 2023
19bbdbb
Auto Configure: Remove duplicated category and fix typo
xZetsubou Oct 10, 2023
298b73d
Revert climate to use min and max dp.
xZetsubou Oct 11, 2023
0e22c6d
Auto Configure: Add Code: Battery Sensors
xZetsubou Oct 11, 2023
1e6889b
Fix: ZigBee `3.4` devices updates affect other nodes on same GW
xZetsubou Oct 11, 2023
62feeb9
Fix `3.3` Sub devices control.
xZetsubou Oct 13, 2023
aaf319d
typo.
xZetsubou Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions custom_components/localtuya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def _shutdown(event):
return True


async def async_migrate_entry(hass, config_entry: ConfigEntry):
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Migrate old entries merging all of them in one."""
new_version = ENTRIES_VERSION
stored_entries = hass.config_entries.async_entries(DOMAIN)
Expand All @@ -183,18 +183,17 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
if config_entry.version == 2:
# Switch config flow to selectors convert DP IDs from int to str require HA 2022.4.
_LOGGER.debug("Migrating config entry from version %s", config_entry.version)
if config_entry.entry_id == stored_entries[0].entry_id:
new_data = stored_entries[0].data.copy()
for device in new_data[CONF_DEVICES]:
i = 0
for _ent in new_data[CONF_DEVICES][device][CONF_ENTITIES]:
ent_items = {}
for k, v in _ent.items():
ent_items[k] = str(v) if type(v) is int else v
new_data[CONF_DEVICES][device][CONF_ENTITIES][i].update(ent_items)
i = i + 1
config_entry.version = new_version
hass.config_entries.async_update_entry(config_entry, data=new_data)
new_data = config_entry.data.copy()
for device in new_data[CONF_DEVICES]:
i = 0
for _ent in new_data[CONF_DEVICES][device][CONF_ENTITIES]:
ent_items = {}
for k, v in _ent.items():
ent_items[k] = str(v) if type(v) is int else v
new_data[CONF_DEVICES][device][CONF_ENTITIES][i].update(ent_items)
i = i + 1
config_entry.version = new_version
hass.config_entries.async_update_entry(config_entry, data=new_data)

_LOGGER.info(
"Entry %s successfully migrated to version %s.",
Expand Down
17 changes: 4 additions & 13 deletions custom_components/localtuya/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
from homeassistant.const import CONF_DEVICE_CLASS

from .common import LocalTuyaEntity, async_setup_entry
from .const import CONF_STATE_ON

_LOGGER = logging.getLogger(__name__)

CONF_STATE_ON = "state_on"
CONF_STATE_OFF = "state_off"


def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Required(CONF_STATE_ON, default="True"): str,
vol.Required(CONF_STATE_OFF, default="False"): str,
# vol.Required(CONF_STATE_OFF, default="False"): str,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
}

Expand All @@ -46,24 +46,15 @@ def is_on(self):
"""Return sensor state."""
return self._is_on

@property
def device_class(self):
"""Return the class of this device."""
return self._config.get(CONF_DEVICE_CLASS)

def status_updated(self):
"""Device status was updated."""
super().status_updated()

state = str(self.dps(self._dp_id)).lower()
if state == self._config[CONF_STATE_ON].lower():
if state == self._config[CONF_STATE_ON].lower() or state == "true":
self._is_on = True
elif state == self._config[CONF_STATE_OFF].lower():
self._is_on = False
else:
self.warning(
"State for entity %s did not match state patterns", self.entity_id
)
self._is_on = False

# No need to restore state for a sensor
async def restore_state_when_connected(self):
Expand Down
116 changes: 73 additions & 43 deletions custom_components/localtuya/climate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Platform to locally control Tuya-based climate devices."""
"""Platform to locally control Tuya-based climate devices.
# PRESETS and HVAC_MODE Needs to be handle in better way.
"""
import asyncio
import logging
from functools import partial
Expand All @@ -12,11 +14,8 @@
ClimateEntity,
)
from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
HVACMode,
HVACAction,
PRESET_AWAY,
PRESET_ECO,
PRESET_HOME,
Expand Down Expand Up @@ -45,59 +44,79 @@
CONF_HVAC_ACTION_SET,
CONF_HVAC_MODE_DP,
CONF_HVAC_MODE_SET,
CONF_MAX_TEMP_DP,
CONF_MIN_TEMP_DP,
CONF_PRECISION,
CONF_PRESET_DP,
CONF_PRESET_SET,
CONF_TARGET_PRECISION,
CONF_TARGET_TEMPERATURE_DP,
CONF_TEMPERATURE_STEP,
CONF_MIN_TEMP_DP,
CONF_MAX_TEMP_DP,
)

_LOGGER = logging.getLogger(__name__)

HVAC_MODE_SETS = {
"manual/auto": {
HVAC_MODE_HEAT: "manual",
HVAC_MODE_AUTO: "auto",
HVACMode.HEAT: "manual",
HVACMode.AUTO: "auto",
},
"Manual/Auto": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Auto",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Auto",
},
"Manual/Program": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Program",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Program",
},
"auto/cold/hot/wet": {
HVACMode.AUTO: "auto",
HVACMode.COOL: "cold",
HVACMode.HEAT: "hot",
HVACMode.DRY: "wet",
},
"m/p": {
HVAC_MODE_HEAT: "m",
HVAC_MODE_AUTO: "p",
HVACMode.HEAT: "m",
HVACMode.AUTO: "p",
},
"True/False": {
HVAC_MODE_HEAT: True,
HVACMode.HEAT: True,
},
"1/0": {
HVAC_MODE_HEAT: "1",
HVAC_MODE_AUTO: "0",
HVACMode.HEAT: "1",
HVACMode.AUTO: "0",
},
"smart/auto": {
HVACMode.HEAT_COOL: "1",
HVACMode.AUTO: "auto",
},
}
HVAC_ACTION_SETS = {
"True/False": {
CURRENT_HVAC_HEAT: True,
CURRENT_HVAC_IDLE: False,
HVACAction.HEATING: True,
HVACAction.IDLE: False,
},
"open/close": {
CURRENT_HVAC_HEAT: "open",
CURRENT_HVAC_IDLE: "close",
HVACAction.HEATING: "open",
HVACAction.IDLE: "close",
},
"heating/no_heating": {
CURRENT_HVAC_HEAT: "heating",
CURRENT_HVAC_IDLE: "no_heating",
HVACAction.HEATING: "heating",
HVACAction.IDLE: "no_heating",
},
"heating/cooling": {
HVACAction.HEATING: "heating",
HVACAction.COOLING: "cooling",
HVACAction.IDLE: "ventilation",
HVACAction.OFF: "off",
},
"Heat/Warming": {
CURRENT_HVAC_HEAT: "Heat",
CURRENT_HVAC_IDLE: "Warming",
HVACAction.HEATING: "Heat",
HVACAction.IDLE: "Warming",
},
"heating/warming": {
HVACAction.HEATING: "heating",
HVACAction.IDLE: "warming",
},
}
PRESET_SETS = {
Expand All @@ -106,6 +125,17 @@
PRESET_HOME: "Program",
PRESET_NONE: "Manual",
},
"auto/smart": {
"auto": "Auto",
"smart": "Smart",
},
"auto/manual/smart/comfortable/eco": {
"auto": "Auto",
"manual": "Manual",
"smart": "Smart",
"comfortable": "Comfort",
"eco": "ECO",
},
}

TEMPERATURE_CELSIUS = "celsius"
Expand All @@ -125,8 +155,8 @@ def flow_schema(dps):
vol.Optional(CONF_TEMPERATURE_STEP): _col_to_select(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_MAX_TEMP_DP): _col_to_select(dps, is_dps=True),
vol.Optional(CONF_MIN_TEMP_DP): _col_to_select(dps, is_dps=True),
vol.Optional(CONF_MAX_TEMP_DP): _col_to_select(dps, is_dps=True),
vol.Optional(CONF_PRECISION): _col_to_select(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
Expand Down Expand Up @@ -169,9 +199,9 @@ def __init__(
self._preset_mode = None
self._hvac_action = None
self._precision = float(self._config.get(CONF_PRECISION, DEFAULT_PRECISION))
self._target_precision = float(self._config.get(
CONF_TARGET_PRECISION, self._precision
))
self._target_precision = float(
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), {}
Expand Down Expand Up @@ -231,7 +261,7 @@ 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]
return list(self._conf_hvac_mode_set) + [HVACMode.OFF]

@property
def hvac_action(self):
Expand All @@ -240,22 +270,22 @@ def hvac_action(self):
Need to be one of CURRENT_HVAC_*.
"""
if self._config.get(CONF_HEURISTIC_ACTION, False):
if self._hvac_mode == HVAC_MODE_HEAT:
if self._hvac_mode == HVACMode.HEAT:
if self._current_temperature < (
self._target_temperature - self._precision
):
self._hvac_action = CURRENT_HVAC_HEAT
self._hvac_action = HVACMode.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._hvac_action == HVACMode.HEAT:
self._hvac_action = HVACMode.HEAT
if self._hvac_action == HVACAction.IDLE:
self._hvac_action = HVACAction.IDLE
if (
self._current_temperature + self._precision
) > self._target_temperature:
self._hvac_action = CURRENT_HVAC_IDLE
self._hvac_action = HVACAction.IDLE
return self._hvac_action
return self._hvac_action

Expand Down Expand Up @@ -313,7 +343,7 @@ def set_fan_mode(self, fan_mode):

async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF:
if hvac_mode == HVACMode.OFF:
await self._device.set_dp(False, self._dp_id)
return
if not self._state and self._conf_hvac_mode_dp != self._dp_id:
Expand Down Expand Up @@ -344,7 +374,7 @@ async def async_set_preset_mode(self, preset_mode):
@property
def min_temp(self):
"""Return the minimum temperature."""
if self.has_config(CONF_MIN_TEMP_DP):
if _min_temp := self._config.get(CONF_MIN_TEMP_DP):
return self.dps_conf(CONF_MIN_TEMP_DP)
# DEFAULT_MIN_TEMP is in C
if self.temperature_unit == TEMP_FAHRENHEIT:
Expand Down Expand Up @@ -394,15 +424,15 @@ def status_updated(self):
# Update the HVAC status
if self.has_config(CONF_HVAC_MODE_DP):
if not self._state:
self._hvac_mode = HVAC_MODE_OFF
self._hvac_mode = HVACMode.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
self._hvac_mode = HVACMode.AUTO

# Update the current action
for action, value in self._conf_hvac_action_set.items():
Expand Down
20 changes: 20 additions & 0 deletions custom_components/localtuya/cloud_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def __init__(self, hass, region_code, client_id, secret, user_id):
self._secret = secret
self._user_id = user_id
self._access_token = ""
self._token_expire_time: int = -1

self.device_list = {}

def generate_payload(self, method, timestamp, url, headers, body=None):
Expand All @@ -63,6 +65,10 @@ def generate_payload(self, method, timestamp, url, headers, body=None):

async def async_make_request(self, method, url, body=None, headers={}):
"""Perform requests."""
# obtain new token if expired.
if not self.token_validate and self._token_expire_time != -1:
await self.async_get_access_token()

timestamp = str(int(time.time() * 1000))
payload = self.generate_payload(method, timestamp, url, headers, body)
default_par = {
Expand Down Expand Up @@ -101,6 +107,10 @@ async def async_make_request(self, method, url, body=None, headers={}):

async def async_get_access_token(self):
"""Obtain a valid access token."""
# Reset access token
self._token_expire_time = -1
self._access_token = ""

try:
resp = await self.async_make_request("GET", "/v1.0/token?grant_type=1")
except requests.exceptions.ConnectionError:
Expand All @@ -113,6 +123,8 @@ async def async_get_access_token(self):
if not r_json["success"]:
return f"Error {r_json['code']}: {r_json['msg']}"

req_results = r_json["result"]
self._token_expire_time = int(time.time()) + int(req_results.get("expire_time"))
self._access_token = resp.json()["result"]["access_token"]
return "ok"

Expand Down Expand Up @@ -167,3 +179,11 @@ async def async_get_device_query_properties(self, device_id):
return {}, f"Error {r_json['code']}: {r_json['msg']}"

return r_json["result"], "ok"

@property
def token_validate(self):
"""Return whether token is expired or not"""
cur_time = int(time.time())
expire_time = self._token_expire_time - 30

return expire_time >= cur_time
Loading