Skip to content

Commit 40eec77

Browse files
authored
Merge pull request #290 from astrinh0/master
Improve hOn entity handling and config-entry service lifecycle
2 parents 1b88b93 + 884ac16 commit 40eec77

6 files changed

Lines changed: 254 additions & 145 deletions

File tree

custom_components/hon/__init__.py

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import asyncio
21
import logging
32
import voluptuous as vol
4-
import aiohttp
5-
import json
6-
import urllib.parse
73
import ast
84

9-
from homeassistant.components.persistent_notification import create
105
from datetime import datetime
116
from dateutil.tz import gettz
12-
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
7+
from homeassistant.config_entries import ConfigEntry
138
from homeassistant.const import ATTR_DEVICE_ID, CONF_EMAIL, CONF_PASSWORD
149
from homeassistant.helpers import config_validation as cv
1510
from homeassistant.core import HomeAssistant, ServiceCall
@@ -25,6 +20,7 @@
2520

2621

2722
_LOGGER = logging.getLogger(__name__)
23+
SERVICE_REGISTRY = "service_registry"
2824

2925

3026
HON_SCHEMA = vol.Schema(
@@ -59,6 +55,11 @@ def get_parameters(call):
5955
parameters_str = str(parameters_str)
6056
return ast.literal_eval(parameters_str)
6157

58+
59+
def _minutes_until(target: datetime, now: datetime) -> int:
60+
"""Return the number of whole minutes until the target time."""
61+
return max(0, int((target - now).total_seconds() / 60))
62+
6263
#def get_device_ids(hass, call):
6364
# device_ids = call.data.get("device_id", [])
6465
#entity_ids = call.data.get("entity_id", [])
@@ -80,8 +81,6 @@ def get_device_ids(hass, call):
8081
return list(device_ids)
8182

8283

83-
from homeassistant.helpers import entity_registry as er
84-
8584
async def async_get_device_ids(hass, call):
8685
device_ids = set(call.data.get("device_id", []))
8786
entity_ids = call.data.get("entity_id", [])
@@ -110,6 +109,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
110109

111110
hass.data.setdefault(DOMAIN, {})
112111
hass.data[DOMAIN][entry.unique_id] = hon
112+
hass.data[DOMAIN].setdefault(SERVICE_REGISTRY, set())
113113

114114
for appliance in hon.appliances:
115115

@@ -130,12 +130,12 @@ async def handle_oven_start(call):
130130

131131
if "start" in call.data:
132132
date = datetime.strptime(call.data.get("start"), "%Y-%m-%d %H:%M:%S").replace(tzinfo=tz)
133-
delay_time = int((date - datetime.now(tz)).seconds / 60)
133+
delay_time = _minutes_until(date, datetime.now(tz))
134134

135135
if "end" in call.data and "duration" in call.data:
136136
date = datetime.strptime(call.data.get("end"), "%Y-%m-%d %H:%M:%S").replace(tzinfo=tz)
137137
duration = call.data.get("duration")
138-
delay_time = int((date - datetime.now(tz)).seconds / 60 - duration)
138+
delay_time = max(0, _minutes_until(date, datetime.now(tz)) - duration)
139139

140140
parameters = {
141141
"delayTime": delay_time,
@@ -161,12 +161,12 @@ async def handle_dishwasher_start(call):
161161

162162
if "start" in call.data:
163163
date = datetime.strptime(call.data.get("start"), "%Y-%m-%d %H:%M:%S").replace(tzinfo=tz)
164-
delay_time = int((date - datetime.now(tz)).seconds / 60)
164+
delay_time = _minutes_until(date, datetime.now(tz))
165165

166166
if "end" in call.data and "duration" in call.data:
167167
date = datetime.strptime(call.data.get("end"), "%Y-%m-%d %H:%M:%S").replace(tzinfo=tz)
168168
duration = call.data.get("duration")
169-
delay_time = int((date - datetime.now(tz)).seconds / 60 - duration)
169+
delay_time = max(0, _minutes_until(date, datetime.now(tz)) - duration)
170170

171171
parameters = {
172172
"delayTime": delay_time,
@@ -190,7 +190,7 @@ async def handle_washingmachine_start(call):
190190
tz = gettz(hass.config.time_zone)
191191
if "end" in call.data:
192192
date = datetime.strptime(call.data.get("end"), "%Y-%m-%d %H:%M:%S").replace(tzinfo=tz)
193-
delay_time = int((date - datetime.now(tz)).seconds / 60)
193+
delay_time = _minutes_until(date, datetime.now(tz))
194194

195195
parameters = {
196196
"haier_MainWashSpeed": "50",
@@ -447,29 +447,57 @@ async def async_get_setting(call: ServiceCall):
447447

448448

449449

450-
hass.services.async_register(DOMAIN, "turn_on_washingmachine", handle_washingmachine_start)
451-
hass.services.async_register(DOMAIN, "turn_on_oven", handle_oven_start)
452-
hass.services.async_register(DOMAIN, "turn_on_dishwasher", handle_dishwasher_start)
453-
hass.services.async_register(DOMAIN, "turn_on_purifier", handle_purifier_start)
454-
hass.services.async_register(DOMAIN, "set_auto_mode_purifier", handle_purifier_automode)
455-
hass.services.async_register(DOMAIN, "set_sleep_mode_purifier", handle_purifier_sleepmode)
456-
hass.services.async_register(DOMAIN, "set_max_mode_purifier", handle_purifier_maxmode)
457-
458-
hass.services.async_register(DOMAIN, "set_mode", handle_set_mode)
459-
hass.services.async_register(DOMAIN, "turn_off", handle_turn_off)
460-
hass.services.async_register(DOMAIN, "turn_light_on", handle_light_on)
461-
hass.services.async_register(DOMAIN, "turn_light_off", handle_light_off)
462-
hass.services.async_register(DOMAIN, "send_custom_request", handle_custom_request)
463-
hass.services.async_register(DOMAIN, "climate_turn_health_mode_on", handle_health_mode_on)
464-
hass.services.async_register(DOMAIN, "climate_turn_health_mode_off", handle_health_mode_off)
465-
466-
hass.services.async_register(DOMAIN, "start_program", handle_start_program)
467-
hass.services.async_register(DOMAIN, "update_settings", handle_update_settings)
468-
hass.services.async_register(
469-
domain=DOMAIN,
470-
service="get_setting",
471-
service_func=async_get_setting,
472-
schema=None
473-
)
450+
services = {
451+
"turn_on_washingmachine": handle_washingmachine_start,
452+
"turn_on_oven": handle_oven_start,
453+
"turn_on_dishwasher": handle_dishwasher_start,
454+
"turn_on_purifier": handle_purifier_start,
455+
"set_auto_mode_purifier": handle_purifier_automode,
456+
"set_sleep_mode_purifier": handle_purifier_sleepmode,
457+
"set_max_mode_purifier": handle_purifier_maxmode,
458+
"set_mode": handle_set_mode,
459+
"turn_off": handle_turn_off,
460+
"turn_light_on": handle_light_on,
461+
"turn_light_off": handle_light_off,
462+
"send_custom_request": handle_custom_request,
463+
"climate_turn_health_mode_on": handle_health_mode_on,
464+
"climate_turn_health_mode_off": handle_health_mode_off,
465+
"start_program": handle_start_program,
466+
"update_settings": handle_update_settings,
467+
"get_setting": async_get_setting,
468+
}
469+
470+
registered_services = hass.data[DOMAIN][SERVICE_REGISTRY]
471+
for service_name, handler in services.items():
472+
if service_name in registered_services:
473+
continue
474+
hass.services.async_register(
475+
domain=DOMAIN,
476+
service=service_name,
477+
service_func=handler,
478+
schema=None,
479+
)
480+
registered_services.add(service_name)
481+
482+
return True
483+
484+
485+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
486+
"""Unload a config entry."""
487+
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
488+
if not unload_ok:
489+
return False
490+
491+
hon = hass.data[DOMAIN].pop(entry.unique_id, None)
492+
if hon is not None:
493+
await hon.async_close()
494+
495+
remaining_entries = [
496+
key for key in hass.data.get(DOMAIN, {}) if key != SERVICE_REGISTRY
497+
]
498+
if not remaining_entries:
499+
for service_name in hass.data[DOMAIN].get(SERVICE_REGISTRY, set()):
500+
hass.services.async_remove(DOMAIN, service_name)
501+
hass.data.pop(DOMAIN, None)
474502

475503
return True

custom_components/hon/command.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,24 @@ def setting_keys(self):
7171
@property
7272
def settings(self):
7373
"""Parameters with typology enum and range"""
74-
return {s: self._parameters.get(s) for s in self.setting_keys if self._parameters.get(s) is not None}
74+
if not self._multi:
75+
return {
76+
key: parameter
77+
for key, parameter in self._parameters.items()
78+
if not isinstance(parameter, HonParameterFixed)
79+
}
80+
81+
result = {}
82+
for key in self.setting_keys:
83+
parameter = self._parameters.get(key)
84+
if parameter is None:
85+
for command in self._multi.values():
86+
parameter = command.parameters.get(key)
87+
if parameter is not None:
88+
break
89+
if parameter is not None:
90+
result[key] = parameter
91+
return result
7592

7693
def dump(self):
7794
text = ""
@@ -83,4 +100,4 @@ def dump(self):
83100
"""
84101
example += f"\'{key}\':{parameter.default},"
85102
example = example[:-1] + "}"
86-
return text, example
103+
return text, example

custom_components/hon/const.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@
2727
"sensor",
2828
"binary_sensor",
2929
"button",
30-
"switch"
31-
]
32-
33-
'''
30+
"switch",
3431
"select",
35-
"number" '''
32+
"number",
33+
]
3634

3735
AUTH_API = "https://account2.hon-smarthome.com/SmartHome"
3836
API_URL = "https://api-iot.he.services"

custom_components/hon/device.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,31 +195,46 @@ def settings_command(self, parameters = {}):
195195
if( "settings" not in self._commands ):
196196
raise ValueError("No command to update settings of the device")
197197
command = self._commands.get("settings")
198-
self.update_command(command, self.attributes["parameters"])
198+
self.update_command(command, self.attributes.get("parameters", {}))
199199
self.update_command(command, parameters)
200200

201201
# Update for next command (in case no refresh happens yet)
202202
for key in command.parameters.keys():
203-
self.attributes["parameters"][key] = command.parameters.get(key).value
203+
self.attributes.setdefault("parameters", {})[key] = command.parameters.get(key).value
204204

205205
return command
206206

207207
def start_command(self, program = None, parameters = {}):
208208
if( "startProgram" not in self._commands ):
209209
raise ValueError("No command to start the device")
210210
command = self._commands.get("startProgram")
211-
command.set_program(program)
211+
if program is not None:
212+
command.set_program(program)
212213
# Return the new default command
213214
command = self._commands.get("startProgram")
214-
self.update_command(command, self.attributes["parameters"])
215+
self.update_command(command, self.attributes.get("parameters", {}))
215216
self.update_command(command, parameters)
216217

217218
# Update for next command (in case no refresh happens yet)
218219
for key in command.parameters.keys():
219-
self.attributes["parameters"][key] = command.parameters.get(key).value
220+
self.attributes.setdefault("parameters", {})[key] = command.parameters.get(key).value
220221

221222
return command
222223

224+
def get_setting(self, setting_key):
225+
command_name, parameter_key = setting_key.split(".", 1)
226+
command = self._commands.get(command_name)
227+
if command is None:
228+
return None
229+
return command.settings.get(parameter_key)
230+
231+
def has_current_setting(self, setting_key):
232+
command_name, parameter_key = setting_key.split(".", 1)
233+
command = self._commands.get(command_name)
234+
if command is None:
235+
return False
236+
return parameter_key in command.parameters
237+
223238
def stop_command(self, parameters = {}):
224239
if( "stopProgram" in self._commands ):
225240
command = self._commands.get("stopProgram")

0 commit comments

Comments
 (0)