hass-core/homeassistant/components/mochad.py
Matthew Treinish 1f1115f631 Serialize mochad requests (#11029)
All mochad devices are sharing a single socket interface. When multiple
threads are issuing requests to the mochad daemon at the same time the
write read cycle might get crossed between the threads. This is normally
not an issue for 1-way X10 devices because as long as the request issued
successfully and data is read over the socket then we know as much as
mochad will tell us (since there is no ACK from the request for most
X10 devices). However, where it does matter is on the device __init__()
because we're relying on the mochad daemon's internal state to take an
educated guess at the device's state to intialize things with. When
there are multiple devices being initialized at the same time the wires
can get crossed between and the wrong device state may be read.

To address this potential issue this commit adds locking using a
semaphore around all pairs of send_cmd() and read_data() (which is what
pymochad.device.Device.get_status() does internally) calls to the mochad
controller to ensure we're only ever dealing with a single request at a
time.

Fixes mtreinish/pymochad#4
2017-12-08 09:18:52 -08:00

89 lines
2.2 KiB
Python

"""
Support for CM15A/CM19A X10 Controller using mochad daemon.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mochad/
"""
import logging
import threading
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.const import (CONF_HOST, CONF_PORT)
REQUIREMENTS = ['pymochad==0.1.1']
_LOGGER = logging.getLogger(__name__)
CONTROLLER = None
CONF_COMM_TYPE = 'comm_type'
DOMAIN = 'mochad'
REQ_LOCK = threading.Lock()
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default='localhost'): cv.string,
vol.Optional(CONF_PORT, default=1099): cv.port,
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the mochad component."""
conf = config[DOMAIN]
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
from pymochad import exceptions
global CONTROLLER
try:
CONTROLLER = MochadCtrl(host, port)
except exceptions.ConfigurationError:
_LOGGER.exception()
return False
def stop_mochad(event):
"""Stop the Mochad service."""
CONTROLLER.disconnect()
def start_mochad(event):
"""Start the Mochad service."""
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_mochad)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mochad)
return True
class MochadCtrl(object):
"""Mochad controller."""
def __init__(self, host, port):
"""Initialize a PyMochad controller."""
super(MochadCtrl, self).__init__()
self._host = host
self._port = port
from pymochad import controller
self.ctrl = controller.PyMochad(server=self._host, port=self._port)
@property
def host(self):
"""Return the server where mochad is running."""
return self._host
@property
def port(self):
"""Return the port mochad is running on."""
return self._port
def disconnect(self):
"""Close the connection to the mochad socket."""
self.ctrl.socket.close()