From 6211c30766d0e0a10bcf64f76bb01967aeefe1f4 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Fri, 11 May 2018 22:06:06 -0700 Subject: [PATCH 01/11] Add a component for Sisyphus Kinetic Art Tables The [Sisyphus Kinetic Art Table](https://sisyphus-industries.com/) uses a steel ball to draw intricate patterns in sand, thrown into sharp relief by a ring of LED lights around the outside. This component enables basic control of these tables through Home Assistant. --- .coveragerc | 3 + homeassistant/components/light/sisyphus.py | 66 +++++++ .../components/media_player/sisyphus.py | 176 ++++++++++++++++++ homeassistant/components/sisyphus.py | 82 ++++++++ requirements_all.txt | 3 + 5 files changed, 330 insertions(+) create mode 100644 homeassistant/components/light/sisyphus.py create mode 100644 homeassistant/components/media_player/sisyphus.py create mode 100644 homeassistant/components/sisyphus.py diff --git a/.coveragerc b/.coveragerc index 73a79c2d87bf7a..53073ff56aa094 100644 --- a/.coveragerc +++ b/.coveragerc @@ -251,6 +251,9 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + homeassistant/components/sisyphus.py + homeassistant/components/*/sisyphus.py + homeassistant/components/skybell.py homeassistant/components/*/skybell.py diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py new file mode 100644 index 00000000000000..ae332261940799 --- /dev/null +++ b/homeassistant/components/light/sisyphus.py @@ -0,0 +1,66 @@ +""" +Exposes a Sisyphus Kinetic Art Table as a light. Turning the switch off +will sleep the table; turning it on will wake it up. Brightness controls the +table light brightness. +""" +import logging + +from homeassistant.const import CONF_NAME +from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.sisyphus import DATA_SISYPHUS + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['sisyphus'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + name = discovery_info[CONF_NAME] + add_devices( + [SisyphusSwitch(name, hass.data[DATA_SISYPHUS][name])], + update_before_add = True) + + +class SisyphusSwitch(Light): + def __init__(self, name, table): + self._name = name + self._table = table + self._initialized = False + + def update(self): + if not self._initialized: + # We wait until update before adding the listener because + # otherwise there's a race condition by which this entity + # might not have had its hass field set, and thus + # the schedule_update_ha_state call will fail + self._table.add_listener( + lambda: self.schedule_update_ha_state(False)) + self._initialized = True + + @property + def name(self): + return self._name + + @property + def is_on(self): + return not self._table.is_sleeping + + @property + def brightness(self): + return self._table.brightness * 255 + + @property + def supported_features(self): + return SUPPORT_BRIGHTNESS + + async def async_turn_off(self, **kwargs): + await self._table.sleep() + _LOGGER.debug("Sisyphus table %s: sleep") + + async def async_turn_on(self, **kwargs): + if not self.is_on: + await self._table.wakeup() + _LOGGER.debug("Sisyphus table %s: wakeup") + + if "brightness" in kwargs: + await self._table.set_brightness(kwargs["brightness"] / 255.0) diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py new file mode 100644 index 00000000000000..ff522c7d6c6e60 --- /dev/null +++ b/homeassistant/components/media_player/sisyphus.py @@ -0,0 +1,176 @@ +""" +Exposes the Sisyphus Kinetic Art Table as a media player. Media controls +work as one would expect. Volume controls table speed. +""" +import logging + +from homeassistant.components.media_player import ( + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SHUFFLE_SET, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + MediaPlayerDevice) +from homeassistant.components.sisyphus import DATA_SISYPHUS +from homeassistant.const import CONF_HOST, CONF_NAME, STATE_PLAYING, \ + STATE_PAUSED, STATE_IDLE, STATE_OFF + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['sisyphus'] + +MEDIA_TYPE_TRACK = "sisyphus_track" + +# Features that are always available, regardless of the state of the table +ALWAYS_FEATURES = \ + SUPPORT_VOLUME_MUTE \ + | SUPPORT_VOLUME_SET \ + | SUPPORT_TURN_OFF \ + | SUPPORT_TURN_ON + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + name = discovery_info[CONF_NAME] + host = discovery_info[CONF_HOST] + add_devices( + [SisyphusPlayer(name, host, hass.data[DATA_SISYPHUS][name])], + update_before_add=True) + + +class SisyphusPlayer(MediaPlayerDevice): + def __init__(self, name, host, table): + self._name = name + self._host = host + self._table = table + self._initialized = False + + def update(self): + if not self._initialized: + # We wait until update before adding the listener because + # otherwise there's a race condition by which this entity + # might not have had its hass field set, and thus + # the schedule_update_ha_state call will fail + self._table.add_listener( + lambda: self.schedule_update_ha_state(False)) + self._initialized = True + + @property + def name(self): + return self._name + + @property + def state(self): + if self._table.state in ["homing", "playing"]: + return STATE_PLAYING + elif self._table.state == "paused": + if self._table.is_sleeping: + return STATE_OFF + else: + return STATE_PAUSED + elif self._table.state == "waiting": + return STATE_IDLE + raise Exception("Unknown state: {0}".format(self._table.state)) + + @property + def volume_level(self): + return self._table.speed + + @property + def shuffle(self): + return self._table.is_shuffle + + async def async_set_shuffle(self, shuffle): + await self._table.set_shuffle(shuffle) + + @property + def media_playlist(self): + return self._table.active_playlist.name \ + if self._table.active_playlist \ + else None + + @property + def media_title(self): + return self._table.active_track.name \ + if self._table.active_track \ + else None + + @property + def media_content_type(self): + return MEDIA_TYPE_TRACK + + @property + def media_content_id(self): + return self._table.active_track.id \ + if self._table.active_track \ + else None + + @property + def supported_features(self): + if self.state == STATE_PLAYING: + features = ALWAYS_FEATURES | SUPPORT_PAUSE + + if self._table.active_playlist: + features |= SUPPORT_SHUFFLE_SET + current_track_index = self._get_current_track_index() + if current_track_index > 0: + features |= SUPPORT_PREVIOUS_TRACK + if current_track_index < len(self._table.tracks) - 1: + features |= SUPPORT_NEXT_TRACK + + return features + else: + return ALWAYS_FEATURES | SUPPORT_PLAY + + @property + def media_image_url(self): + from sisyphus.control import Track + if self._table.active_track: + return self._table.active_track.get_thumbnail_url( + Track.ThumbnailSize.LARGE) + else: + return super.media_image_url() + + async def async_turn_on(self): + await self._table.wakeup() + + async def async_turn_off(self): + await self._table.sleep() + + async def async_volume_down(self): + await self._table.set_speed(max(0, self._table.speed - 0.1)) + + async def async_volume_up(self): + await self._table.set_speed(min(1.0, self._table.speed + 0.1)) + + async def async_set_volume_level(self, volume): + await self._table.set_speed(volume) + + async def async_media_play(self): + await self._table.play() + + async def async_media_pause(self): + await self._table.pause() + + async def async_media_next_track(self): + cur_track_index = self._get_current_track_index() + + await self._table.active_playlist.play( + self._table.active_playlist.tracks[cur_track_index + 1]) + + async def async_media_previous_track(self): + cur_track_index = self._get_current_track_index() + + await self._table.active_playlist.play( + self._table.active_playlist.tracks[cur_track_index - 1]) + + def _get_current_track_index(self): + for index, track in enumerate(self._table.active_playlist.tracks): + if track.id == self._table.active_track.id: + return index + + return -1 diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py new file mode 100644 index 00000000000000..7fe3788d416b38 --- /dev/null +++ b/homeassistant/components/sisyphus.py @@ -0,0 +1,82 @@ +""" +Enables control of Sisyphus Kinetic Art Tables. Each table is exposed as +a light and a media player. +""" +import logging +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + EVENT_HOMEASSISTANT_STOP +) +from homeassistant.helpers.discovery import async_load_platform + +REQUIREMENTS = ['sisyphus-control==1.1.1'] + +_LOGGER = logging.getLogger(__name__) + +DATA_SISYPHUS = 'sisyphus' +DOMAIN = 'sisyphus' + +AUTODETECT_SCHEMA = vol.Schema({ + DOMAIN: {}, +}, extra=vol.ALLOW_EXTRA) + +TABLES_SCHEMA = vol.Schema({ + DOMAIN: [ + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + }, + ], +}, extra=vol.ALLOW_EXTRA) + +CONFIG_SCHEMA = vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA) + + +async def async_setup(hass, config): + import sisyphus.control + tables = hass.data.setdefault(DATA_SISYPHUS, {}) + table_configs = config.get(DOMAIN) + + async def add_table(host, name=None): + table = await sisyphus.control.Table.connect(host) + if name is None: + name = table.name + tables[name] = table + _LOGGER.debug("Connected to %s at %s", name, host) + + hass.async_add_job(async_load_platform( + hass, 'light', 'sisyphus', { + CONF_NAME: name, + }, config + )) + hass.async_add_job(async_load_platform( + hass, 'media_player', 'sisyphus', { + CONF_NAME: name, + CONF_HOST: host, + }, config + )) + + if isinstance(table_configs, dict): # AUTODETECT_SCHEMA + for ip in await sisyphus.control.Table.find_table_ips(): + await add_table(ip) + else: # TABLES_SCHEMA + for conf in table_configs: + if conf.get(CONF_HOST) is not None: + await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) + + async def close_tables(*args): + for table in tables.values(): + await table.close() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables) + + return True + + + + + diff --git a/requirements_all.txt b/requirements_all.txt index cde7060df74f9d..bb648b18242078 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1243,6 +1243,9 @@ simplepush==1.1.4 # homeassistant.components.alarm_control_panel.simplisafe simplisafe-python==1.0.5 +# homeassistant.components.sisyphus +sisyphus-control==1.1.1 + # homeassistant.components.skybell skybellpy==0.1.2 From 001b7db83a33dc3df804c3d328d6e4d60fcf1f61 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Mon, 14 May 2018 22:30:40 -0700 Subject: [PATCH 02/11] Fix lints --- homeassistant/components/light/sisyphus.py | 4 ++-- homeassistant/components/sisyphus.py | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py index ae332261940799..75dceb65b02ef7 100644 --- a/homeassistant/components/light/sisyphus.py +++ b/homeassistant/components/light/sisyphus.py @@ -6,7 +6,7 @@ import logging from homeassistant.const import CONF_NAME -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light from homeassistant.components.sisyphus import DATA_SISYPHUS _LOGGER = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): name = discovery_info[CONF_NAME] add_devices( [SisyphusSwitch(name, hass.data[DATA_SISYPHUS][name])], - update_before_add = True) + update_before_add=True) class SisyphusSwitch(Light): diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index 7fe3788d416b38..0cd4814946008d 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -75,8 +75,3 @@ async def close_tables(*args): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables) return True - - - - - From a5c21013002020d847496646c62fde11e85a4b8b Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Tue, 15 May 2018 19:42:04 -0700 Subject: [PATCH 03/11] Docstrings, other lints --- homeassistant/components/light/sisyphus.py | 27 ++++++++++--- .../components/media_player/sisyphus.py | 40 ++++++++++++++++++- homeassistant/components/sisyphus.py | 6 ++- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py index 75dceb65b02ef7..a362f4a38fa469 100644 --- a/homeassistant/components/light/sisyphus.py +++ b/homeassistant/components/light/sisyphus.py @@ -1,7 +1,8 @@ """ -Exposes a Sisyphus Kinetic Art Table as a light. Turning the switch off -will sleep the table; turning it on will wake it up. Brightness controls the -table light brightness. +Exposes a Sisyphus Kinetic Art Table as a light. + +Turning the switch off will sleep the table; turning it on will wake it up. +Brightness controls the table light brightness. """ import logging @@ -15,19 +16,29 @@ def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a single Sisyphus table.""" name = discovery_info[CONF_NAME] add_devices( - [SisyphusSwitch(name, hass.data[DATA_SISYPHUS][name])], + [SisyphusLight(name, hass.data[DATA_SISYPHUS][name])], update_before_add=True) -class SisyphusSwitch(Light): +class SisyphusLight(Light): + """Represents a Sisyphus table as a light.""" + def __init__(self, name, table): + """ + Constructor. + + :param name: name of the table + :param table: sisyphus-control Table object + """ self._name = name self._table = table self._initialized = False def update(self): + """Lazily initializes the table.""" if not self._initialized: # We wait until update before adding the listener because # otherwise there's a race condition by which this entity @@ -39,25 +50,31 @@ def update(self): @property def name(self): + """Return the ame of the table.""" return self._name @property def is_on(self): + """Return True if the table is on.""" return not self._table.is_sleeping @property def brightness(self): + """Return the urrent brightness of the table's ring light.""" return self._table.brightness * 255 @property def supported_features(self): + """Return the eatures supported by the table; i.e. brightness.""" return SUPPORT_BRIGHTNESS async def async_turn_off(self, **kwargs): + """Put the table to sleep.""" await self._table.sleep() _LOGGER.debug("Sisyphus table %s: sleep") async def async_turn_on(self, **kwargs): + """Wake up the table if necessary, optionally changes brightness.""" if not self.is_on: await self._table.wakeup() _LOGGER.debug("Sisyphus table %s: wakeup") diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index ff522c7d6c6e60..3318bdfc867f4b 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -1,6 +1,7 @@ """ -Exposes the Sisyphus Kinetic Art Table as a media player. Media controls -work as one would expect. Volume controls table speed. +Exposes the Sisyphus Kinetic Art Table as a media player. + +Media controls work as one would expect. Volume controls table speed. """ import logging @@ -35,6 +36,7 @@ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a media player entity for a Sisyphus table.""" name = discovery_info[CONF_NAME] host = discovery_info[CONF_HOST] add_devices( @@ -43,13 +45,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SisyphusPlayer(MediaPlayerDevice): + """Represents a single Sisyphus table as a media player device.""" + def __init__(self, name, host, table): + """ + Constructor. + + :param name: name of the table + :param host: hostname or ip address + :param table: sisyphus-control Table object + """ self._name = name self._host = host self._table = table self._initialized = False def update(self): + """Lazily initializes the table.""" if not self._initialized: # We wait until update before adding the listener because # otherwise there's a race condition by which this entity @@ -61,10 +73,12 @@ def update(self): @property def name(self): + """Return the name of the table.""" return self._name @property def state(self): + """Return the urrent state of the table; sleeping maps to off.""" if self._table.state in ["homing", "playing"]: return STATE_PLAYING elif self._table.state == "paused": @@ -78,39 +92,51 @@ def state(self): @property def volume_level(self): + """Return the current playback speed (0..1).""" return self._table.speed @property def shuffle(self): + """Return True if the current playlist is in shuffle mode.""" return self._table.is_shuffle async def async_set_shuffle(self, shuffle): + """ + Change the shuffle mode of the current playlist. + + :param shuffle: True to shuffle, False not to + """ await self._table.set_shuffle(shuffle) @property def media_playlist(self): + """Return the name of the current playlist.""" return self._table.active_playlist.name \ if self._table.active_playlist \ else None @property def media_title(self): + """Return the title of the current track.""" return self._table.active_track.name \ if self._table.active_track \ else None @property def media_content_type(self): + """Return the content type currently playing; i.e. a Sisyphus track.""" return MEDIA_TYPE_TRACK @property def media_content_id(self): + """Return the track ID of the current track.""" return self._table.active_track.id \ if self._table.active_track \ else None @property def supported_features(self): + """Return the features supported by this table in its current state.""" if self.state == STATE_PLAYING: features = ALWAYS_FEATURES | SUPPORT_PAUSE @@ -128,6 +154,7 @@ def supported_features(self): @property def media_image_url(self): + """Return the URL for a thumbnail image of the current track.""" from sisyphus.control import Track if self._table.active_track: return self._table.active_track.get_thumbnail_url( @@ -136,33 +163,42 @@ def media_image_url(self): return super.media_image_url() async def async_turn_on(self): + """Wake up a sleeping table.""" await self._table.wakeup() async def async_turn_off(self): + """Put the table to sleep.""" await self._table.sleep() async def async_volume_down(self): + """Slow down playback.""" await self._table.set_speed(max(0, self._table.speed - 0.1)) async def async_volume_up(self): + """Speed up playback.""" await self._table.set_speed(min(1.0, self._table.speed + 0.1)) async def async_set_volume_level(self, volume): + """Set playback speed (0..1).""" await self._table.set_speed(volume) async def async_media_play(self): + """Start playing.""" await self._table.play() async def async_media_pause(self): + """Pause.""" await self._table.pause() async def async_media_next_track(self): + """Skip to next track.""" cur_track_index = self._get_current_track_index() await self._table.active_playlist.play( self._table.active_playlist.tracks[cur_track_index + 1]) async def async_media_previous_track(self): + """Skip to previous track.""" cur_track_index = self._get_current_track_index() await self._table.active_playlist.play( diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index 0cd4814946008d..c20d188bf4ffbc 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -1,6 +1,7 @@ """ -Enables control of Sisyphus Kinetic Art Tables. Each table is exposed as -a light and a media player. +Enables control of Sisyphus Kinetic Art Tables. + +Each table is exposed as a light and a media player. """ import logging import voluptuous as vol @@ -37,6 +38,7 @@ async def async_setup(hass, config): + """Set up the sisyphus component.""" import sisyphus.control tables = hass.data.setdefault(DATA_SISYPHUS, {}) table_configs = config.get(DOMAIN) From af35c9396351032bb4ddf5ec9bf6d623f55f01f1 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Sun, 20 May 2018 22:18:12 -0700 Subject: [PATCH 04/11] More lints --- homeassistant/components/media_player/sisyphus.py | 14 +++++++------- homeassistant/components/sisyphus.py | 12 +++++++----- requirements_all.txt | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index 3318bdfc867f4b..642f11152170cb 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -84,8 +84,8 @@ def state(self): elif self._table.state == "paused": if self._table.is_sleeping: return STATE_OFF - else: - return STATE_PAUSED + + return STATE_PAUSED elif self._table.state == "waiting": return STATE_IDLE raise Exception("Unknown state: {0}".format(self._table.state)) @@ -149,18 +149,18 @@ def supported_features(self): features |= SUPPORT_NEXT_TRACK return features - else: - return ALWAYS_FEATURES | SUPPORT_PLAY + + return ALWAYS_FEATURES | SUPPORT_PLAY @property def media_image_url(self): """Return the URL for a thumbnail image of the current track.""" - from sisyphus.control import Track + from sisyphus_control import Track if self._table.active_track: return self._table.active_track.get_thumbnail_url( Track.ThumbnailSize.LARGE) - else: - return super.media_image_url() + + return super.media_image_url() async def async_turn_on(self): """Wake up a sleeping table.""" diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index c20d188bf4ffbc..80c1d740642237 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -14,7 +14,7 @@ ) from homeassistant.helpers.discovery import async_load_platform -REQUIREMENTS = ['sisyphus-control==1.1.1'] +REQUIREMENTS = ['sisyphus-control==2.0.0'] _LOGGER = logging.getLogger(__name__) @@ -39,12 +39,13 @@ async def async_setup(hass, config): """Set up the sisyphus component.""" - import sisyphus.control + from sisyphus_control import Table tables = hass.data.setdefault(DATA_SISYPHUS, {}) table_configs = config.get(DOMAIN) async def add_table(host, name=None): - table = await sisyphus.control.Table.connect(host) + """Adds platforms for a single table with the given hostname""" + table = await Table.connect(host) if name is None: name = table.name tables[name] = table @@ -63,14 +64,15 @@ async def add_table(host, name=None): )) if isinstance(table_configs, dict): # AUTODETECT_SCHEMA - for ip in await sisyphus.control.Table.find_table_ips(): - await add_table(ip) + for ip_address in await Table.find_table_ips(): + await add_table(ip_address) else: # TABLES_SCHEMA for conf in table_configs: if conf.get(CONF_HOST) is not None: await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) async def close_tables(*args): + """Closes all table objects""" for table in tables.values(): await table.close() diff --git a/requirements_all.txt b/requirements_all.txt index bb648b18242078..151739cb5e787c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1244,7 +1244,7 @@ simplepush==1.1.4 simplisafe-python==1.0.5 # homeassistant.components.sisyphus -sisyphus-control==1.1.1 +sisyphus-control==2.0.0 # homeassistant.components.skybell skybellpy==0.1.2 From bfdeddf13f285eada7e322fec2c78585e4da4360 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Mon, 21 May 2018 20:05:18 -0700 Subject: [PATCH 05/11] Yet more. --- homeassistant/components/sisyphus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index 80c1d740642237..f99da5d557bc14 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -44,7 +44,7 @@ async def async_setup(hass, config): table_configs = config.get(DOMAIN) async def add_table(host, name=None): - """Adds platforms for a single table with the given hostname""" + """Add platforms for a single table with the given hostname.""" table = await Table.connect(host) if name is None: name = table.name @@ -72,7 +72,7 @@ async def add_table(host, name=None): await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) async def close_tables(*args): - """Closes all table objects""" + """Close all table objects.""" for table in tables.values(): await table.close() From 78463c15dd01e8952c42fcde75b91ae94a98f655 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Tue, 5 Jun 2018 19:58:39 -0700 Subject: [PATCH 06/11] Feedback --- homeassistant/components/light/sisyphus.py | 22 ++++------- .../components/media_player/sisyphus.py | 37 +++++++------------ homeassistant/components/sisyphus.py | 37 +++++++++---------- 3 files changed, 38 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py index a362f4a38fa469..c90ca181d7a043 100644 --- a/homeassistant/components/light/sisyphus.py +++ b/homeassistant/components/light/sisyphus.py @@ -1,8 +1,8 @@ """ -Exposes a Sisyphus Kinetic Art Table as a light. +Support for the light on the Sisyphus Kinetic Art Table. -Turning the switch off will sleep the table; turning it on will wake it up. -Brightness controls the table light brightness. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.sisyphus/ """ import logging @@ -35,18 +35,10 @@ def __init__(self, name, table): """ self._name = name self._table = table - self._initialized = False - - def update(self): - """Lazily initializes the table.""" - if not self._initialized: - # We wait until update before adding the listener because - # otherwise there's a race condition by which this entity - # might not have had its hass field set, and thus - # the schedule_update_ha_state call will fail - self._table.add_listener( - lambda: self.schedule_update_ha_state(False)) - self._initialized = True + + async def async_added_to_hass(self): + self._table.add_listener( + lambda: self.schedule_update_ha_state(False)) @property def name(self): diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index 642f11152170cb..22da7a887bc296 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -1,7 +1,8 @@ """ -Exposes the Sisyphus Kinetic Art Table as a media player. +Support for track controls on the Sisyphus Kinetic Art Table. -Media controls work as one would expect. Volume controls table speed. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.sisyphus/ """ import logging @@ -26,13 +27,6 @@ MEDIA_TYPE_TRACK = "sisyphus_track" -# Features that are always available, regardless of the state of the table -ALWAYS_FEATURES = \ - SUPPORT_VOLUME_MUTE \ - | SUPPORT_VOLUME_SET \ - | SUPPORT_TURN_OFF \ - | SUPPORT_TURN_ON - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): @@ -136,21 +130,16 @@ def media_content_id(self): @property def supported_features(self): - """Return the features supported by this table in its current state.""" - if self.state == STATE_PLAYING: - features = ALWAYS_FEATURES | SUPPORT_PAUSE - - if self._table.active_playlist: - features |= SUPPORT_SHUFFLE_SET - current_track_index = self._get_current_track_index() - if current_track_index > 0: - features |= SUPPORT_PREVIOUS_TRACK - if current_track_index < len(self._table.tracks) - 1: - features |= SUPPORT_NEXT_TRACK - - return features - - return ALWAYS_FEATURES | SUPPORT_PLAY + """Return the features supported by this table.""" + return SUPPORT_VOLUME_MUTE \ + | SUPPORT_VOLUME_SET \ + | SUPPORT_TURN_OFF \ + | SUPPORT_TURN_ON \ + | SUPPORT_PAUSE \ + | SUPPORT_SHUFFLE_SET \ + | SUPPORT_PREVIOUS_TRACK \ + | SUPPORT_NEXT_TRACK \ + | SUPPORT_PLAY @property def media_image_url(self): diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index f99da5d557bc14..85281d539d034f 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -1,17 +1,19 @@ """ -Enables control of Sisyphus Kinetic Art Tables. +Support for controlling Sisyphus Kinetic Art Tables. -Each table is exposed as a light and a media player. +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/sisyphus/ """ import logging + import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform REQUIREMENTS = ['sisyphus-control==2.0.0'] @@ -21,20 +23,18 @@ DATA_SISYPHUS = 'sisyphus' DOMAIN = 'sisyphus' -AUTODETECT_SCHEMA = vol.Schema({ - DOMAIN: {}, -}, extra=vol.ALLOW_EXTRA) +AUTODETECT_SCHEMA = vol.Schema({}) -TABLES_SCHEMA = vol.Schema({ - DOMAIN: [ - { - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - }, - ], -}, extra=vol.ALLOW_EXTRA) +TABLE_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, +}) + +TABLES_SCHEMA = vol.Schema([TABLE_SCHEMA]) -CONFIG_SCHEMA = vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA) +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA), +}, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): @@ -52,12 +52,12 @@ async def add_table(host, name=None): _LOGGER.debug("Connected to %s at %s", name, host) hass.async_add_job(async_load_platform( - hass, 'light', 'sisyphus', { + hass, 'light', DOMAIN, { CONF_NAME: name, }, config )) hass.async_add_job(async_load_platform( - hass, 'media_player', 'sisyphus', { + hass, 'media_player', DOMAIN, { CONF_NAME: name, CONF_HOST: host, }, config @@ -68,8 +68,7 @@ async def add_table(host, name=None): await add_table(ip_address) else: # TABLES_SCHEMA for conf in table_configs: - if conf.get(CONF_HOST) is not None: - await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) + await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) async def close_tables(*args): """Close all table objects.""" From b605a7ec4d10cc3ec69a1735344b46e9e2d3263d Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Sun, 17 Jun 2018 13:14:29 -0700 Subject: [PATCH 07/11] Lint --- homeassistant/components/light/sisyphus.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py index c90ca181d7a043..4d5f35bc8bb347 100644 --- a/homeassistant/components/light/sisyphus.py +++ b/homeassistant/components/light/sisyphus.py @@ -37,6 +37,7 @@ def __init__(self, name, table): self._table = table async def async_added_to_hass(self): + """Add listeners after this object has been initialized.""" self._table.add_listener( lambda: self.schedule_update_ha_state(False)) @@ -52,12 +53,12 @@ def is_on(self): @property def brightness(self): - """Return the urrent brightness of the table's ring light.""" + """Return the current brightness of the table's ring light.""" return self._table.brightness * 255 @property def supported_features(self): - """Return the eatures supported by the table; i.e. brightness.""" + """Return the features supported by the table; i.e. brightness.""" return SUPPORT_BRIGHTNESS async def async_turn_off(self, **kwargs): From af670bbe647f5697ba1927abb1c266461161d066 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Tue, 3 Jul 2018 17:22:19 -0700 Subject: [PATCH 08/11] Missed one piece of feedback --- homeassistant/components/media_player/sisyphus.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index 22da7a887bc296..7ade523b4db00a 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -72,7 +72,7 @@ def name(self): @property def state(self): - """Return the urrent state of the table; sleeping maps to off.""" + """Return the current state of the table; sleeping maps to off.""" if self._table.state in ["homing", "playing"]: return STATE_PLAYING elif self._table.state == "paused": @@ -82,7 +82,8 @@ def state(self): return STATE_PAUSED elif self._table.state == "waiting": return STATE_IDLE - raise Exception("Unknown state: {0}".format(self._table.state)) + + return None @property def volume_level(self): From 383566ba339d029ce5c9e1b858e5c92a65dc16e5 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Mon, 16 Jul 2018 19:16:07 -0700 Subject: [PATCH 09/11] - Use async_added_to_hass in media player - async_schedule_update_ha_state in listeners - constants for supported features - subscripting for required keys - asyncio.wait - update to sisyphus-control with passed-in session --- homeassistant/components/light/sisyphus.py | 6 ++- .../components/media_player/sisyphus.py | 37 ++++++++----------- homeassistant/components/sisyphus.py | 16 +++++--- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py index 4d5f35bc8bb347..ded78716317161 100644 --- a/homeassistant/components/light/sisyphus.py +++ b/homeassistant/components/light/sisyphus.py @@ -14,6 +14,8 @@ DEPENDENCIES = ['sisyphus'] +SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a single Sisyphus table.""" @@ -39,7 +41,7 @@ def __init__(self, name, table): async def async_added_to_hass(self): """Add listeners after this object has been initialized.""" self._table.add_listener( - lambda: self.schedule_update_ha_state(False)) + lambda: self.async_schedule_update_ha_state(False)) @property def name(self): @@ -59,7 +61,7 @@ def brightness(self): @property def supported_features(self): """Return the features supported by the table; i.e. brightness.""" - return SUPPORT_BRIGHTNESS + return SUPPORTED_FEATURES async def async_turn_off(self, **kwargs): """Put the table to sleep.""" diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index 7ade523b4db00a..db5d823ea3fe8b 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -27,6 +27,16 @@ MEDIA_TYPE_TRACK = "sisyphus_track" +SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE \ + | SUPPORT_VOLUME_SET \ + | SUPPORT_TURN_OFF \ + | SUPPORT_TURN_ON \ + | SUPPORT_PAUSE \ + | SUPPORT_SHUFFLE_SET \ + | SUPPORT_PREVIOUS_TRACK \ + | SUPPORT_NEXT_TRACK \ + | SUPPORT_PLAY + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): @@ -52,18 +62,11 @@ def __init__(self, name, host, table): self._name = name self._host = host self._table = table - self._initialized = False - - def update(self): - """Lazily initializes the table.""" - if not self._initialized: - # We wait until update before adding the listener because - # otherwise there's a race condition by which this entity - # might not have had its hass field set, and thus - # the schedule_update_ha_state call will fail - self._table.add_listener( - lambda: self.schedule_update_ha_state(False)) - self._initialized = True + + async def async_added_to_hass(self): + """Add listeners after this object has been initialized.""" + self._table.add_listener( + lambda: self.async_schedule_update_ha_state(False)) @property def name(self): @@ -132,15 +135,7 @@ def media_content_id(self): @property def supported_features(self): """Return the features supported by this table.""" - return SUPPORT_VOLUME_MUTE \ - | SUPPORT_VOLUME_SET \ - | SUPPORT_TURN_OFF \ - | SUPPORT_TURN_ON \ - | SUPPORT_PAUSE \ - | SUPPORT_SHUFFLE_SET \ - | SUPPORT_PREVIOUS_TRACK \ - | SUPPORT_NEXT_TRACK \ - | SUPPORT_PLAY + return SUPPORTED_FEATURES @property def media_image_url(self): diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py index 85281d539d034f..dc9f9cc4c2573d 100644 --- a/homeassistant/components/sisyphus.py +++ b/homeassistant/components/sisyphus.py @@ -4,6 +4,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/sisyphus/ """ +import asyncio import logging import voluptuous as vol @@ -13,10 +14,11 @@ CONF_NAME, EVENT_HOMEASSISTANT_STOP ) +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform -REQUIREMENTS = ['sisyphus-control==2.0.0'] +REQUIREMENTS = ['sisyphus-control==2.1'] _LOGGER = logging.getLogger(__name__) @@ -42,10 +44,11 @@ async def async_setup(hass, config): from sisyphus_control import Table tables = hass.data.setdefault(DATA_SISYPHUS, {}) table_configs = config.get(DOMAIN) + session = async_get_clientsession(hass) async def add_table(host, name=None): """Add platforms for a single table with the given hostname.""" - table = await Table.connect(host) + table = await Table.connect(host, session) if name is None: name = table.name tables[name] = table @@ -64,16 +67,17 @@ async def add_table(host, name=None): )) if isinstance(table_configs, dict): # AUTODETECT_SCHEMA - for ip_address in await Table.find_table_ips(): + for ip_address in await Table.find_table_ips(session): await add_table(ip_address) else: # TABLES_SCHEMA for conf in table_configs: - await add_table(conf.get(CONF_HOST), conf.get(CONF_NAME)) + await add_table(conf[CONF_HOST], conf[CONF_NAME]) async def close_tables(*args): """Close all table objects.""" - for table in tables.values(): - await table.close() + tasks = [table.close() for table in tables.values()] + if tasks: + await asyncio.wait(tasks) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables) From bf3ac4dc90060f681cd51750d880c21bf15eda2e Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Thu, 26 Jul 2018 16:56:01 -0700 Subject: [PATCH 10/11] Update requirements --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index 151739cb5e787c..6fe703cbe6d565 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1244,7 +1244,7 @@ simplepush==1.1.4 simplisafe-python==1.0.5 # homeassistant.components.sisyphus -sisyphus-control==2.0.0 +sisyphus-control==2.1 # homeassistant.components.skybell skybellpy==0.1.2 From b325ea92439c1724c0f0941493925c3eb627b9ba Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Sat, 28 Jul 2018 21:21:54 -0700 Subject: [PATCH 11/11] lint --- homeassistant/components/media_player/sisyphus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py index db5d823ea3fe8b..9a94da158a19da 100644 --- a/homeassistant/components/media_player/sisyphus.py +++ b/homeassistant/components/media_player/sisyphus.py @@ -78,12 +78,12 @@ def state(self): """Return the current state of the table; sleeping maps to off.""" if self._table.state in ["homing", "playing"]: return STATE_PLAYING - elif self._table.state == "paused": + if self._table.state == "paused": if self._table.is_sleeping: return STATE_OFF return STATE_PAUSED - elif self._table.state == "waiting": + if self._table.state == "waiting": return STATE_IDLE return None