mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Remove vendor lookup for mac addresses (#13788)
* Remove vendor lookup for mac addresses * Fix tests
This commit is contained in:
parent
7595401dcb
commit
5ac52b74e0
@ -9,8 +9,6 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, List, Sequence, Callable
|
from typing import Any, List, Sequence, Callable
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import async_timeout
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.setup import async_prepare_setup_platform
|
from homeassistant.setup import async_prepare_setup_platform
|
||||||
@ -19,7 +17,6 @@ from homeassistant.loader import bind_hass
|
|||||||
from homeassistant.components import group, zone
|
from homeassistant.components import group, zone
|
||||||
from homeassistant.config import load_yaml_config_file, async_log_exception
|
from homeassistant.config import load_yaml_config_file, async_log_exception
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
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 import config_per_platform, discovery
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
@ -76,7 +73,6 @@ ATTR_LOCATION_NAME = 'location_name'
|
|||||||
ATTR_MAC = 'mac'
|
ATTR_MAC = 'mac'
|
||||||
ATTR_NAME = 'name'
|
ATTR_NAME = 'name'
|
||||||
ATTR_SOURCE_TYPE = 'source_type'
|
ATTR_SOURCE_TYPE = 'source_type'
|
||||||
ATTR_VENDOR = 'vendor'
|
|
||||||
ATTR_CONSIDER_HOME = 'consider_home'
|
ATTR_CONSIDER_HOME = 'consider_home'
|
||||||
|
|
||||||
SOURCE_TYPE_GPS = 'gps'
|
SOURCE_TYPE_GPS = 'gps'
|
||||||
@ -328,14 +324,10 @@ class DeviceTracker(object):
|
|||||||
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
|
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
|
||||||
name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id])
|
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, {
|
self.hass.bus.async_fire(EVENT_NEW_DEVICE, {
|
||||||
ATTR_ENTITY_ID: device.entity_id,
|
ATTR_ENTITY_ID: device.entity_id,
|
||||||
ATTR_HOST_NAME: device.host_name,
|
ATTR_HOST_NAME: device.host_name,
|
||||||
ATTR_MAC: device.mac,
|
ATTR_MAC: device.mac,
|
||||||
ATTR_VENDOR: device.vendor,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# update known_devices.yaml
|
# update known_devices.yaml
|
||||||
@ -413,7 +405,6 @@ class Device(Entity):
|
|||||||
consider_home = None # type: dt_util.dt.timedelta
|
consider_home = None # type: dt_util.dt.timedelta
|
||||||
battery = None # type: int
|
battery = None # type: int
|
||||||
attributes = None # type: dict
|
attributes = None # type: dict
|
||||||
vendor = None # type: str
|
|
||||||
icon = None # type: str
|
icon = None # type: str
|
||||||
|
|
||||||
# Track if the last update of this device was HOME.
|
# Track if the last update of this device was HOME.
|
||||||
@ -423,7 +414,7 @@ class Device(Entity):
|
|||||||
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
|
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
|
||||||
track: bool, dev_id: str, mac: str, name: str = None,
|
track: bool, dev_id: str, mac: str, name: str = None,
|
||||||
picture: str = None, gravatar: str = None, icon: 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."""
|
"""Initialize a device."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
||||||
@ -451,7 +442,6 @@ class Device(Entity):
|
|||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
|
||||||
self.away_hide = hide_if_away
|
self.away_hide = hide_if_away
|
||||||
self.vendor = vendor
|
|
||||||
|
|
||||||
self.source_type = None
|
self.source_type = None
|
||||||
|
|
||||||
@ -567,51 +557,6 @@ class Device(Entity):
|
|||||||
self._state = STATE_HOME
|
self._state = STATE_HOME
|
||||||
self.last_update_home = True
|
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
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self):
|
def async_added_to_hass(self):
|
||||||
"""Add an entity."""
|
"""Add an entity."""
|
||||||
@ -685,7 +630,6 @@ def async_load_config(path: str, hass: HomeAssistantType,
|
|||||||
vol.Optional('picture', default=None): vol.Any(None, cv.string),
|
vol.Optional('picture', default=None): vol.Any(None, cv.string),
|
||||||
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
|
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
|
||||||
cv.time_period, cv.positive_timedelta),
|
cv.time_period, cv.positive_timedelta),
|
||||||
vol.Optional('vendor', default=None): vol.Any(None, cv.string),
|
|
||||||
})
|
})
|
||||||
try:
|
try:
|
||||||
result = []
|
result = []
|
||||||
@ -697,6 +641,8 @@ def async_load_config(path: str, hass: HomeAssistantType,
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
for dev_id, device in devices.items():
|
for dev_id, device in devices.items():
|
||||||
|
# Deprecated option. We just ignore it to avoid breaking change
|
||||||
|
device.pop('vendor', None)
|
||||||
try:
|
try:
|
||||||
device = dev_schema(device)
|
device = dev_schema(device)
|
||||||
device['dev_id'] = cv.slugify(dev_id)
|
device['dev_id'] = cv.slugify(dev_id)
|
||||||
@ -772,7 +718,6 @@ def update_config(path: str, dev_id: str, device: Device):
|
|||||||
'picture': device.config_picture,
|
'picture': device.config_picture,
|
||||||
'track': device.track,
|
'track': device.track,
|
||||||
CONF_AWAY_HIDE: device.away_hide,
|
CONF_AWAY_HIDE: device.away_hide,
|
||||||
'vendor': device.vendor,
|
|
||||||
}}
|
}}
|
||||||
out.write('\n')
|
out.write('\n')
|
||||||
out.write(dump(device))
|
out.write(dump(device))
|
||||||
|
@ -24,9 +24,7 @@ from homeassistant.remote import JSONEncoder
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
get_test_home_assistant, fire_time_changed,
|
get_test_home_assistant, fire_time_changed,
|
||||||
patch_yaml_files, assert_setup_component, mock_restore_cache, mock_coro)
|
patch_yaml_files, assert_setup_component, mock_restore_cache)
|
||||||
|
|
||||||
from ...test_util.aiohttp import mock_aiohttp_client
|
|
||||||
|
|
||||||
TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}
|
TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}
|
||||||
|
|
||||||
@ -111,7 +109,6 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
self.assertEqual(device.config_picture, config.config_picture)
|
self.assertEqual(device.config_picture, config.config_picture)
|
||||||
self.assertEqual(device.away_hide, config.away_hide)
|
self.assertEqual(device.away_hide, config.away_hide)
|
||||||
self.assertEqual(device.consider_home, config.consider_home)
|
self.assertEqual(device.consider_home, config.consider_home)
|
||||||
self.assertEqual(device.vendor, config.vendor)
|
|
||||||
self.assertEqual(device.icon, config.icon)
|
self.assertEqual(device.icon, config.icon)
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@ -173,124 +170,6 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
"55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar")
|
"55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar")
|
||||||
self.assertEqual(device.config_picture, gravatar_url)
|
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(
|
@patch(
|
||||||
'homeassistant.components.device_tracker.DeviceTracker.see')
|
'homeassistant.components.device_tracker.DeviceTracker.see')
|
||||||
@patch(
|
@patch(
|
||||||
@ -463,7 +342,6 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
'entity_id': 'device_tracker.hello',
|
'entity_id': 'device_tracker.hello',
|
||||||
'host_name': 'hello',
|
'host_name': 'hello',
|
||||||
'mac': 'MAC_1',
|
'mac': 'MAC_1',
|
||||||
'vendor': 'unknown',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@ -495,9 +373,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
timedelta(seconds=0))
|
timedelta(seconds=0))
|
||||||
assert len(config) == 0
|
assert len(config) == 0
|
||||||
|
|
||||||
@patch('homeassistant.components.device_tracker.Device'
|
def test_see_state(self):
|
||||||
'.set_vendor_for_mac', return_value=mock_coro())
|
|
||||||
def test_see_state(self, mock_set_vendor):
|
|
||||||
"""Test device tracker see records state correctly."""
|
"""Test device tracker see records state correctly."""
|
||||||
self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN,
|
self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN,
|
||||||
TEST_PLATFORM))
|
TEST_PLATFORM))
|
||||||
|
@ -123,7 +123,5 @@ def mock_device_tracker_conf():
|
|||||||
), patch(
|
), patch(
|
||||||
'homeassistant.components.device_tracker.async_load_config',
|
'homeassistant.components.device_tracker.async_load_config',
|
||||||
side_effect=lambda *args: mock_coro(devices)
|
side_effect=lambda *args: mock_coro(devices)
|
||||||
), patch('homeassistant.components.device_tracker'
|
):
|
||||||
'.Device.set_vendor_for_mac'):
|
|
||||||
|
|
||||||
yield devices
|
yield devices
|
||||||
|
Loading…
x
Reference in New Issue
Block a user