Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 3 additions & 58 deletions homeassistant/components/device_tracker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import logging
from typing import Any, List, Sequence, Callable

import aiohttp
import async_timeout
import voluptuous as vol

from homeassistant.setup import async_prepare_setup_platform
Expand All @@ -19,7 +17,6 @@
from homeassistant.components import group, zone
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
Expand Down Expand Up @@ -76,7 +73,6 @@
ATTR_MAC = 'mac'
ATTR_NAME = 'name'
ATTR_SOURCE_TYPE = 'source_type'
ATTR_VENDOR = 'vendor'
ATTR_CONSIDER_HOME = 'consider_home'

SOURCE_TYPE_GPS = 'gps'
Expand Down Expand Up @@ -328,14 +324,10 @@ def async_see(
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id])

# lookup mac vendor string to be stored in config
yield from device.set_vendor_for_mac()

self.hass.bus.async_fire(EVENT_NEW_DEVICE, {
ATTR_ENTITY_ID: device.entity_id,
ATTR_HOST_NAME: device.host_name,
ATTR_MAC: device.mac,
ATTR_VENDOR: device.vendor,
})

# update known_devices.yaml
Expand Down Expand Up @@ -413,7 +405,6 @@ class Device(Entity):
consider_home = None # type: dt_util.dt.timedelta
battery = None # type: int
attributes = None # type: dict
vendor = None # type: str
icon = None # type: str

# Track if the last update of this device was HOME.
Expand All @@ -423,7 +414,7 @@ class Device(Entity):
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str = None,
picture: str = None, gravatar: str = None, icon: str = None,
hide_if_away: bool = False, vendor: str = None) -> None:
hide_if_away: bool = False) -> None:
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
Expand Down Expand Up @@ -451,7 +442,6 @@ def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
self.icon = icon

self.away_hide = hide_if_away
self.vendor = vendor

self.source_type = None

Expand Down Expand Up @@ -567,51 +557,6 @@ def async_update(self):
self._state = STATE_HOME
self.last_update_home = True

@asyncio.coroutine
def set_vendor_for_mac(self):
"""Set vendor string using api.macvendors.com."""
self.vendor = yield from self.get_vendor_for_mac()

@asyncio.coroutine
def get_vendor_for_mac(self):
"""Try to find the vendor string for a given MAC address."""
if not self.mac:
return None

if '_' in self.mac:
_, mac = self.mac.split('_', 1)
else:
mac = self.mac

if not len(mac.split(':')) == 6:
return 'unknown'

# We only need the first 3 bytes of the MAC for a lookup
# this improves somewhat on privacy
oui_bytes = mac.split(':')[0:3]
# bytes like 00 get truncates to 0, API needs full bytes
oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes])
url = 'http://api.macvendors.com/' + oui
try:
websession = async_get_clientsession(self.hass)

with async_timeout.timeout(5, loop=self.hass.loop):
resp = yield from websession.get(url)
# mac vendor found, response is the string
if resp.status == 200:
vendor_string = yield from resp.text()
return vendor_string
# If vendor is not known to the API (404) or there
# was a failure during the lookup (500); set vendor
# to something other then None to prevent retry
# as the value is only relevant when it is to be stored
# in the 'known_devices.yaml' file which only happens
# the first time the device is seen.
return 'unknown'
except (asyncio.TimeoutError, aiohttp.ClientError):
# Same as above
return 'unknown'

@asyncio.coroutine
def async_added_to_hass(self):
"""Add an entity."""
Expand Down Expand Up @@ -685,7 +630,6 @@ def async_load_config(path: str, hass: HomeAssistantType,
vol.Optional('picture', default=None): vol.Any(None, cv.string),
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional('vendor', default=None): vol.Any(None, cv.string),
})
try:
result = []
Expand All @@ -697,6 +641,8 @@ def async_load_config(path: str, hass: HomeAssistantType,
return []

for dev_id, device in devices.items():
# Deprecated option. We just ignore it to avoid breaking change
device.pop('vendor', None)
try:
device = dev_schema(device)
device['dev_id'] = cv.slugify(dev_id)
Expand Down Expand Up @@ -772,7 +718,6 @@ def update_config(path: str, dev_id: str, device: Device):
'picture': device.config_picture,
'track': device.track,
CONF_AWAY_HIDE: device.away_hide,
'vendor': device.vendor,
}}
out.write('\n')
out.write(dump(device))
Expand Down
128 changes: 2 additions & 126 deletions tests/components/device_tracker/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@

from tests.common import (
get_test_home_assistant, fire_time_changed,
patch_yaml_files, assert_setup_component, mock_restore_cache, mock_coro)

from ...test_util.aiohttp import mock_aiohttp_client
patch_yaml_files, assert_setup_component, mock_restore_cache)

TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}

Expand Down Expand Up @@ -111,7 +109,6 @@ def test_reading_yaml_config(self):
self.assertEqual(device.config_picture, config.config_picture)
self.assertEqual(device.away_hide, config.away_hide)
self.assertEqual(device.consider_home, config.consider_home)
self.assertEqual(device.vendor, config.vendor)
self.assertEqual(device.icon, config.icon)

# pylint: disable=invalid-name
Expand Down Expand Up @@ -173,124 +170,6 @@ def test_gravatar_and_picture(self):
"55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar")
self.assertEqual(device.config_picture, gravatar_url)

def test_mac_vendor_lookup(self):
"""Test if vendor string is lookup on macvendors API."""
mac = 'B8:27:EB:00:00:00'
vendor_string = 'Raspberry Pi Foundation'

device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, 'test', mac, 'Test name')

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
text=vendor_string)

run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()
assert aioclient_mock.call_count == 1

self.assertEqual(device.vendor, vendor_string)

def test_mac_vendor_mac_formats(self):
"""Verify all variations of MAC addresses are handled correctly."""
vendor_string = 'Raspberry Pi Foundation'

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
text=vendor_string)
aioclient_mock.get('http://api.macvendors.com/00:27:eb',
text=vendor_string)

mac = 'B8:27:EB:00:00:00'
device = device_tracker.Device(
self.hass, timedelta(seconds=180),
True, 'test', mac, 'Test name')
run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()
self.assertEqual(device.vendor, vendor_string)

mac = '0:27:EB:00:00:00'
device = device_tracker.Device(
self.hass, timedelta(seconds=180),
True, 'test', mac, 'Test name')
run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()
self.assertEqual(device.vendor, vendor_string)

mac = 'PREFIXED_B8:27:EB:00:00:00'
device = device_tracker.Device(
self.hass, timedelta(seconds=180),
True, 'test', mac, 'Test name')
run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()
self.assertEqual(device.vendor, vendor_string)

def test_mac_vendor_lookup_unknown(self):
"""Prevent another mac vendor lookup if was not found first time."""
mac = 'B8:27:EB:00:00:00'

device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, 'test', mac, 'Test name')

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
status=404)

run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()

self.assertEqual(device.vendor, 'unknown')

def test_mac_vendor_lookup_error(self):
"""Prevent another lookup if failure during API call."""
mac = 'B8:27:EB:00:00:00'

device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, 'test', mac, 'Test name')

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
status=500)

run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()

self.assertEqual(device.vendor, 'unknown')

def test_mac_vendor_lookup_exception(self):
"""Prevent another lookup if exception during API call."""
mac = 'B8:27:EB:00:00:00'

device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, 'test', mac, 'Test name')

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
exc=asyncio.TimeoutError())

run_coroutine_threadsafe(device.set_vendor_for_mac(),
self.hass.loop).result()

self.assertEqual(device.vendor, 'unknown')

def test_mac_vendor_lookup_on_see(self):
"""Test if macvendor is looked up when device is seen."""
mac = 'B8:27:EB:00:00:00'
vendor_string = 'Raspberry Pi Foundation'

tracker = device_tracker.DeviceTracker(
self.hass, timedelta(seconds=60), 0, {}, [])

with mock_aiohttp_client() as aioclient_mock:
aioclient_mock.get('http://api.macvendors.com/b8:27:eb',
text=vendor_string)

run_coroutine_threadsafe(
tracker.async_see(mac=mac), self.hass.loop).result()
assert aioclient_mock.call_count == 1, \
'No http request for macvendor made!'
self.assertEqual(tracker.devices['b827eb000000'].vendor, vendor_string)

@patch(
'homeassistant.components.device_tracker.DeviceTracker.see')
@patch(
Expand Down Expand Up @@ -463,7 +342,6 @@ def listener(event):
'entity_id': 'device_tracker.hello',
'host_name': 'hello',
'mac': 'MAC_1',
'vendor': 'unknown',
}

# pylint: disable=invalid-name
Expand Down Expand Up @@ -495,9 +373,7 @@ def test_not_allow_invalid_dev_id(self):
timedelta(seconds=0))
assert len(config) == 0

@patch('homeassistant.components.device_tracker.Device'
'.set_vendor_for_mac', return_value=mock_coro())
def test_see_state(self, mock_set_vendor):
def test_see_state(self):
"""Test device tracker see records state correctly."""
self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN,
TEST_PLATFORM))
Expand Down
4 changes: 1 addition & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,5 @@ async def mock_update_config(path, id, entity):
), patch(
'homeassistant.components.device_tracker.async_load_config',
side_effect=lambda *args: mock_coro(devices)
), patch('homeassistant.components.device_tracker'
'.Device.set_vendor_for_mac'):

):
yield devices