-
-
Notifications
You must be signed in to change notification settings - Fork 29.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a component for Sisyphus Kinetic Art Tables (#14472)
* 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. * Fix lints * Docstrings, other lints * More lints * Yet more. * Feedback * Lint * Missed one piece of feedback * - 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 * Update requirements * lint
- Loading branch information
1 parent
93d6fb8
commit a2b793c
Showing
5 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
""" | ||
Support for the light on the Sisyphus Kinetic Art Table. | ||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/light.sisyphus/ | ||
""" | ||
import logging | ||
|
||
from homeassistant.const import CONF_NAME | ||
from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light | ||
from homeassistant.components.sisyphus import DATA_SISYPHUS | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DEPENDENCIES = ['sisyphus'] | ||
|
||
SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS | ||
|
||
|
||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
"""Set up a single Sisyphus table.""" | ||
name = discovery_info[CONF_NAME] | ||
add_devices( | ||
[SisyphusLight(name, hass.data[DATA_SISYPHUS][name])], | ||
update_before_add=True) | ||
|
||
|
||
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 | ||
|
||
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): | ||
"""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 current brightness of the table's ring light.""" | ||
return self._table.brightness * 255 | ||
|
||
@property | ||
def supported_features(self): | ||
"""Return the features supported by the table; i.e. brightness.""" | ||
return SUPPORTED_FEATURES | ||
|
||
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") | ||
|
||
if "brightness" in kwargs: | ||
await self._table.set_brightness(kwargs["brightness"] / 255.0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
""" | ||
Support for track controls on the Sisyphus Kinetic Art Table. | ||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/media_player.sisyphus/ | ||
""" | ||
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" | ||
|
||
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): | ||
"""Set up a media player entity for a Sisyphus table.""" | ||
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): | ||
"""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 | ||
|
||
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): | ||
"""Return the name of the table.""" | ||
return self._name | ||
|
||
@property | ||
def state(self): | ||
"""Return the current state of the table; sleeping maps to off.""" | ||
if self._table.state in ["homing", "playing"]: | ||
return STATE_PLAYING | ||
if self._table.state == "paused": | ||
if self._table.is_sleeping: | ||
return STATE_OFF | ||
|
||
return STATE_PAUSED | ||
if self._table.state == "waiting": | ||
return STATE_IDLE | ||
|
||
return None | ||
|
||
@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.""" | ||
return SUPPORTED_FEATURES | ||
|
||
@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( | ||
Track.ThumbnailSize.LARGE) | ||
|
||
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( | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
""" | ||
Support for controlling Sisyphus Kinetic Art Tables. | ||
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 | ||
|
||
from homeassistant.const import ( | ||
CONF_HOST, | ||
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.1'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_SISYPHUS = 'sisyphus' | ||
DOMAIN = 'sisyphus' | ||
|
||
AUTODETECT_SCHEMA = vol.Schema({}) | ||
|
||
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.Schema({ | ||
DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA), | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
|
||
async def async_setup(hass, config): | ||
"""Set up the sisyphus component.""" | ||
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, session) | ||
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', DOMAIN, { | ||
CONF_NAME: name, | ||
}, config | ||
)) | ||
hass.async_add_job(async_load_platform( | ||
hass, 'media_player', DOMAIN, { | ||
CONF_NAME: name, | ||
CONF_HOST: host, | ||
}, config | ||
)) | ||
|
||
if isinstance(table_configs, dict): # AUTODETECT_SCHEMA | ||
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[CONF_HOST], conf[CONF_NAME]) | ||
|
||
async def close_tables(*args): | ||
"""Close all table objects.""" | ||
tasks = [table.close() for table in tables.values()] | ||
if tasks: | ||
await asyncio.wait(tasks) | ||
|
||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables) | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters