Add support for ZhongHong HVAC Controllers#14552
Add support for ZhongHong HVAC Controllers#14552balloob merged 24 commits intohome-assistant:devfrom
Conversation
fabaff
left a comment
There was a problem hiding this comment.
Just some quick comments to get started.
There was a problem hiding this comment.
Name it gateway_address. Will be easier to spot for humans.
There was a problem hiding this comment.
I guess that there will be a traceback if the controller is not available. You need to decide what to do. Make the setup of the platform fails or raise PlatformNotReady.
There was a problem hiding this comment.
Looks like that this should be attached to EVENT_HOMEASSISTANT_START
There was a problem hiding this comment.
Why I have to put this after EVENT_HOMEASSISTANT_START? Just curious
There was a problem hiding this comment.
I found some times this error will occur. What's wrong with it?
Exception in thread Thread-4:
Traceback (most recent call last): File "/usr/local/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run() File "/usr/local/lib/python3.6/threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hub.py", line 131, in _listen_to_msg func(payload) File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 46, in _status_update self._call_status_update()
File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 32, in _call_status_update
func(self)
File "/config/custom_components/climate/zhong_hong.py", line 74, in _after_update self.schedule_update_ha_state()
File "/usr/src/app/homeassistant/helpers/entity.py", line 294, in schedule_update_ha_state
self.hass.add_job(self.async_update_ha_state(force_refresh))
AttributeError: 'NoneType' object has no attribute 'add_job'There was a problem hiding this comment.
Why do we wait with adding the devices until home assistant start? @MartinHjelmare
Emmm, I don't know the reason. But I get the suggestion here.
There was a problem hiding this comment.
This should be in imperative mood. Perhaps something like: "Set up the ZhongHong climate devices."
There was a problem hiding this comment.
Is there any further thought on using string format or % format?
|
BTW, what's the correct name for the mode of just blow the wind? Not making any cold or heat |
|
Fan only is probably what you're looking for, there's a constant defined here for it |
|
thanks, FAN_ONLY name done. |
crhan
left a comment
There was a problem hiding this comment.
still cannot fix this problem:
File "/usr/local/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/usr/local/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hub.py", line 148, in _listen_to_msg
func(payload)
File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 50, in _status_update
self._call_status_update()
File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 32, in _call_status_update
func(self)
File "/config/custom_components/climate/zhong_hong.py", line 74, in _after_update
self.schedule_update_ha_state()
File "/usr/src/app/homeassistant/helpers/entity.py", line 294, in schedule_update_ha_state
self.hass.add_job(self.async_update_ha_state(force_refresh))
AttributeError: 'NoneType' object has no attribute 'add_job'There was a problem hiding this comment.
Is there any further thought on using string format or % format?
There was a problem hiding this comment.
I found some times this error will occur. What's wrong with it?
Exception in thread Thread-4:
Traceback (most recent call last): File "/usr/local/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run() File "/usr/local/lib/python3.6/threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hub.py", line 131, in _listen_to_msg func(payload) File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 46, in _status_update self._call_status_update()
File "/config/deps/lib/python3.6/site-packages/zhong_hong_hvac/hvac.py", line 32, in _call_status_update
func(self)
File "/config/custom_components/climate/zhong_hong.py", line 74, in _after_update self.schedule_update_ha_state()
File "/usr/src/app/homeassistant/helpers/entity.py", line 294, in schedule_update_ha_state
self.hass.add_job(self.async_update_ha_state(force_refresh))
AttributeError: 'NoneType' object has no attribute 'add_job'| ] | ||
| except Exception as exc: | ||
| _LOGGER.error("ZhongHong controller is not ready", exc_info=exc) | ||
| raise PlatformNotReady |
There was a problem hiding this comment.
This will not matter becaus you're no longer running this inside setup_platform. You need to test the credentials inside the setup_platform method.
There was a problem hiding this comment.
Is this the right way to solve the problem?
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the ZhongHong HVAC platform."""
from zhong_hong_hvac.hub import ZhongHongGateway
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
gw_addr = config.get(CONF_GATEWAY_ADDRRESS)
hub = ZhongHongGateway(host, port, gw_addr)
try:
devices = [
ZhongHongClimate(hub, addr_out, addr_in)
for (addr_out, addr_in) in hub.discovery_ac()
]
except Exception as exc:
_LOGGER.error("ZhongHong controller is not ready", exc_info=exc)
raise PlatformNotReady
def startup(event):
"""Add devices to HA and start hub socket."""
add_devices(devices)
hub.start_listen()
hub.query_all_status()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, startup)| @property | ||
| def max_temp(self): | ||
| """Return the maximum temperature.""" | ||
| return convert_temperature(self._device.max_temp, TEMP_CELSIUS, |
There was a problem hiding this comment.
You don't need to convert anything. Your device is already in CELSIUS and you are converting to CELSIUS. Just return self._device_max_temp
|
@MartinHjelmare Have a look~ |
MartinHjelmare
left a comment
There was a problem hiding this comment.
Looks good. A small comment on a name and a question.
| CONF_GATEWAY_ADDRRESS = 'gateway_address' | ||
|
|
||
| REQUIREMENTS = ['zhong_hong_hvac==1.0.9'] | ||
| SIGNAL_DEVICE_SETTED_UP = 'zhong_hong_device_setted_up' |
There was a problem hiding this comment.
SIGNAL_DEVICE_ADDED = 'zhong_hong_device_added'| return | ||
|
|
||
| _LOGGER.debug("zhong_hong hub start listen event") | ||
| hub.start_listen() |
There was a problem hiding this comment.
Are these calls to the hub async safe? No I/O or blocking?
There was a problem hiding this comment.
Yes, It just start a thread to process the tcp stream. https://github.com/crhan/ZhongHongHVAC/blob/master/zhong_hong_hvac/hub.py#L159-L174
BTW, how can I change the TCP socket process code to async friendly?
There was a problem hiding this comment.
There's a potential socket interaction and sleep call here. Let's do this in a worker thread via use of async_add_job.
There was a problem hiding this comment.
If you want to build an async library, you would use either of the two alternatives for socket communication in asyncio standard library, transports and protocols or streams.
SIGNAL_DEVICE_SETTED_UP -> SIGNAL_DEVICE_ADDED
MartinHjelmare
left a comment
There was a problem hiding this comment.
I think you should change back to setup_platform and the sync api but keep startup a coroutine. There's less context switching needed that way since the library is not async.
| for (addr_out, addr_in) in hub.discovery_ac() | ||
| ] | ||
|
|
||
| except Exception as exc: |
There was a problem hiding this comment.
Reading the library, it looks like you already catch Exception in the library when dealing with the socket. When could an exception bubble up to here?
There was a problem hiding this comment.
Maybe I will let the exception through out in the future. Must I remove the stupid try...except now?
There was a problem hiding this comment.
You should never catch a bare exception. You should always only catch the exceptions that you expect.
There was a problem hiding this comment.
So if you're using a Socket, see what the base class is and catch that.
There was a problem hiding this comment.
This has to be updated to be the exception that is raised. You are not allowed to catch a bare exception
There was a problem hiding this comment.
Sorry I missed this comment. It is really not necessary to catch this exception. Because I catch the exception in the device library.
| return | ||
|
|
||
| _LOGGER.debug("zhong_hong hub start listen event") | ||
| hub.start_listen() |
There was a problem hiding this comment.
There's a potential socket interaction and sleep call here. Let's do this in a worker thread via use of async_add_job.
|
|
||
| _LOGGER.debug("zhong_hong hub start listen event") | ||
| hub.start_listen() | ||
| hub.query_all_status() |
There was a problem hiding this comment.
This is sending messages to the device via socket and must be done in a worker thread (not inside event loop).
| try: | ||
| devices = [ | ||
| ZhongHongClimate(hub, addr_out, addr_in) | ||
| for (addr_out, addr_in) in hub.discovery_ac() |
There was a problem hiding this comment.
This is doing I/O via socket and is not async safe. I would change back to the sync api and use setup_platform. You can still have startup be a coroutine.
There was a problem hiding this comment.
Can I still use async_add_job here to avoid the problem?
There was a problem hiding this comment.
Yes, but I would instead change to setup_platform as mentioned.
|
Is there any code I need to change? The |
|
|
||
| operation_mode = kwargs.get(ATTR_OPERATION_MODE) | ||
| if operation_mode is not None: | ||
| self._device.set_operation_mode(operation_mode) |
There was a problem hiding this comment.
Here you don't cast the operation mode to upper case like below. Is that not needed?
There was a problem hiding this comment.
Nope, you are right. I think I should use self.set_operation_mode directly, but not use self._device.set_operation_mode.
| ] | ||
|
|
||
| except Exception as exc: | ||
| _LOGGER.error("ZhongHong controller is not ready", exc_info=exc) |
There was a problem hiding this comment.
Don't print the full stack trace of the exception, that will confuse users. Stringify the exception instead
| self.is_initialized = True | ||
| async_dispatcher_send(self.hass, SIGNAL_DEVICE_ADDED) | ||
|
|
||
| def update(self): |
There was a problem hiding this comment.
I suppose home assistant core logic will invoke this method in some situation?
There was a problem hiding this comment.
Only if should_poll is true or if you pass True as second argument to add_devices or as first argument to schedule_update_ha_state.
| ] | ||
|
|
||
| except Exception as exc: | ||
| _LOGGER.error("ZhongHong controller is not ready: %s", str(exc)) |
There was a problem hiding this comment.
Exceptions already have a magic string method. We don't need to copy to string.
| for (addr_out, addr_in) in hub.discovery_ac() | ||
| ] | ||
|
|
||
| except Exception as exc: |
There was a problem hiding this comment.
This has to be updated to be the exception that is raised. You are not allowed to catch a bare exception
| SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) | ||
| from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, | ||
| EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS) | ||
| from homeassistant.exceptions import PlatformNotReady |
There was a problem hiding this comment.
'homeassistant.exceptions.PlatformNotReady' imported but unused
* first blood for ZhongHong HVAC Controller * add requirements * requirements_all.txt updated * add zhong_hong.py to coveragerc * add comments * unique_id add platform name * zhong_hong_hvac version bump to 1.0.1 * improve some coding style to match the project standard * zhong_hong_hvac version bump to 1.0.4 * zhong_hong_hvac version require 1.0.7 * update requirements by script/gen_requirements_all.py * zhong_hong_hvac version bump to 1.0.8 * fix startup problem * remove unused import * zhong_hong_hvac version bump to 1.0.9 - operation_mode: cold -> cool * start hub listen event when all climate entities is ready * use dispatcher to setup hub * var name change SIGNAL_DEVICE_SETTED_UP -> SIGNAL_DEVICE_ADDED * async problem fix * bugfix: set_operation_mode forget to use upper case * stringify the exception instead of print full stack of traceback * avoid to call str(exception) explicity * remove unnecessary try...except clause * remove unused import
Description:
This is a platform for using a HVAC Controller product from Zhong hong tech which sells in China. It can control a bunch of AC brand like Daikin, Hitachi, Meidi, Gree, Haier, Toshiba and Mitsubishi. You can find the product selling page on taobao.com.
Related issue (if applicable): fixes #
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5402
Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTSvariable (example).requirements_all.txtby runningscript/gen_requirements_all.py..coveragerc.If the code does not interact with devices: