-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add light control to Sleepiq integration #31322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,68 @@ | ||||||
| """ | ||||||
| Support for switching lights on Sleep Number beds off and on. | ||||||
|
|
||||||
| Creates entities for both night stand lamps and the two | ||||||
| night light (underbed) lights. Night lights cannot be | ||||||
| controlled separately by the stock remote but can be with | ||||||
| this integration. | ||||||
| """ | ||||||
|
|
||||||
| import logging | ||||||
|
|
||||||
| from homeassistant.components import sleepiq | ||||||
|
|
||||||
| _LOGGER = logging.getLogger(__name__) | ||||||
|
|
||||||
| ICON = "mdi:lamp" | ||||||
|
|
||||||
|
|
||||||
| def setup_platform(hass, config, add_entities, discovery_info=None): | ||||||
| """Set up the SleepIQ lights.""" | ||||||
| if discovery_info is None: | ||||||
| return | ||||||
|
|
||||||
| data = sleepiq.DATA | ||||||
| data.update() | ||||||
|
|
||||||
| dev = list() | ||||||
|
|
||||||
| for bed_id, bed in data.beds.items(): # pylint: disable=unused-variable | ||||||
| for light in sleepiq.BED_LIGHTS: | ||||||
| dev.append(SleepNumberLight(data, bed_id, light)) | ||||||
| add_entities(dev) | ||||||
|
|
||||||
|
|
||||||
| class SleepNumberLight(sleepiq.SleepIQLight): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The base class for this should be a Light |
||||||
| """Representation of a SleepIQ Light.""" | ||||||
|
|
||||||
| def __init__(self, sleepiq_data, bed_id, light): | ||||||
| """Initialize the light.""" | ||||||
| sleepiq.SleepIQLight.__init__(self, sleepiq_data, bed_id, light) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove this and pass in what you need from |
||||||
|
|
||||||
| self._light = light | ||||||
| self._state = False | ||||||
| self.type = sleepiq.SLEEP_NUMBER | ||||||
| self._bed_id = bed_id | ||||||
|
|
||||||
| @property | ||||||
| def state(self): | ||||||
| """Return state of light.""" | ||||||
| return "on" if self._state else "off" | ||||||
|
|
||||||
| @property | ||||||
| def icon(self): | ||||||
| """Icon to use in the frontend, if any.""" | ||||||
| return ICON | ||||||
|
|
||||||
| async def async_turn_on(self): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This integration isn't async so you'll need to avoid doing I/O in async. |
||||||
| """Instruct the light to turn on.""" | ||||||
| self.turn_on() | ||||||
|
|
||||||
| async def async_turn_off(self): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| """Instruct the light to turn off.""" | ||||||
| self.turn_off() | ||||||
|
|
||||||
| def update(self): | ||||||
| """Get the latest data from SleepIQ and updates the states.""" | ||||||
| sleepiq.SleepIQLight.update(self) | ||||||
| self._state = self.is_on | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| """The tests for SleepIQ light platform.""" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for adding tests!!! Please remove the classes from the tests. A good example of how tests should be written is |
||
| import asyncio | ||
| import unittest | ||
| from unittest.mock import MagicMock | ||
|
|
||
| import requests_mock | ||
|
|
||
| import homeassistant.components.sleepiq.light as sleepiq | ||
| from homeassistant.setup import setup_component | ||
|
|
||
| from tests.common import get_test_home_assistant | ||
| from tests.components.sleepiq.test_init import mock_responses | ||
|
|
||
|
|
||
| class TestSleepIQLightSetup(unittest.TestCase): | ||
| """Tests the SleepIQ Light platform.""" | ||
|
|
||
| DEVICES = [] | ||
|
|
||
| def add_entities(self, devices): | ||
| """Mock add devices.""" | ||
| for device in devices: | ||
| self.DEVICES.append(device) | ||
|
|
||
| def setUp(self): | ||
| """Initialize values for this testcase class.""" | ||
| self.hass = get_test_home_assistant() | ||
| self.username = "foo" | ||
| self.password = "bar" | ||
| self.config = {"username": self.username, "password": self.password} | ||
| self.DEVICES = [] | ||
|
|
||
| def tearDown(self): # pylint: disable=invalid-name | ||
| """Stop everything that was started.""" | ||
| self.hass.stop() | ||
|
|
||
| @requests_mock.Mocker() | ||
| def test_setup(self, mock): | ||
| """Test for successfully setting up the SleepIQ platform.""" | ||
| mock_responses(mock) | ||
|
|
||
| assert setup_component(self.hass, "sleepiq", {"sleepiq": self.config}) | ||
|
|
||
| sleepiq.setup_platform(self.hass, self.config, self.add_entities, None) | ||
| assert not len(self.DEVICES) | ||
|
|
||
| sleepiq.setup_platform(self.hass, self.config, self.add_entities, MagicMock()) | ||
| assert 4 == len(self.DEVICES) | ||
|
|
||
| left_night_stand = self.DEVICES[0] | ||
| assert "SleepNumber ILE Left Night Stand" == left_night_stand.name | ||
| assert "off" == left_night_stand.state | ||
|
|
||
| right_night_stand = self.DEVICES[1] | ||
| assert "SleepNumber ILE Right Night Stand" == right_night_stand.name | ||
| assert "off" == right_night_stand.state | ||
|
|
||
| right_night_light = self.DEVICES[2] | ||
| assert "SleepNumber ILE Right Night Light" == right_night_light.name | ||
| assert "off" == right_night_light.state | ||
|
|
||
| left_night_light = self.DEVICES[3] | ||
| assert "SleepNumber ILE Left Night Light" == left_night_light.name | ||
| assert "off" == left_night_light.state | ||
|
|
||
|
|
||
| class TestSleepIQLight(unittest.TestCase): | ||
| """Tests for functionality of the lights platform.""" | ||
|
|
||
| DEVICES = [] | ||
|
|
||
| def add_entities(self, devices): | ||
| """Mock add devices.""" | ||
| for device in devices: | ||
| self.DEVICES.append(device) | ||
|
|
||
| def setUp(self): | ||
| """Initialize values for this testcase class.""" | ||
| self.hass = get_test_home_assistant() | ||
| self.username = "foo" | ||
| self.password = "bar" | ||
| self.config = {"username": self.username, "password": self.password} | ||
| self.DEVICES = [] | ||
|
|
||
| def tearDown(self): # pylint: disable=invalid-name | ||
| """Stop everything that was started.""" | ||
| self.hass.stop() | ||
|
|
||
| @requests_mock.Mocker() | ||
| def test_turn_on(self, mock): | ||
| """Test turning on a light.""" | ||
| mock_responses(mock) | ||
|
|
||
| sleepiq.setup_platform(self.hass, self.config, self.add_entities, MagicMock()) | ||
|
|
||
| left_night_stand = self.DEVICES[0] | ||
| asyncio.run_coroutine_threadsafe( | ||
| sleepiq.SleepNumberLight.async_turn_on(left_night_stand), self.hass.loop | ||
| ) | ||
|
|
||
| assert left_night_stand.state is not None | ||
|
|
||
| @requests_mock.Mocker() | ||
| def test_turn_off(self, mock): | ||
| """Test turning off a light.""" | ||
| mock_responses(mock) | ||
|
|
||
| sleepiq.setup_platform(self.hass, self.config, self.add_entities, MagicMock()) | ||
|
|
||
| left_night_stand = self.DEVICES[0] | ||
|
|
||
| asyncio.run_coroutine_threadsafe( | ||
| sleepiq.SleepNumberLight.async_turn_off(left_night_stand), self.hass.loop | ||
| ) | ||
|
|
||
| assert left_night_stand.state == "off" | ||
|
|
||
| @requests_mock.Mocker() | ||
| def test_update(self, mock): | ||
| """Test that the update function leaves the lights with a measureable state.""" | ||
| mock_responses(mock) | ||
|
|
||
| sleepiq.setup_platform(self.hass, self.config, self.add_entities, MagicMock()) | ||
|
|
||
| left_night_stand = self.DEVICES[0] | ||
| sleepiq.SleepNumberLight.update(left_night_stand) | ||
| assert left_night_stand.state is not None | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "bedId": "-31", | ||
| "outlet": 1, | ||
| "setting": 0, | ||
| "timer": null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "bedId": "-31", | ||
| "outlet": 1, | ||
| "setting": 0, | ||
| "timer": null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "bedId": "-31", | ||
| "outlet": 1, | ||
| "setting": 0, | ||
| "timer": null | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this will already be up to date in init so you shouldn't need to update again here.
I realize this is existing in init but we shouldn't use globals. You can fix this by storing the data in
Then you can access it in setup_platform
DATAshould probably be renamed as well.