diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 3f9364a4cd5..9afc826e83c 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -6,14 +6,14 @@ https://home-assistant.io/components/light.tellstick/ """ import voluptuous as vol -from homeassistant.components import tellstick from homeassistant.components.light import (ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, - ATTR_DISCOVER_CONFIG) + ATTR_DISCOVER_CONFIG, + DOMAIN, TellstickDevice) -PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): tellstick.DOMAIN}) +PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS @@ -22,32 +22,25 @@ SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Tellstick lights.""" if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None or - tellstick.TELLCORE_REGISTRY is None): + discovery_info[ATTR_DISCOVER_DEVICES] is None): return + # Allow platform level override, fallback to module config signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS) - add_devices(TellstickLight( - tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions) - for switch_id in discovery_info[ATTR_DISCOVER_DEVICES]) + add_devices(TellstickLight(tellcore_id, signal_repetitions) + for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]) -class TellstickLight(tellstick.TellstickDevice, Light): +class TellstickLight(TellstickDevice, Light): """Representation of a Tellstick light.""" - def __init__(self, tellstick_device, signal_repetitions): + def __init__(self, tellcore_id, signal_repetitions): """Initialize the light.""" - self._brightness = 255 - tellstick.TellstickDevice.__init__(self, - tellstick_device, - signal_repetitions) + super().__init__(tellcore_id, signal_repetitions) - @property - def is_on(self): - """Return true if switch is on.""" - return self._state + self._brightness = 255 @property def brightness(self): @@ -59,37 +52,32 @@ class TellstickLight(tellstick.TellstickDevice, Light): """Flag supported features.""" return SUPPORT_TELLSTICK - def set_tellstick_state(self, last_command_sent, last_data_sent): - """Update the internal representation of the switch.""" - from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_DIM - if last_command_sent == TELLSTICK_DIM: - if last_data_sent is not None: - self._brightness = int(last_data_sent) - self._state = self._brightness > 0 + def _parse_ha_data(self, kwargs): + """Turn the value from HA into something useful.""" + return kwargs.get(ATTR_BRIGHTNESS) + + def _parse_tellcore_data(self, tellcore_data): + """Turn the value recieved from tellcore into something useful.""" + if tellcore_data is not None: + brightness = int(tellcore_data) + return brightness else: - self._state = last_command_sent == TELLSTICK_TURNON + return None - def _send_tellstick_command(self, command, data): - """Handle the turn_on / turn_off commands.""" - from tellcore.constants import (TELLSTICK_TURNOFF, TELLSTICK_DIM) - if command == TELLSTICK_TURNOFF: - self.tellstick_device.turn_off() - elif command == TELLSTICK_DIM: - self.tellstick_device.dim(self._brightness) + def _update_model(self, new_state, data): + """Update the device entity state to match the arguments.""" + if new_state: + brightness = data + if brightness is not None: + self._brightness = brightness + + self._state = (self._brightness > 0) else: - raise NotImplementedError( - "Command not implemented: {}".format(command)) + self._state = False - def turn_on(self, **kwargs): - """Turn the switch on.""" - from tellcore.constants import TELLSTICK_DIM - brightness = kwargs.get(ATTR_BRIGHTNESS) - if brightness is not None: - self._brightness = brightness - - self.call_tellstick(TELLSTICK_DIM, self._brightness) - - def turn_off(self, **kwargs): - """Turn the switch off.""" - from tellcore.constants import TELLSTICK_TURNOFF - self.call_tellstick(TELLSTICK_TURNOFF) + def _send_tellstick_command(self): + """Let tellcore update the device to match the current state.""" + if self._state: + self._tellcore_device.dim(self._brightness) + else: + self._tellcore_device.turn_off() diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index 08e15cd332f..d16bb28f4f4 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -16,6 +16,8 @@ import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['tellcore-py==1.1.2'] +_LOGGER = logging.getLogger(__name__) + DatatypeDescription = namedtuple('DatatypeDescription', ['name', 'unit']) CONF_DATATYPE_MASK = 'datatype_mask' @@ -65,27 +67,28 @@ def setup_platform(hass, config, add_devices, discovery_info=None): } try: - core = telldus.TelldusCore() + tellcore_lib = telldus.TelldusCore() except OSError: - logging.getLogger(__name__).exception('Could not initialize Tellstick') + _LOGGER.exception('Could not initialize Tellstick') return sensors = [] datatype_mask = config.get(CONF_DATATYPE_MASK) - for ts_sensor in core.sensors(): + for tellcore_sensor in tellcore_lib.sensors(): try: - sensor_name = config[ts_sensor.id] + sensor_name = config[tellcore_sensor.id] except KeyError: if config.get(CONF_ONLY_NAMED): continue - sensor_name = str(ts_sensor.id) + sensor_name = str(tellcore_sensor.id) for datatype in sensor_value_descriptions: - if datatype & datatype_mask and ts_sensor.has_value(datatype): - sensor_info = sensor_value_descriptions[datatype] - sensors.append(TellstickSensor( - sensor_name, ts_sensor, datatype, sensor_info)) + if datatype & datatype_mask: + if tellcore_sensor.has_value(datatype): + sensor_info = sensor_value_descriptions[datatype] + sensors.append(TellstickSensor( + sensor_name, tellcore_sensor, datatype, sensor_info)) add_devices(sensors) @@ -93,10 +96,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TellstickSensor(Entity): """Representation of a Tellstick sensor.""" - def __init__(self, name, sensor, datatype, sensor_info): + def __init__(self, name, tellcore_sensor, datatype, sensor_info): """Initialize the sensor.""" - self.datatype = datatype - self.sensor = sensor + self._datatype = datatype + self._tellcore_sensor = tellcore_sensor self._unit_of_measurement = sensor_info.unit or None self._value = None @@ -119,4 +122,4 @@ class TellstickSensor(Entity): def update(self): """Update tellstick sensor.""" - self._value = self.sensor.value(self.datatype).value + self._value = self._tellcore_sensor.value(self._datatype).value diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index e5134c07a34..d3660ab36ca 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -6,61 +6,51 @@ https://home-assistant.io/components/switch.tellstick/ """ import voluptuous as vol -from homeassistant.components import tellstick -from homeassistant.components.tellstick import (ATTR_DISCOVER_DEVICES, - ATTR_DISCOVER_CONFIG) +from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS, + ATTR_DISCOVER_DEVICES, + ATTR_DISCOVER_CONFIG, + DOMAIN, TellstickDevice) from homeassistant.helpers.entity import ToggleEntity -PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): tellstick.DOMAIN}) +PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Tellstick switches.""" if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None or - tellstick.TELLCORE_REGISTRY is None): + discovery_info[ATTR_DISCOVER_DEVICES] is None): return # Allow platform level override, fallback to module config - signal_repetitions = discovery_info.get( - ATTR_DISCOVER_CONFIG, tellstick.DEFAULT_SIGNAL_REPETITIONS) + signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG, + DEFAULT_SIGNAL_REPETITIONS) - add_devices(TellstickSwitchDevice( - tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions) - for switch_id in discovery_info[ATTR_DISCOVER_DEVICES]) + add_devices(TellstickSwitch(tellcore_id, signal_repetitions) + for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]) -class TellstickSwitchDevice(tellstick.TellstickDevice, ToggleEntity): +class TellstickSwitch(TellstickDevice, ToggleEntity): """Representation of a Tellstick switch.""" - @property - def is_on(self): - """Return true if switch is on.""" - return self._state + def _parse_ha_data(self, kwargs): + """Turn the value from HA into something useful.""" + return None - def set_tellstick_state(self, last_command_sent, last_data_sent): - """Update the internal representation of the switch.""" - from tellcore.constants import TELLSTICK_TURNON - self._state = last_command_sent == TELLSTICK_TURNON + def _parse_tellcore_data(self, tellcore_data): + """Turn the value recieved from tellcore into something useful.""" + return None - def _send_tellstick_command(self, command, data): - """Handle the turn_on / turn_off commands.""" - from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_TURNOFF - if command == TELLSTICK_TURNON: - self.tellstick_device.turn_on() - elif command == TELLSTICK_TURNOFF: - self.tellstick_device.turn_off() + def _update_model(self, new_state, data): + """Update the device entity state to match the arguments.""" + self._state = new_state - def turn_on(self, **kwargs): - """Turn the switch on.""" - from tellcore.constants import TELLSTICK_TURNON - self.call_tellstick(TELLSTICK_TURNON) - - def turn_off(self, **kwargs): - """Turn the switch off.""" - from tellcore.constants import TELLSTICK_TURNOFF - self.call_tellstick(TELLSTICK_TURNOFF) + def _send_tellstick_command(self): + """Let tellcore update the device to match the current state.""" + if self._state: + self._tellcore_device.turn_on() + else: + self._tellcore_device.turn_off() @property def force_update(self) -> bool: diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py index 6d8ad967ad2..cbd5ff20583 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -25,12 +25,12 @@ DEFAULT_SIGNAL_REPETITIONS = 1 ATTR_DISCOVER_DEVICES = 'devices' ATTR_DISCOVER_CONFIG = 'config' -# Use a global tellstick domain lock to handle Tellcore errors then calling -# to concurrently +# Use a global tellstick domain lock to avoid getting Tellcore errors when +# calling concurrently. TELLSTICK_LOCK = threading.Lock() -# Keep a reference the the callback registry. Used from entities that register -# callback listeners +# A TellstickRegistry that keeps a map from tellcore_id to the corresponding +# tellcore_device and HA device (entity). TELLCORE_REGISTRY = None CONFIG_SCHEMA = vol.Schema({ @@ -41,50 +41,52 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -def _discover(hass, config, found_devices, component_name): +def _discover(hass, config, component_name, found_tellcore_devices): """Setup and send the discovery event.""" - if not len(found_devices): + if not len(found_tellcore_devices): return - _LOGGER.info( - "Discovered %d new %s devices", len(found_devices), component_name) + _LOGGER.info("Discovered %d new %s devices", len(found_tellcore_devices), + component_name) signal_repetitions = config[DOMAIN].get(ATTR_SIGNAL_REPETITIONS) discovery.load_platform(hass, component_name, DOMAIN, { - ATTR_DISCOVER_DEVICES: found_devices, + ATTR_DISCOVER_DEVICES: found_tellcore_devices, ATTR_DISCOVER_CONFIG: signal_repetitions}, config) def setup(hass, config): """Setup the Tellstick component.""" - # pylint: disable=global-statement, import-error + from tellcore.constants import TELLSTICK_DIM + from tellcore.library import DirectCallbackDispatcher + from tellcore.telldus import TelldusCore + global TELLCORE_REGISTRY - import tellcore.telldus as telldus - import tellcore.constants as tellcore_constants - from tellcore.library import DirectCallbackDispatcher + try: + tellcore_lib = TelldusCore( + callback_dispatcher=DirectCallbackDispatcher()) + except OSError: + _LOGGER.exception('Could not initialize Tellstick') + return False - core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher()) - - TELLCORE_REGISTRY = TellstickRegistry(hass, core) - - devices = core.devices() + # Get all devices, switches and lights alike + all_tellcore_devices = tellcore_lib.devices() # Register devices - TELLCORE_REGISTRY.register_devices(devices) + TELLCORE_REGISTRY = TellstickRegistry(hass, tellcore_lib) + TELLCORE_REGISTRY.register_tellcore_devices(all_tellcore_devices) # Discover the switches - _discover(hass, config, [switch.id for switch in - devices if not switch.methods( - tellcore_constants.TELLSTICK_DIM)], - 'switch') + _discover(hass, config, 'switch', + [tellcore_device.id for tellcore_device in all_tellcore_devices + if not tellcore_device.methods(TELLSTICK_DIM)]) # Discover the lights - _discover(hass, config, [light.id for light in - devices if light.methods( - tellcore_constants.TELLSTICK_DIM)], - 'light') + _discover(hass, config, 'light', + [tellcore_device.id for tellcore_device in all_tellcore_devices + if tellcore_device.methods(TELLSTICK_DIM)]) return True @@ -92,50 +94,57 @@ def setup(hass, config): class TellstickRegistry(object): """Handle everything around Tellstick callbacks. - Keeps a map device ids to home-assistant entities. - Also responsible for registering / cleanup of callbacks. + Keeps a map device ids to the tellcore device object, and + another to the HA device objects (entities). + + Also responsible for registering / cleanup of callbacks, and for + dispatching the callbacks to the corresponding HA device object. All device specific logic should be elsewhere (Entities). """ def __init__(self, hass, tellcore_lib): """Initialize the Tellstick mappings and callbacks.""" - self._core_lib = tellcore_lib # used when map callback device id to ha entities. - self._id_to_entity_map = {} - self._id_to_device_map = {} - self._setup_device_callback(hass, tellcore_lib) + self._id_to_ha_device_map = {} + self._id_to_tellcore_device_map = {} + self._setup_tellcore_callback(hass, tellcore_lib) - def _device_callback(self, tellstick_id, method, data, cid): + def _tellcore_event_callback(self, tellcore_id, tellcore_command, + tellcore_data, cid): """Handle the actual callback from Tellcore.""" - entity = self._id_to_entity_map.get(tellstick_id, None) - if entity is not None: - entity.set_tellstick_state(method, data) - entity.schedule_update_ha_state() + ha_device = self._id_to_ha_device_map.get(tellcore_id, None) + if ha_device is not None: + # Pass it on to the HA device object + ha_device.update_from_tellcore(tellcore_command, tellcore_data) + ha_device.schedule_update_ha_state() - def _setup_device_callback(self, hass, tellcore_lib): + def _setup_tellcore_callback(self, hass, tellcore_lib): """Register the callback handler.""" - callback_id = tellcore_lib.register_device_event(self._device_callback) + callback_id = tellcore_lib.register_device_event( + self._tellcore_event_callback) def clean_up_callback(event): """Unregister the callback bindings.""" if callback_id is not None: tellcore_lib.unregister_callback(callback_id) + _LOGGER.debug("Tellstick callback unregistered") hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, clean_up_callback) - def register_entity(self, tellcore_id, entity): - """Register a new entity to receive callback updates.""" - self._id_to_entity_map[tellcore_id] = entity + def register_ha_device(self, tellcore_id, ha_device): + """Register a new HA device to receive callback updates.""" + self._id_to_ha_device_map[tellcore_id] = ha_device - def register_devices(self, devices): + def register_tellcore_devices(self, tellcore_devices): """Register a list of devices.""" - self._id_to_device_map.update( - {device.id: device for device in devices}) + self._id_to_tellcore_device_map.update( + {tellcore_device.id: tellcore_device for tellcore_device + in tellcore_devices}) - def get_device(self, tellcore_id): + def get_tellcore_device(self, tellcore_id): """Return a device by tellcore_id.""" - return self._id_to_device_map.get(tellcore_id, None) + return self._id_to_tellcore_device_map.get(tellcore_id, None) class TellstickDevice(Entity): @@ -144,19 +153,21 @@ class TellstickDevice(Entity): Contains the common logic for all Tellstick devices. """ - def __init__(self, tellstick_device, signal_repetitions): + def __init__(self, tellcore_id, signal_repetitions): """Initalize the Tellstick device.""" - self.signal_repetitions = signal_repetitions + self._signal_repetitions = signal_repetitions self._state = None - self.tellstick_device = tellstick_device - # Add to id to entity mapping - TELLCORE_REGISTRY.register_entity(tellstick_device.id, self) + # Look up our corresponding tellcore device + self._tellcore_device = TELLCORE_REGISTRY.get_tellcore_device( + tellcore_id) # Query tellcore for the current state self.update() + # Add ourselves to the mapping + TELLCORE_REGISTRY.register_ha_device(tellcore_id, self) @property def should_poll(self): - """Tell Home Assistant not to poll this entity.""" + """Tell Home Assistant not to poll this device.""" return False @property @@ -166,43 +177,80 @@ class TellstickDevice(Entity): @property def name(self): - """Return the name of the switch if any.""" - return self.tellstick_device.name + """Return the name of the device as reported by tellcore.""" + return self._tellcore_device.name - def set_tellstick_state(self, last_command_sent, last_data_sent): - """Set the private switch state.""" - raise NotImplementedError( - "set_tellstick_state needs to be implemented.") + @property + def is_on(self): + """Return true if the device is on.""" + return self._state - def _send_tellstick_command(self, command, data): - """Do the actual call to the tellstick device.""" - raise NotImplementedError( - "_call_tellstick needs to be implemented.") + def _parse_ha_data(self, kwargs): + """Turn the value from HA into something useful.""" + raise NotImplementedError - def call_tellstick(self, command, data=None): - """Send a command to the device.""" + def _parse_tellcore_data(self, tellcore_data): + """Turn the value recieved from tellcore into something useful.""" + raise NotImplementedError + + def _update_model(self, new_state, data): + """Update the device entity state to match the arguments.""" + raise NotImplementedError + + def _send_tellstick_command(self): + """Let tellcore update the device to match the current state.""" + raise NotImplementedError + + def _do_action(self, new_state, data): + """The logic for actually turning on or off the device.""" from tellcore.library import TelldusError + with TELLSTICK_LOCK: + # Update self with requested new state + self._update_model(new_state, data) + # ... and then send this new state to the Tellstick try: - for _ in range(self.signal_repetitions): - self._send_tellstick_command(command, data) - # Update the internal state - self.set_tellstick_state(command, data) - self.update_ha_state() + for _ in range(self._signal_repetitions): + self._send_tellstick_command() except TelldusError: _LOGGER.error(TelldusError) + self.update_ha_state() + + def turn_on(self, **kwargs): + """Turn the switch on.""" + self._do_action(True, self._parse_ha_data(kwargs)) + + def turn_off(self, **kwargs): + """Turn the switch off.""" + self._do_action(False, None) + + def update_from_tellcore(self, tellcore_command, tellcore_data): + """Handle updates from the tellcore callback.""" + from tellcore.constants import (TELLSTICK_TURNON, TELLSTICK_TURNOFF, + TELLSTICK_DIM) + + if tellcore_command not in [TELLSTICK_TURNON, TELLSTICK_TURNOFF, + TELLSTICK_DIM]: + _LOGGER.debug("Unhandled tellstick command: %d", + tellcore_command) + return + + self._update_model(tellcore_command != TELLSTICK_TURNOFF, + self._parse_tellcore_data(tellcore_data)) def update(self): """Poll the current state of the device.""" - import tellcore.constants as tellcore_constants from tellcore.library import TelldusError + from tellcore.constants import (TELLSTICK_TURNON, TELLSTICK_TURNOFF, + TELLSTICK_DIM) + try: - last_command = self.tellstick_device.last_sent_command( - tellcore_constants.TELLSTICK_TURNON | - tellcore_constants.TELLSTICK_TURNOFF | - tellcore_constants.TELLSTICK_DIM + last_tellcore_command = self._tellcore_device.last_sent_command( + TELLSTICK_TURNON | TELLSTICK_TURNOFF | TELLSTICK_DIM ) - last_value = self.tellstick_device.last_sent_value() - self.set_tellstick_state(last_command, last_value) + last_tellcore_data = self._tellcore_device.last_sent_value() + + self.update_from_tellcore(last_tellcore_command, + last_tellcore_data) except TelldusError: _LOGGER.error(TelldusError)