Remote Home-Assistant: Linking multiple instances via Websocket API#13876
Remote Home-Assistant: Linking multiple instances via Websocket API#13876lukas-hetzenecker wants to merge 7 commits intohome-assistant:devfrom
Conversation
which connects to a remote Home-Assistant instance via the Websocket API
According to the changelog, they should be compatible ( https://github.com/aaugustin/websockets/blob/master/docs/changelog.rst ) Compression is now enabled by default
|
This is huge for me! Thank you!! |
| from homeassistant.config import DATA_CUSTOMIZE | ||
| import homeassistant.helpers.config_validation as cv | ||
|
|
||
| REQUIREMENTS = ['websockets==4.0.1'] |
There was a problem hiding this comment.
We should use aiohttp to make the websocket connection as it's already part of the core.
| """Publish remove event on local instance.""" | ||
| if message['type'] == 'result': | ||
| pass | ||
| elif message['type'] == 'event': |
There was a problem hiding this comment.
Use guard clauses to make the code more readable.
elif message['type'] != 'event':
return
if message…| SLAVES_SCHEMA = vol.Schema({ | ||
| vol.Required(CONF_HOST): cv.string, | ||
| vol.Optional(CONF_PORT, default=8123): cv.port, | ||
| vol.Optional(CONF_SECURE, default=False): cv.boolean, |
There was a problem hiding this comment.
It would make more sense to have the user just define a url like https://blabla.duckdns.org:1220
There was a problem hiding this comment.
wss://blabla.duckdns.org:1220 .. hopefully users won't confuse ws/wss with http/https?
| homeassistant/components/raspihats.py | ||
| homeassistant/components/*/raspihats.py | ||
|
|
||
| homeassistant/components/remote_homeassistant.py |
There was a problem hiding this comment.
Nope.
This needs like a 1000 tests added.
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CONF_SLAVES = 'slaves' |
There was a problem hiding this comment.
Besides slaves being a politically loaded word, it's also the wrong description. We are not delegating work to them, we're integrating them into our instance. I think that we should just call the entry instances
There was a problem hiding this comment.
totally agree, fixed
| except websockets.exceptions.ConnectionClosed: | ||
| _LOGGER.error('remote websocket connection closed') | ||
| await self._disconnected() | ||
| return |
|
Really hype to get this into hass! |
|
@cmsimike maybe you can help him with writing some tests? |
|
Wouldn't mind that at all :) - but please tell me before you do, would otherwise start soonish with them |
|
@balloob thanks for letting me know that's all that's required! i have added it to my todo list. not sure when i can get to it. @lukas-hetzenecker definitely, but as above, might be a bit before i can get to it. |
|
FYI, just started with the test cases.. (preparation for my PyDays talk can wait.. ;) ) |
…onents" This reverts commit 6c8e23d1b2001e027fd03593d5010f8d7b479610.
|
|
||
| #self.hass_main.block_till_done() | ||
| pass | ||
|
|
| #setup_component(self.hass_main, remote_homeassistant.DOMAIN, { | ||
| # remote_homeassistant.DOMAIN: config}) | ||
|
|
||
| #self.hass_main.block_till_done() |
There was a problem hiding this comment.
block comment should start with '# '
| # } | ||
| # ] | ||
| #} | ||
| #setup_component(self.hass_main, remote_homeassistant.DOMAIN, { |
There was a problem hiding this comment.
block comment should start with '# '
| # #remote_homeassistant.CONF_PORT: instance01_port | ||
| # } | ||
| # ] | ||
| #} |
There was a problem hiding this comment.
block comment should start with '# '
| #self.hass_instance01.block_till_done() | ||
|
|
||
|
|
||
| #config = { |
There was a problem hiding this comment.
too many blank lines (2)
block comment should start with '# '
| # } | ||
| #) | ||
|
|
||
| #setup_component(self.hass_instance01, 'websocket_api') |
There was a problem hiding this comment.
block comment should start with '# '
| # http.CONF_SERVER_PORT: instance01_port, | ||
| # } | ||
| # } | ||
| #) |
There was a problem hiding this comment.
block comment should start with '# '
|
|
||
| #self.hass_main.block_till_done() | ||
|
|
||
| #setup_component( |
There was a problem hiding this comment.
block comment should start with '# '
|
|
||
| self.hass_main = get_test_home_assistant() | ||
|
|
||
| #self.hass_main.block_till_done() |
There was a problem hiding this comment.
block comment should start with '# '
| def setup_method(self): | ||
| """Setup things to be run when tests are started.""" | ||
| self.hass_instance01 = get_test_home_assistant() | ||
| #self.hass_instance02 = get_test_home_assistant() |
There was a problem hiding this comment.
block comment should start with '# '
|
|
||
| CONFIG_SCHEMA = vol.Schema({ | ||
| DOMAIN: vol.Schema({ | ||
| vol.Required(CONF_INSTANCES): [INSTANCES_SCHEMA], |
There was a problem hiding this comment.
vol.Required(CONF_INSTANCES): vol.All(cv.ensure_list, [INSTANCES_SCHEMA]),| """Set up the remote_homeassistant component.""" | ||
| conf = config.get(DOMAIN) | ||
|
|
||
| for instance in conf.get(CONF_INSTANCES): |
There was a problem hiding this comment.
Don't use dict.get(key) for required config items, use dict[key].
| vol.Optional(CONF_SECURE, default=False): cv.boolean, | ||
| vol.Optional(CONF_API_PASSWORD): cv.string, | ||
| vol.Optional(CONF_SUBSCRIBE_EVENTS, | ||
| default=DEFAULT_SUBSCRIBED_EVENTS): cv.ensure_list, |
There was a problem hiding this comment.
vol.All(cv.ensure_list, [cv.string])| def __init__(self, hass, conf): | ||
| """Initialize the connection.""" | ||
| self._hass = hass | ||
| self._host = conf.get(CONF_HOST) |
There was a problem hiding this comment.
self._host = conf[CONF_HOST]|
|
||
| async def async_connect(self): | ||
| """Connect to remote home-assistant websocket...""" | ||
| url = '%s://%s:%s/api/websocket' % ( |
There was a problem hiding this comment.
Use new style string formatting: https://pyformat.info/.
Maybe break out url string into constant?
| if self._entity_prefix: | ||
| domain, object_id = split_entity_id(entity_id) | ||
| object_id = self._entity_prefix + object_id | ||
| entity_id = domain + '.' + object_id |
There was a problem hiding this comment.
Use new style string formatting.
| asyncio.ensure_future(self._recv()) | ||
|
|
||
| def _next_id(self): | ||
| _id = self.__id |
| return _id | ||
|
|
||
| async def _call(self, callback, message_type, **extra_args): | ||
| _id = self._next_id() |
| def _remove_prefix(entity_id): | ||
| domain, object_id = split_entity_id(entity_id) | ||
| object_id = object_id.replace(self._entity_prefix, '', 1) | ||
| return domain + '.' + object_id |
There was a problem hiding this comment.
Use new style string formatting.
| instance01_port = 8124 | ||
| instance02_port = 8125 | ||
|
|
||
| class TestRemoteHomeassistant(object): |
There was a problem hiding this comment.
I suggest you go with pytest tests that are not class based. That will be cleaner and you probably want to use the async home assistant api, so then write tests that are coroutines.
There was a problem hiding this comment.
line too long (89 > 79 characters)
There was a problem hiding this comment.
'homeassistant.const.CONF_URL' imported but unused
There was a problem hiding this comment.
line too long (89 > 79 characters)
There was a problem hiding this comment.
'homeassistant.const.CONF_URL' imported but unused
f068a5a to
0ca296e
Compare
|
So I've just added a stub test to get 2 hass instances work together to help you move along. Because you're going around our async infrastructure, I could not get it working without an However, and this brings me to the next part: the code is messy, too many features for MVP, too configurable and the data flows in the wrong direction. Functionality like this should preferably proposed in our architecture repo to allow people to discuss such major features. Here is an MVP that I would accept. Client is the one that has "remote_homeassistant" configured. Master is another HASS instance with the websocket API running.
The reason for keeping the config decentralized, not centralized is that it will only require the master instance to have a fixed IP. All other clients can just spin up, publish states while they are online and drop off, and it will be cleaned up. The other nice thing about this approach is that it can be broken up in multiple PRs:
I am going to close this PR now as this functionality should be added in 2 PRs. If you don't agree with my proposal, feel free to open an issue in the architecture repo so we can discuss this. |
|
No, it was so close... |
|
@balloob made some good points though ;) |
|
@lukas-hetzenecker was super simple to setup as a custom component, thanks for doing this. Have a large house spread across a couple buildings and this solves my zwave distance issues. |
|
Will the be any work done to this great plugin? I had some issues when it comes to stability. |
Description:
This platform links multiple home-assistant instances together, replacing the deprecated "Multiple Instances".
The master instance connects to the Websocket APIs of the slaves, the connection options are specified via the
host,port, andsecureconfiguration parameters. An API password can also be set viaapi_password.After the connection is completed, the remote states get populated into the master instance.
The entity ids can optionally be prefixed via the
entity_prefixparameter.The component keeps track which objects originate from which instance. Whenever a service is called on an object, the call gets forwarded to the particular slave instance.
When the connection to the remote instance is lost, all previously published states are removed again from the local state registry.
A possible use case for this is to be able to use different z-wave networks, on different z-wave sticks (with the second one possible running on another computer in a different location). This is a highly requested feature, as can be seen in the following forum postings:
Currently this use case could be solved by using mqtt_eventstream, but this comes with its own set of problems, e.g.: https://community.home-assistant.io/t/master-slave-with-mqtt-eventstream-retain-all-published-devices/17012
Another option would be to use, mqtt_statestream, but this makes dealing with service calls a real hassle (see https://community.home-assistant.io/t/best-way-to-connect-zwave-switches-on-second-ha-instance-to-main-ha-instance/40577 )
My alternative proposes a way, which
customizeblock.. and therefore makes linking multiple instances really simple.
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5170
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: