Skip to content

Reconnect before mochad switch send command#11296

Merged
MartinHjelmare merged 10 commits into
home-assistant:devfrom
aosadchyy:dev
Jan 8, 2018
Merged

Reconnect before mochad switch send command#11296
MartinHjelmare merged 10 commits into
home-assistant:devfrom
aosadchyy:dev

Conversation

@aosadchyy
Copy link
Copy Markdown
Contributor

@aosadchyy aosadchyy commented Dec 24, 2017

Description:

Connection to mochad occasionally stalls on RPi and CM19A. Re-connect the underlying socket in controller.PyMochad when X10 swtich is set to on/off.
Current design of mochad in homeassistant is to keep 24/7 connection to mochad server open. There is no watchdog nor automatic reconnect routine.

There is a known issue for mochad to get locked up occasionally. e.g. as seen often on RPi.
https://sourceforge.net/p/mochad/discussion/1320003/thread/1c193f01/

When this happens, mochad can be recovered by restarting it. However, mochad.py components in home-assistant once connected on HA startup, won't ever reconnect.

This improvement allows to reconnect underlying socket in controller. PyMochad when X10 commands are sent to mochad server. It's tested to work fine on my HA.

Related issue (if applicable): fixes #
https://sourceforge.net/p/mochad/discussion/1320003/thread/1c193f01/

self._state = False
# Recycle socket on new command to recover mochad connection
_LOGGER.debug("Reconnect %s:%s", self._controller.server,
self._controller.port)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line over-indented for visual indent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fixed in commit 9fbade8

self._state = True
# Recycle socket on new command to recover mochad connection
_LOGGER.debug("Reconnect %s:%s", self._controller.server,
self._controller.port)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line over-indented for visual indent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fixed in commit 9fbade8

@MartinHjelmare MartinHjelmare changed the title Connection to mochad occasionally stalls on RPi and CM19A. Reconnect on... Reconnect before mochad switch send command Dec 25, 2017
Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the indentation issues outlined by hound and see below.

self.device.send_cmd('on')
self._controller.read_data()
try:
self._state = True
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this at the end so that the state is not changed if there's an error in the communication with the device.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fixed in commit 9fbade8

try:
self._state = True
# Recycle socket on new command to recover mochad connection
_LOGGER.debug("Reconnect %s:%s", self._controller.server,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this up outside the try... except.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fixed in commit 9fbade8

# No read data on CM19A which is rf only
if (self._comm_type == 'pl'):
self._controller.read_data()
except Exception as e:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't except all exceptions. Import MochadException from pymochad.exceptions and except that exception and OSError to catch socket errors.

Also change variable name to eg exc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fixed in commit 9fbade8

from homeassistant.const import (CONF_NAME, CONF_DEVICES,
CONF_PLATFORM, CONF_ADDRESS)
from homeassistant.helpers import config_validation as cv
from pymochad.exceptions import MochadException
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing whitespace

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this import to inside each method where the exception is used. Imports from libraries that are specified in the requirements list need to be done inside each method since the requirements will be installed and made available during setup, ie not before starting home assistant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it as requested. See commit b1f5344

from homeassistant.const import (CONF_NAME, CONF_DEVICES,
CONF_PLATFORM, CONF_ADDRESS)
from homeassistant.helpers import config_validation as cv
from pymochad.exceptions import MochadException
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this import to inside each method where the exception is used. Imports from libraries that are specified in the requirements list need to be done inside each method since the requirements will be installed and made available during setup, ie not before starting home assistant.

self._controller.read_data()
try:
# Recycle socket on new command to recover mochad connection
_LOGGER.debug("Reconnect %s:%s", self._controller.server,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still be move up outside try... except. Put as little code as possible within try... except, ie only the code that's relevant to catch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it as requested. See commit b1f5344

def turn_off(self, **kwargs):
"""Turn the switch off."""
self._state = False
from pymochad.exceptions import MochadException
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing whitespace

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

def turn_on(self, **kwargs):
"""Turn the switch on."""
self._state = True
from pymochad.exceptions import MochadException
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing whitespace

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@MartinHjelmare
Copy link
Copy Markdown
Member

There are some linting issues and you need to mock the dependency pymochad.exceptions in the tests for the switch.

@aosadchyy
Copy link
Copy Markdown
Contributor Author

Regarding mocking the exceptions for switches. do you have any example? I looked in home-assistant\tests\components\switch\ and didn't see any.

@MartinHjelmare
Copy link
Copy Markdown
Member

I think you can extend the existing pymochad mock fixture and patch pymochad.exceptions in sys.modules.

Comment thread tests/components/switch/test_mochad.py Outdated
from homeassistant.components.switch import mochad

from tests.common import get_test_home_assistant
from tests.common import (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'tests.common.MockDependency' imported but unused

Comment thread tests/components/switch/test_mochad.py Outdated
from homeassistant.components.switch import mochad

from tests.common import get_test_home_assistant
from tests.common import (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'tests.common.MockDependency' imported but unused

Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clarify if it's possible to poll the mochad device for state, and why that isn't done currently for each service call. It seems some devices don't support this, but some do. But I think the current implementation isn't polling for state when it should do that.

self.device.send_cmd('on')
# No read data on CM19A which is rf only
if self._comm_type == 'pl':
self._controller.read_data()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this done without handling the return value? Shouldn't the return value be used to update state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original code has read_data() after each send_cmd(). This works for pl, but not for rf.
pl = powerline devices (like appliance switches). rf = wireless devices (mostly sensors). When sending data to pl devices = they respond back with the state. rf devices do not do that. CM19A unlike CM15A, handles all commands as rf. It relies on a separate translator device - TM751 for rf->pl translation. And it's only one way. There is no read back. The original code gets in stuck on read_data() for me until any data shows up from sensors in the buffer. Making it conditional fixes the problem without affecting CM15A behaviours.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that RF devices don't support this call, but I don't understand why the call is done at all, when the return value is not handled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is done for pl devices in order to clear the receive buffer. When pl device is set to On/Off state, it responds back with it's new value. This read command reads it and ignores it. The state is already known on hass side.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the empty read buffer makes sense, but since the device responds with the new state, that should be used in home assistant. Home assistant doesn't know the new state for sure. Sending a command to the device over some kind of network transport doesn't guarantee that the device receives the command or changes state. We can only be sure after the device has replied to home assistant with its new state. So whenever possible, a device should feedback its state to home assistant when that changes.

except (MochadException, OSError) as exc:
_LOGGER.error("Error with mochad communication: %s", exc)

def _get_device_status(self):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems _get_device_status can be used to update state, but why isn't that called in an update method? Then the new state would automatically be polled after each service call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_device_status is shortcut to the parent SwitchDevice.get_status(). It doesn't make a trip to Mochad and the device. Rather it's a status of the device in hass. No need to change anything here. Not my code. But I did a little research. Mochad supports status_request for pl commands, but not for rf. https://bfocht.github.io/mochad/mochad_reference.html I think the reasoning behind this original code is as I described in my above comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does ask the mochad device for status.

Copy link
Copy Markdown
Contributor Author

@aosadchyy aosadchyy Jan 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're correct. I might invest more time into all this x10 stuff at some later point. For now, whatever changes I made here, work for my home with ca 20 x10 devices of different kinds. I can also do bug fix if anyone reports an issue.

Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is ok and for RF devices that can't feedback state my comments don't matter. But it would be good if someone knowledgeable about this platform could take a look. I don't think the current implementation is doings things correct.

@MartinHjelmare MartinHjelmare merged commit 2ba9d82 into home-assistant:dev Jan 8, 2018
@balloob balloob mentioned this pull request Jan 11, 2018
@home-assistant home-assistant locked and limited conversation to collaborators May 29, 2018
@ghost ghost removed the platform: switch.mochad label Mar 21, 2019
@ghost ghost added the integration: mochad label Mar 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants