-
-
Notifications
You must be signed in to change notification settings - Fork 38k
Move Broadlink services to component #21465
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
Merged
MartinHjelmare
merged 7 commits into
home-assistant:dev
from
elupus:broadlink_component
Apr 12, 2019
Merged
Changes from 4 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c6e64f6
Register services in broadlink domain
elupus 1672d71
Add tests for broadlink services
elupus 2cfdedf
Resolve review comments
elupus 0c5bfcb
One more review fix
elupus ef97cb8
Restore auth retry
elupus 2324d3b
Drop unused constants
elupus 1161f15
Fix flake8 errors
elupus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
| @@ -1 +1,86 @@ | ||
| """The broadlink component.""" | ||
| import asyncio | ||
| from base64 import b64decode, b64encode | ||
| import logging | ||
| import re | ||
|
|
||
| from datetime import timedelta | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.const import CONF_HOST | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.util.dt import utcnow | ||
|
|
||
| from .const import CONF_PACKET, DOMAIN, SERVICE_LEARN, SERVICE_SEND | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def ipv4_address(value): | ||
| """Validate an ipv4 address.""" | ||
| regex = re.compile(r'^\d+\.\d+\.\d+\.\d+$') | ||
| if not regex.match(value): | ||
| raise vol.Invalid('Invalid Ipv4 address, expected a.b.c.d') | ||
| return value | ||
|
|
||
|
|
||
| def data_packet(value): | ||
| """Decode a data packet given for broadlink.""" | ||
| return b64decode(cv.string(value)) | ||
|
|
||
|
|
||
| SERVICE_SEND_SCHEMA = vol.Schema({ | ||
| vol.Required(CONF_HOST): ipv4_address, | ||
| vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]) | ||
| }) | ||
|
|
||
| SERVICE_LEARN_SCHEMA = vol.Schema({ | ||
| vol.Required(CONF_HOST): ipv4_address, | ||
| }) | ||
|
|
||
|
|
||
| def async_setup_service(hass, host, device): | ||
| """Register a device for given host for use in services.""" | ||
| hass.data.setdefault(DOMAIN, {})[host] = device | ||
|
|
||
| if not hass.services.has_service(DOMAIN, SERVICE_LEARN): | ||
|
|
||
| async def _learn_command(call): | ||
| """Learn a packet from remote.""" | ||
| device = hass.data[DOMAIN][call.data[CONF_HOST]] | ||
| await hass.async_add_executor_job(device.enter_learning) | ||
|
|
||
| _LOGGER.info("Press the key you want Home Assistant to learn") | ||
| start_time = utcnow() | ||
| while (utcnow() - start_time) < timedelta(seconds=20): | ||
| packet = await hass.async_add_executor_job( | ||
| device.check_data) | ||
| if packet: | ||
| data = b64encode(packet).decode('utf8') | ||
| log_msg = "Received packet is: {}".\ | ||
| format(data) | ||
| _LOGGER.info(log_msg) | ||
| hass.components.persistent_notification.async_create( | ||
| log_msg, title='Broadlink switch') | ||
| return | ||
| await asyncio.sleep(1) | ||
| _LOGGER.error("No signal was received") | ||
| hass.components.persistent_notification.async_create( | ||
| "No signal was received", title='Broadlink switch') | ||
|
|
||
| hass.services.async_register( | ||
| DOMAIN, SERVICE_LEARN, _learn_command, | ||
| schema=SERVICE_LEARN_SCHEMA) | ||
|
|
||
| if not hass.services.has_service(DOMAIN, SERVICE_SEND): | ||
|
|
||
| async def _send_packet(call): | ||
| """Send a packet.""" | ||
| device = hass.data[DOMAIN][call.data[CONF_HOST]] | ||
| packets = call.data[CONF_PACKET] | ||
| for packet in packets: | ||
| await hass.async_add_executor_job( | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
| device.send_data, packet) | ||
| hass.services.async_register( | ||
| DOMAIN, SERVICE_SEND, _send_packet, | ||
| schema=SERVICE_SEND_SCHEMA) | ||
This file contains hidden or 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,7 @@ | ||
| """Constants for broadlink platform.""" | ||
| CONF_PACKET = 'packet' | ||
|
|
||
| DOMAIN = 'broadlink' | ||
|
|
||
| SERVICE_LEARN = 'learn' | ||
| SERVICE_SEND = 'send' |
This file contains hidden or 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,9 @@ | ||
| send: | ||
| description: Send a raw packet to device. | ||
| fields: | ||
| host: {description: IP address of device to send packet via. This must be an already configured device., example: "192.168.0.1"} | ||
| packet: {description: base64 encoded packet.} | ||
| learn: | ||
| description: Learn a IR or RF code from remote. | ||
| fields: | ||
| host: {description: IP address of device to send packet via. This must be an already configured device., example: "192.168.0.1"} |
This file contains hidden or 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 hidden or 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 @@ | ||
| """The tests for broadlink platforms.""" |
This file contains hidden or 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,117 @@ | ||
| """The tests for the broadlink component.""" | ||
| from datetime import timedelta | ||
| from base64 import b64decode | ||
| from unittest.mock import MagicMock, patch, call | ||
|
|
||
| import pytest | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.util.dt import utcnow | ||
| from homeassistant.components.broadlink import async_setup_service | ||
| from homeassistant.components.broadlink.const import ( | ||
|
elupus marked this conversation as resolved.
|
||
| DOMAIN, SERVICE_LEARN, SERVICE_SEND) | ||
|
|
||
| DUMMY_IR_PACKET = ("JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ" | ||
| "OhEUERQRORE5EBURFBA6EBUQOhE5EBUQFRA6EDoRFBEADQUAAA==") | ||
| DUMMY_HOST = "192.168.0.2" | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
|
elupus marked this conversation as resolved.
|
||
| def dummy_broadlink(): | ||
| """Mock broadlink module so we don't have that dependency on tests.""" | ||
| broadlink = MagicMock() | ||
| with patch.dict('sys.modules', { | ||
| 'broadlink': broadlink, | ||
| }): | ||
| yield broadlink | ||
|
|
||
|
|
||
| async def test_send(hass): | ||
| """Test send service.""" | ||
| mock_device = MagicMock() | ||
| mock_device.send_data.return_value = None | ||
|
|
||
| async_setup_service(hass, DUMMY_HOST, mock_device) | ||
| await hass.async_block_till_done() | ||
|
|
||
| await hass.services.async_call(DOMAIN, SERVICE_SEND, { | ||
| "host": DUMMY_HOST, | ||
| "packet": (DUMMY_IR_PACKET) | ||
| }) | ||
| await hass.async_block_till_done() | ||
|
|
||
| assert mock_device.send_data.call_count == 1 | ||
| assert mock_device.send_data.call_args == call( | ||
| b64decode(DUMMY_IR_PACKET)) | ||
|
|
||
|
|
||
| async def test_learn(hass): | ||
| """Test learn service.""" | ||
| mock_device = MagicMock() | ||
| mock_device.enter_learning.return_value = None | ||
| mock_device.check_data.return_value = b64decode(DUMMY_IR_PACKET) | ||
|
|
||
| with patch.object(hass.components.persistent_notification, | ||
| 'async_create') as mock_create: | ||
|
|
||
| async_setup_service(hass, DUMMY_HOST, mock_device) | ||
| await hass.async_block_till_done() | ||
|
|
||
| await hass.services.async_call(DOMAIN, SERVICE_LEARN, { | ||
| "host": DUMMY_HOST, | ||
| }) | ||
| await hass.async_block_till_done() | ||
|
|
||
| assert mock_device.enter_learning.call_count == 1 | ||
| assert mock_device.enter_learning.call_args == call() | ||
|
|
||
| assert mock_create.call_count == 1 | ||
| assert mock_create.call_args == call( | ||
| "Received packet is: {}".format(DUMMY_IR_PACKET), | ||
| title='Broadlink switch') | ||
|
|
||
|
|
||
| async def test_learn_timeout(hass): | ||
|
elupus marked this conversation as resolved.
|
||
| """Test learn service.""" | ||
| mock_device = MagicMock() | ||
| mock_device.enter_learning.return_value = None | ||
| mock_device.check_data.return_value = None | ||
|
|
||
| async_setup_service(hass, DUMMY_HOST, mock_device) | ||
| await hass.async_block_till_done() | ||
|
|
||
| now = utcnow() | ||
|
|
||
| with patch.object(hass.components.persistent_notification, | ||
| 'async_create') as mock_create, \ | ||
| patch('homeassistant.components.broadlink.utcnow') as mock_utcnow: | ||
|
|
||
| mock_utcnow.side_effect = [now, now + timedelta(20)] | ||
|
|
||
| await hass.services.async_call(DOMAIN, SERVICE_LEARN, { | ||
| "host": DUMMY_HOST, | ||
| }) | ||
| await hass.async_block_till_done() | ||
|
|
||
| assert mock_device.enter_learning.call_count == 1 | ||
| assert mock_device.enter_learning.call_args == call() | ||
|
|
||
| assert mock_create.call_count == 1 | ||
| assert mock_create.call_args == call( | ||
| "No signal was received", | ||
| title='Broadlink switch') | ||
|
|
||
|
|
||
| async def test_ipv4(): | ||
|
elupus marked this conversation as resolved.
|
||
| """Test ipv4 parsing.""" | ||
| from homeassistant.components.broadlink import ipv4_address | ||
|
|
||
| schema = vol.Schema(ipv4_address) | ||
|
|
||
| for value in ('invalid', '1', '192', '192.168', | ||
| '192.168.0', '192.168.0.A'): | ||
| with pytest.raises(vol.MultipleInvalid): | ||
| schema(value) | ||
|
|
||
| for value in ('192.168.0.1', '10.0.0.1'): | ||
| schema(value) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.